Formula compiler and runtime Internals
Reactive Properties and Formulas covers what a formula does and how to write one. This is the deeper layer underneath — how a formula you type actually becomes running, tracked code, and what the editor is doing while you type it.
The compile pipeline
Source text goes through four real stages: Source text → Acorn parse → AST transform → Astring codegen → Compiled function. The AST transform step is where node references become tracked lookups rather than plain property access:
Ball.x → ctx.resolve('Ball', 'x')
this.width → this.getW()
Ball.x = 200 → ctx.set('Ball', 'x', 200)
Ball.reset() → ctx.callMethod('Ball', 'reset')A handful of node built-ins — is(typeOrTag) and destroy() — call directly on the node object rather than routing through callMethod, since they're intrinsic to every node rather than something you define.
Compile modes
The compiler distinguishes multiple contexts, and what's allowed differs by mode:
Formula mode parses a single expression only — no assignment, no variable declarations, no statements. The result of that expression is the property's value.
Event mode parses a full statement block: assignments, if/else, loops, local variables. An event handler also receives an event object carrying data specific to whatever triggered it.
Method mode also parses a statement block — a method is a named function defined on a node, callable from an event or another formula (this.reset(), Ball.explode()).
Dependency tracking and evaluation order
Every reactive value is backed by a real Signal. Reading a signal during a formula's evaluation automatically records that formula as a dependent — there's no manual subscription step anywhere.
Formulas form a directed acyclic graph, and the runtime evaluates it in dependency order using a genuine topological sort (Kahn's algorithm): if A depends on B, B always evaluates first, and every formula evaluates exactly once per frame — never twice, even if several of its dependencies change in the same frame.
Cycle detection happens before a formula ever runs, not while it's running. Before wiring a new formula in, the graph runs a depth-first search for cycles — if Ball.x depends on Wall.x and Wall.x depends on Ball.x, the formula is rejected with an error at the moment you write it, not as a runtime crash later.
Multiple changes in one frame batch into a single evaluation pass. Mouse x and y typically move together; a physics step can update many bodies in the same tick. Rather than re-evaluating on every individual change, the system defers the actual recalculation until the batch closes, then flushes the whole dirty set once, in topological order.
Editor tooling
A few things the formula editor itself does are worth knowing about, since they're not obvious from just typing in the field.
Per-node-ID color coding. Every node reference in a formula gets a color from a fixed 8-color palette (the same one Google Sheets uses for its reference highlighting) — Ball is always the same color everywhere you reference it in that session, Platform another, and the palette cycles once you exceed 8 distinct nodes in one formula. It's rendered via the browser's native CSS Custom Highlight API rather than DOM overlay elements, which is why it stays cheap even in a long formula.
Real, debuggable source maps. A compiled formula ships with an inline, VLQ-encoded source map and a synthetic sourceURL (foximation:///Ball/x-style). Open DevTools, find that source under the foximation:/// scheme, and you can set a real breakpoint — the debugger shows your original expression, not the compiled ctx.resolve() calls underneath it.
Property locking
Once a property has a formula bound to it, the property becomes locked against manual edits — dragging its gizmo or typing into its field directly won't silently fight the formula. There's one deliberate exception: a combined geometric transform (scaling from an anchor point, rotating around an origin) has to write x, y, and rotation together as a single atomic operation, so those specific handlers bypass the lock and then explicitly notify the formula system afterward so it still sees the change.
MathEval: arithmetic without a formula
A plain number field accepts inline arithmetic without the = prefix that starts a real formula. Type 200 + 50% into a width field and it resolves immediately — +, -, *, /, parentheses, % (relative to the field's current value), and px (an explicit absolute value) are all supported. This is a one-time calculation, not a reactive binding: it evaluates once, sets the field, and creates no dependency on anything.
For the everyday formula-writing experience - syntax, node references, globals, examples - see Reactive properties and formulas. For the built-in variables available everywhere (mouse, keys, time, dt, frame, Tag), see Formula globals.