RUNE-301: RuneValue Proposed
Summary
RuneValue is a 4-byte NaN-boxed uint32_t that stores either a static IEEE-754 float, an ordinary animation slot index, or a derived token produced by expression and sampled-path outputs. It is the fundamental numeric transport type for animatable parameters in the rendering engine.
Motivation
Animation as a first-class citizen requires every numeric parameter to potentially be animated without bloating command payloads. RuneValue preserves float-sized storage and blittable ABI layout while still carrying snapshot-local indirections for animated and derived values.
Design
Bit Layout
static float: [S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM]
animated slot: [0 11111111 1IIIIIIIIIIIIIIIIIIIIIII]
derived token: [1 11111111 1TTTTTTTTTTTTTTTTTTTTTT]0x7FC00000is the canonical static NaN tag.- Ordinary animated values store
(index + 1)in the low 21 bits. - Derived values set the sign bit and store a non-zero token in the low 22 bits. Native resolves that token to an animation slot with
rune_value_derived_slot().
Detection
bool rune_value_is_animated(RuneValue v) {
if ((v & 0x7FC00000u) != 0x7FC00000u) return false;
const uint32_t mask = (v & 0x80000000u) != 0 ? 0x003FFFFFu : 0x001FFFFFu;
return (v & mask) != 0;
}Derived values intentionally participate in rune_value_is_animated() because replay resolves them through snapshot-local animation storage.
Encoding Rules
- static values are stored by bit-casting the original float
- user-provided NaN values are canonicalized to
0x7FC00000 - ordinary slot payload
0is reserved for canonical static NaN - derived token
0is invalid
Resolve
The render thread resolves RuneValues in two phases:
- evaluate ordinary animation descriptors into
snapshot->resolved[] - execute the ordered
DATA_EXPRESSIONbytecode so derived outputs fill their assigned slots
Replay then performs one lightweight branch and array lookup:
float resolve_value(RuneValue v, const float* resolved) {
if (rune_value_is_animated(v)) return resolved[rune_value_anim_index(v)];
return rune_value_to_float(v);
}Memory Model
RuneValueis copied by value and embedded inline in commands, data records, and metadata descriptors- ordinary animated values reference entries in the owning snapshot's
anims[]array - derived values are downstream-only outputs owned by the recording node's expression/sample program
- no float round-trip occurs before render-thread resolve, avoiding accidental payload canonicalization
Error Handling
- invalid or stale derived tokens are rejected during recording and liveness validation
- out-of-bounds slot resolution asserts in debug builds; release builds return
0.0f - user NaN input remains a static canonical NaN, not a forged animated payload
API Surface
typedef uint32_t RuneValue;[StructLayout(LayoutKind.Explicit, Size = 4)]
public readonly struct RuneValue {
[FieldOffset(0)] private readonly uint _bits;
}Performance
- static resolve: one predictable branch plus 4-byte reinterpret
- animated resolve: branch, slot decode, then snapshot-array lookup
- derived resolve reuses the same storage path after expression execution instead of introducing a second transport type
Alternatives Considered
| Approach | Rejected because |
|---|---|
Separate float + tag/flag fields | increases command size and marshal overhead |
| Distinct expression-output transport type | complicates every command/data encoding path |
double for everything | doubles storage without a documented need |
Dependencies
- RUNE-300 (ABI — blittable type, P/Invoke marshaling)
Test Strategy
- verify static float round-trip preserves bits except canonical NaN normalization
- verify ordinary animation slot encoding/decoding and invalid payload handling
- verify derived tokens resolve through
rune_value_derived_slot()into snapshot-local slots - verify C# helpers mirror native
IsAnimatedandAnimIndexbehavior - verify render-thread resolve executes ordinary slots before expression-derived outputs