Repository navigation
Let scenario values be arithmetic expressions #327
Description
Part of #326. Batch 1 — #328, #329 and #332 depend on it.
A scenario cannot compute. Reproducing the reel meant writing 316 lines of Python to emit the JSON, and every position in the result is a frozen literal: unreadable in the studio, uneditable by hand, and divorced from the intent that produced it. Placing eight badges on a circle, shading twenty-four facets by their angle to a light, or floating an element on a sine are all one line in the reference reels and impossible here.
Owned files
crates/rustmotion-core/src/expr/(new:lexer.rs,parser.rs,ast.rs,eval.rs,builtins.rs,mod.rs)crates/rustmotion/src/loader.rs(the static-folding pass)
Do NOT write: anything under engine/ (#328 owns the dependency graph), schema/scenario.rs (#336).
Deliverables
-
A value may be an expression — a string prefixed with
=:{ "x": "= $W/2 + cos($i / $count * TAU - PI/2) * 700 - 110" }Allowed wherever a number or a colour is:
styleproperties, root numeric fields, keyframe values, andat/delay/durationwhere the result is read as aTimePoint. -
A deliberately non-Turing-complete grammar. Literals,
+ - * / %, unary minus, parentheses, comparisons, a ternary, function calls. No loops, no user-defined functions, no recursion. Evaluation stays bounded, which is what makes it validatable and what keeps it out of the class of things that need a sandbox. -
Builtins —
sin cos tan atan2 sqrt pow abs min max clamp floor ceil round sign exp log lerp smoothstep, plusrand(seed)andnoise(x, seed). Both are pure functions of their arguments with no global state, so two renders of a file are identical. ConstantsPI,TAU,E. -
Scope —
$tscene time,$Tabsolute time,$beat,$i/$index/$item/$countinsidefor-each,$W/$H/$fps/$duration. Component params keep the existing$namesigil and the existing resolution order. -
Two evaluation tiers, and this is the decision that matters. An expression whose free variables are all known at load time is evaluated once during resolution and replaced by a literal, at zero per-frame cost. This tier alone removes the generator script. An expression reading
$t,$T,$beat, a variable or a node reference compiles once to a stack machine overf64and evaluates per frame, allocation-free in the hot loop. -
expression_erroras a hard validation error covering parse failures, unknown identifiers and arity mismatches, named and located the way the existingfor-eacherrors are. -
--fixrefuses a file using expressions, as it already refusesfor-each,useandinclude: path indices no longer match the source.
Acceptance
for-eachover eight items with"x": "= cos($i / $count * TAU) * 700"places them on a circle, folded to literals at load- A hostile expression (deep nesting, huge exponent) is rejected at load rather than hanging a render
- <= 15% render-time regression on
examples/mega-showcase.jsonwith per-frame expressions in play
Activity
[-]WS-1 timeline: absolute scene placement and a beat grid[/-][+]WS-2 expressions: an arithmetic layer for scenario values[/+][-]WS-2 expressions: an arithmetic layer for scenario values[/-][+]Let scenario values be arithmetic expressions[/+]