π€ AI text below π€
Related: #6857
Summary
#6857's root cause is that affected_objects_from_events
(crates/engine/src/game/effects/mod.rs:5590) has no arm for effect heads that affect objects without
emitting a per-object event: they fall through to the _ => ZoneChanged harvest, publish an
empty tracked set, and every downstream TargetFilter::TrackedSet anaphor binds nothing.
The #6857 PR contracted, by maintainer ruling, to a measured core β only heads where every
firing row is dispositioned by runtime measurement ship (PumpAll, GoadAll, GiveControl).
TargetOnly is the largest excluded head and is carried here. This is scope, not a deferral.
Numbers (corrected census, BASE 9b7c66e30)
82 production-gated TargetOnly rows. No structural partition of that 82 into
"would publish" / "structurally excluded" was computed for this head β the census round that computed
that partition covered GoadAll, GiveControl, CopySpell, PumpAll and Pump only. So the count
of rows that would actually publish is unknown, which is itself part of the burden.
What is known: an earlier round measured 13 previously-unnamed TargetOnly rows at runtime β
1 FIX (march of progress), 5 PRESERVED (archfiend of depravity, disorienting choice, lucius the
eternal, run away together, time to feed), 5 REGRESSION (cryptek, fatal fissure, reincarnation,
rushed rebirth, together forever β all removed by the delayed-trigger exclusion), and 2
behaviour-change rows detailed below. That is 13 of 82.
Three rows worth recording individually
trial of agony β a caster-advantageous wrong-player prompt (root cause is #7460)
Card: "Choose two target creatures controlled by the same opponent. That player chooses one of
those creatures. Trial of Agony deals 5 damage to that creature, and the other can't block this turn."
Parsed chain: TargetOnly{Creature} -> ChooseFromZone{1, Exile, chooser: Controller} -> DealDamage{5, TriggeringSource}.
|
HEAD |
with the publish |
| prompt |
none (Priority) |
ChooseFromZoneChoice |
| addressee (measured) |
β |
CONTROLLER (PlayerId(0)) β the caster |
| prompt contents |
β |
cards=[ObjectId(1), ObjectId(2)] count=1 up_to=false |
| damage before the answer |
Alpha 5, Beta 5 |
Alpha 0, Beta 0 |
| damage after answering "Alpha" |
(n/a) |
Alpha 5, Beta 0 |
| "the other can't block" |
absent |
absent |
Two separable findings:
Also relevant: the pool this prompt offers is subject to the ChooseFromZone chain-set pre-emption
defect filed alongside this issue.
deliver unto evil β prompt correct, outcome moot
Card: "β¦ Otherwise, an opponent chooses two of them. Leave the chosen cards in your graveyard and
put the rest into your hand."
Addressee measured CORRECT: OPPONENT (PlayerId(1)), count=2, both cards offered β matching the
card. The prompt is the only observable: both the if and the else branch terminate in
Effect::Unimplemented{"Leave the chosen cards in your graveyard and put the rest into your hand"}, so
there is no post-choice outcome to be right or wrong about. Net: HEAD silently skips a prompt the card
requires; the publish surfaces the correct prompt in front of a dead tail.
This row and trial of agony have the same surface shape (Priority -> ChooseFromZoneChoice with a
populated pool) and opposite addressees β the discriminator is the parsed chooser field, which is
exactly #7460's subject.
the doctor's childhood barn β measured UNREACHABLE; the card stays broken
Census row: trigger[0], head TargetOnly, repeat_for=true, later_pub=true. The
SetTapState node below it is itself in publisher position β its own sub-chain is
PhaseOut -> GenericEffect{ParentTarget, CantPhaseIn}, which consumes β so leg 2 of the publish
guard declines and the new TargetOnly arm never runs.
HEAD sets=[(1, [1])] #1 Theirs phased_in=false tap=TRUE TCE CantPhaseIn on #1
CANDIDATE sets=[(1, [1])] #1 Theirs phased_in=false tap=TRUE TCE CantPhaseIn on #1 <- byte-identical
The mechanism is leg 2 β explicitly NOT the empty-ability.targets repeat_for fan-out story
proposed in an earlier review round (the shellshock shape). ability.targets here is populated.
Discriminating pair, measured: the same fixture with the "can't phase in" clause removed clears leg 2,
and the candidate then does untap the permanent (tap=true -> tap=false). So the publish mechanism
works and leg 2 is precisely what blocks this card.
What a future PR needs
- A structural partition of the 82 rows (leg-2 declines + delayed-trigger exclusion) so the firing
count is known at all.
- Runtime disposition of the firing remainder beyond the 13 already measured.
- The
ChooseFromZone pool defect resolved first for the rows whose consumer is a ChooseFromZone
(trial of agony, deliver unto evil) β otherwise a publish arm converts "silently wrong" into
"wrongly prompted".
Evidence provenance
Row count from a census whose predicate is production's own next_sub_needs_tracked_set (called, not
mirrored) over the full card export at BASE 9b7c66e30. Runtime rows measured one-binary, env-toggled,
with a known-changed control and a no-consumer negative control in each run; prompt addressees resolved
explicitly via WaitingFor::acting_player(). The 69 rows not enumerated above are unmeasured and no
runtime symptom is asserted for them.
π€ AI text below π€
Related: #6857
Summary
#6857's root cause is that
affected_objects_from_events(
crates/engine/src/game/effects/mod.rs:5590) has no arm for effect heads that affect objects withoutemitting a per-object event: they fall through to the
_ =>ZoneChangedharvest, publish anempty tracked set, and every downstream
TargetFilter::TrackedSetanaphor binds nothing.The #6857 PR contracted, by maintainer ruling, to a measured core β only heads where every
firing row is dispositioned by runtime measurement ship (
PumpAll,GoadAll,GiveControl).TargetOnlyis the largest excluded head and is carried here. This is scope, not a deferral.Numbers (corrected census, BASE
9b7c66e30)82 production-gated
TargetOnlyrows. No structural partition of that 82 into"would publish" / "structurally excluded" was computed for this head β the census round that computed
that partition covered
GoadAll,GiveControl,CopySpell,PumpAllandPumponly. So the countof rows that would actually publish is unknown, which is itself part of the burden.
What is known: an earlier round measured 13 previously-unnamed
TargetOnlyrows at runtime β1 FIX (march of progress), 5 PRESERVED (archfiend of depravity, disorienting choice, lucius the
eternal, run away together, time to feed), 5 REGRESSION (cryptek, fatal fissure, reincarnation,
rushed rebirth, together forever β all removed by the delayed-trigger exclusion), and 2
behaviour-change rows detailed below. That is 13 of 82.
Three rows worth recording individually
trial of agony β a caster-advantageous wrong-player prompt (root cause is #7460)
Card: "Choose two target creatures controlled by the same opponent. That player chooses one of
those creatures. Trial of Agony deals 5 damage to that creature, and the other can't block this turn."
Parsed chain:
TargetOnly{Creature} -> ChooseFromZone{1, Exile, chooser: Controller} -> DealDamage{5, TriggeringSource}.Priority)ChooseFromZoneChoicePlayerId(0)) β the castercards=[ObjectId(1), ObjectId(2)] count=1 up_to=falseTwo separable findings:
both.
is addressed to the spell's controller. That is Parser: ChooseFromZone chooser defaults to Controller where the card names an opponent (Intuition, Thran Tome, Trial of Agony + 2)Β #7460 β a parser defect (
chooser: Controllerwhere the card names an opponent), not a publish-authority defect. The publish only supplies the
pool;
resolve_choosersupplies the addressee. The publish makes the pre-existing misparseobservable; it does not cause it.
parser gap. The card cannot be made fully correct by a publish-authority change even after Parser: ChooseFromZone chooser defaults to Controller where the card names an opponent (Intuition, Thran Tome, Trial of Agony + 2)Β #7460.
Also relevant: the pool this prompt offers is subject to the
ChooseFromZonechain-set pre-emptiondefect filed alongside this issue.
deliver unto evil β prompt correct, outcome moot
Card: "β¦ Otherwise, an opponent chooses two of them. Leave the chosen cards in your graveyard and
put the rest into your hand."
Addressee measured CORRECT:
OPPONENT (PlayerId(1)),count=2, both cards offered β matching thecard. The prompt is the only observable: both the
ifand theelsebranch terminate inEffect::Unimplemented{"Leave the chosen cards in your graveyard and put the rest into your hand"}, sothere is no post-choice outcome to be right or wrong about. Net: HEAD silently skips a prompt the card
requires; the publish surfaces the correct prompt in front of a dead tail.
This row and trial of agony have the same surface shape (
Priority->ChooseFromZoneChoicewith apopulated pool) and opposite addressees β the discriminator is the parsed
chooserfield, which isexactly #7460's subject.
the doctor's childhood barn β measured UNREACHABLE; the card stays broken
Census row:
trigger[0], headTargetOnly,repeat_for=true,later_pub=true. TheSetTapStatenode below it is itself in publisher position β its own sub-chain isPhaseOut -> GenericEffect{ParentTarget, CantPhaseIn}, which consumes β so leg 2 of the publishguard declines and the new
TargetOnlyarm never runs.The mechanism is leg 2 β explicitly NOT the empty-
ability.targetsrepeat_forfan-out storyproposed in an earlier review round (the shellshock shape).
ability.targetshere is populated.Discriminating pair, measured: the same fixture with the "can't phase in" clause removed clears leg 2,
and the candidate then does untap the permanent (
tap=true -> tap=false). So the publish mechanismworks and leg 2 is precisely what blocks this card.
What a future PR needs
count is known at all.
ChooseFromZonepool defect resolved first for the rows whose consumer is aChooseFromZone(trial of agony, deliver unto evil) β otherwise a publish arm converts "silently wrong" into
"wrongly prompted".
Evidence provenance
Row count from a census whose predicate is production's own
next_sub_needs_tracked_set(called, notmirrored) over the full card export at BASE
9b7c66e30. Runtime rows measured one-binary, env-toggled,with a known-changed control and a no-consumer negative control in each run; prompt addressees resolved
explicitly via
WaitingFor::acting_player(). The 69 rows not enumerated above are unmeasured and noruntime symptom is asserted for them.