refactor(parser): introduce AssemblyEnv with emit-time registries (U6-B1)#5573
Conversation
…-B1) Infrastructure for Unit 6's antecedent layer. Populated write-only: nothing reads it, so this is a provable no-op. U6-C flips the consumers over, keeping population and consumption in separate commits so a bisect can never land on the bookkeeping. AssemblyEnv carries, per clause: - `prov`: provenance parallel to `defs`. `origin` is `Option<ClauseId>`, NOT `ClauseId` — `apply_clause_continuation` pushes nodes of its own (e.g. the SearchDestination `ChangeZone`) that belong to no clause, and `FoldSearchIntoElse` binds to exactly such a node. `origin: None` is that case made concrete, and it is why a ClauseId-only arena key is insufficient. - role registries: last_dig / last_destroy_like / last_cast_from_zone / last_mana / last_play_from_exile / last_conditional / last_optional_for / last_search_destination. `last_conditional` is registered off the BUILT def's `condition`, never off `ClauseIr::condition`: the Emit path deliberately leaves `def.condition` unset for a `GenericEffect` whose condition was pushed down onto its `StaticDefinition`s, and BranchOtherwise's backward scan (`d.condition.is_some()`) intentionally skips those nodes. Reading the def mirrors the scan's truth exactly. `observe` is called after every statement that changes `defs.len()`, because a pop followed by a push in one region would otherwise let the new node silently inherit the popped node's provenance. NOT done, deliberately: the two `iter().any(..)` scans (`chain_has_revealed_subject`, `any_prior_publishes`) are NOT converted to accumulated flags. They are not equivalent by construction — see the report; this is a stop-and-report, not an oversight. Faithfulness: full engine lib 16111 passed / 0 failed, 79 snapshots zero drift and zero .snap.new, clippy -D warnings clean, combinator gate clean. `dead_code` allows are expected for exactly one increment (fields are written, consumed in U6-C).
There was a problem hiding this comment.
Code Review
This pull request introduces the AssemblyEnv struct and associated types (NodeRole, NodeProvenance, NodeRef) to track emit-time provenance and maintain role registries of the assembled effect chain. The AssemblyEnv::observe method is integrated throughout assemble_effect_chain to record provenance and recompute registries whenever defs is modified. This is a write-only bookkeeping increment preparing for future binding refactors. I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Parse changes introduced by this PR✓ No card-parse changes detected. |
Infrastructure for Unit 6's antecedent layer. Populated write-only: nothing reads
it, so this is a provable no-op. U6-C flips the consumers over, keeping population
and consumption in separate commits so a bisect can never land on the bookkeeping.
AssemblyEnv carries, per clause:
prov: provenance parallel todefs.originisOption<ClauseId>, NOTClauseId—apply_clause_continuationpushes nodes of its own (e.g. theSearchDestination
ChangeZone) that belong to no clause, andFoldSearchIntoElsebinds to exactly such a node.origin: Noneis that casemade concrete, and it is why a ClauseId-only arena key is insufficient.
last_play_from_exile / last_conditional / last_optional_for /
last_search_destination.
last_conditionalis registered off the BUILT def'scondition, never offClauseIr::condition: the Emit path deliberately leavesdef.conditionunset fora
GenericEffectwhose condition was pushed down onto itsStaticDefinitions, andBranchOtherwise's backward scan (
d.condition.is_some()) intentionally skips thosenodes. Reading the def mirrors the scan's truth exactly.
observeis called after every statement that changesdefs.len(), because a popfollowed by a push in one region would otherwise let the new node silently inherit
the popped node's provenance.
NOT done, deliberately: the two
iter().any(..)scans (chain_has_revealed_subject,any_prior_publishes) are NOT converted to accumulated flags. They are notequivalent by construction — see the report; this is a stop-and-report, not an
oversight.
Faithfulness: full engine lib 16111 passed / 0 failed, 79 snapshots zero drift and
zero .snap.new, clippy -D warnings clean, combinator gate clean.
dead_codeallowsare expected for exactly one increment (fields are written, consumed in U6-C).