Skip to content

fix: evaluate conditions against tick-start state (order independence)#36

Merged
zntznt merged 1 commit into
mainfrom
fix/synchronous-conditions
Jul 4, 2026
Merged

fix: evaluate conditions against tick-start state (order independence)#36
zntznt merged 1 commit into
mainfrom
fix/synchronous-conditions

Conversation

@zntznt

@zntznt zntznt commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Closes the last open item from the codebase review: activator and connection conditions read live pool values while pulls, gates, and converters mutate mid-tick, so a step's result could depend on node storage order, contradicting CONCEPTS.md section 1.

Semantics (hybrid synchronous)

  • doStep snapshots every node's state value before the fire phase; activator checks (_nodeEnabled) and source-value connection conditions (_connFires) read the snapshot through a single seam, _condValueOf
  • Trigger and reverse-trigger cascades keep live reads (queue entries carry a live flag): a cascade is a causal reaction within the step whose order comes from the graph, not storage, so live reads there are deterministic and intentional
  • Registers in conditions read their previous settled value, consistent with the existing one-step variable lag; variable-mode conditions were already lagged and are untouched
  • Movement is untouched: rates, fair allocation, capacity clamps, and the atomic push commit still read live state, so conservation cannot break
  • New CONCEPTS.md subsection documents the rule and the cascade exception

Evidence

  • 197 tests green (194 + 3 new); every new test verified to FAIL against the pre-fix engine via stash:
    • the original repro in both insertion orders produces identical, tick-start-correct traces
    • permutation property: identity / reversed / rotated node storage orders give byte-identical 25-step seeded traces
    • cascade exception: a trigger-fired node acts on live mid-step state while a snapshot-checked contrast node does not
  • All 11 built-in demos traced 300 seeded steps pre vs post: byte-identical. Not vacuous (the demos lean on activators and conditions heavily); their condition sources only change at the atomic commit or in variables, so tick-start and live reads coincide for them. No shipped behavior changes.

Activator and connection conditions used to read live pool values mid
tick while pulls, gates and converters mutate source pools immediately
during the fire phase, so whether a condition passed could depend on
node storage order (contradicting docs/CONCEPTS.md section 1).

doStep now snapshots every node's state value before the fire phase;
_nodeEnabled and _connFires read it via the new _condValueOf. Trigger
and reverse-trigger cascades are the deliberate exception: they pass
live=true and keep reading mid-step state, because a cascade's order is
causal (graph-defined) and therefore deterministic. Movement (rates,
fair allocation, capacity, converter/gate mechanics) still reads live
state, and variable-referencing conditions keep their documented
one-step lag.

Adds an insertion-order repro test, a node-array permutation property
test and a cascade live-read test; documents the semantics in
docs/CONCEPTS.md section 7. All 11 built-in demos produce byte-identical
300-step seeded traces before and after this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zntznt
zntznt merged commit 822ca6f into main Jul 4, 2026
1 check passed
@zntznt
zntznt deleted the fix/synchronous-conditions branch July 16, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant