π€ AI text below π€
Summary
GameState::chain_tracked_set_id carries transient mid-chain residue into CR 104.4b loop-equality comparison: normalize_for_loop() clears its lockstep partner resolving_modal_instruction (fixed in #7484, commit 976eb923d β merged 83f61d8d1) but deliberately does not clear chain_tracked_set_id. Two otherwise-identical loop states that differ only in this transient field then never compare equal, so the loop detector cannot confirm a repeated position β a false negative for mandatory-loop draw detection (CR 104.4b) in any loop whose sampled states sit mid-chain.
In-tree pin (merged to main)
crates/engine/src/types/game_state.rs, inside normalize_for_loop() (~line 22143 at f7c44692f):
// NOTE: its lockstep partner `chain_tracked_set_id` carries the same
// residue and is deliberately NOT cleared here β that is pre-existing
// behavior with its own follow-up, not something this line may widen.
clone.resolving_modal_instruction = None;
The field's own doc (game_state.rs ~15655) states it is "Cleared at the top-level chain entry (depth == 0) in resolve_ability_chain" β i.e. it is Some(..) precisely while a chain is mid-resolution, which is exactly when a paused/sampled loop state can be captured.
Why it was split out rather than fixed in #7484
The resolving_modal_instruction clear was a review fix scoped to the mode latch; widening it to the partner field mid-review would have changed loop-equality behavior for a field #7484 otherwise never touched. The two fields are documented as a cleared-in-the-same-prelude pair, so the residue classes are the same; the fix is the symmetric one-line clear plus a discriminating test showing a mid-chain loop state pair that compares equal only with the clear (and a negative control showing depth-0 states are unaffected, since depth-0 already resets the field).
Provenance
Found during the #6857/#7484 lane while fixing the partner field. Pre-existing; #7484 neither caused nor widened it.
π€ AI text below π€
Summary
GameState::chain_tracked_set_idcarries transient mid-chain residue into CR 104.4b loop-equality comparison:normalize_for_loop()clears its lockstep partnerresolving_modal_instruction(fixed in #7484, commit976eb923dβ merged83f61d8d1) but deliberately does not clearchain_tracked_set_id. Two otherwise-identical loop states that differ only in this transient field then never compare equal, so the loop detector cannot confirm a repeated position β a false negative for mandatory-loop draw detection (CR 104.4b) in any loop whose sampled states sit mid-chain.In-tree pin (merged to main)
crates/engine/src/types/game_state.rs, insidenormalize_for_loop()(~line 22143 atf7c44692f):The field's own doc (
game_state.rs~15655) states it is "Cleared at the top-level chain entry (depth == 0) inresolve_ability_chain" β i.e. it isSome(..)precisely while a chain is mid-resolution, which is exactly when a paused/sampled loop state can be captured.Why it was split out rather than fixed in #7484
The
resolving_modal_instructionclear was a review fix scoped to the mode latch; widening it to the partner field mid-review would have changed loop-equality behavior for a field #7484 otherwise never touched. The two fields are documented as a cleared-in-the-same-prelude pair, so the residue classes are the same; the fix is the symmetric one-line clear plus a discriminating test showing a mid-chain loop state pair that compares equal only with the clear (and a negative control showing depth-0 states are unaffected, since depth-0 already resets the field).Provenance
Found during the #6857/#7484 lane while fixing the partner field. Pre-existing; #7484 neither caused nor widened it.