Skip to content

Engine: the DraftFromSpellbook/ManifestDread tracked-set publish belongs in the WaitingFor continuation handler (Kayla's Kindling, Valgavoth's Onslaught, Dazzling Flameweaver) #7467

Description

@lgray

🤖 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 publish an empty tracked set and every downstream
TargetFilter::TrackedSet anaphor binds nothing.

DraftFromSpellbook (2 gated rows) and ManifestDread (1 gated row) are filed together because they
share one mechanism: each parks a WaitingFor mid-resolution, and the objects they produce land on
the continuation's event vector — not on the resolver's.
A compensating publish therefore belongs
in the continuation handler, not in affected_objects_from_events.

Both heads are excluded from the #6857 PR because they were dispositioned by code-read only and were
never runtime-measured
. Under the maintainer's measured-core ruling that is disqualifying.

The mechanism

head parks handler
DraftFromSpellbook WaitingFor::SpellbookDraft (crates/engine/src/game/effects/spellbook.rs:83) engine_resolution_choices.rs:6428, GameAction::SubmitSpellbookDraft -> spellbook::complete_draft
ManifestDread WaitingFor::ManifestDreadChoice (crates/engine/src/game/effects/manifest_dread.rs:66) engine_resolution_choices.rs:1785, GameAction::SelectCards

Each head has two arms, and only one of them is the continuation case:

  • ManifestDread with count == 1 manifests synchronously via morph::manifest_card ->
    zone_pipeline::move_object, which emits ZoneChanged — the existing _ => harvest already sees it.
    count >= 2 parks ManifestDreadChoice.
  • DraftFromSpellbook's random arm conjures synchronously and emits
    GameEvent::ObjectConjured { object_id } unconditionally (conjure.rs:221); the ZoneChanged at
    conjure.rs:217 is gated on destination == Battlefield and both spellbook cards conjure to
    Exile, so a ZoneChanged-only harvest sees nothing there. The choice arm parks SpellbookDraft.

Precedent — the same pattern already exists twice

engine_resolution_choices.rs already computes effects::chain_references_tracked_set(...) inside a
continuation handler and publishes when it is true:

// engine_resolution_choices.rs:979
    .is_some_and(|continuation| effects::chain_references_tracked_set(&continuation.chain));
if continuation_consumes_tracked_set {
    effects::publish_fresh_tracked_set(state, chosen.to_vec());
}
// engine_resolution_choices.rs:4325
    .is_some_and(|cont| effects::chain_references_tracked_set(&cont.chain));
if continuation_consumes_tracked_set {
    effects::publish_fresh_tracked_set(state, chosen.clone());
}

chain_references_tracked_set (effects/mod.rs:5118) is the same predicate as
next_sub_needs_tracked_set under a second name, so a continuation publish here would be gated
identically to the resolver publish — no new authority, an existing one applied at the right seam.

Candidate cards

head card arm
DraftFromSpellbook kayla's kindling choice arm (SpellbookDraft continuation)
DraftFromSpellbook dazzling flameweaver random arm (synchronous, ObjectConjured)
ManifestDread valgavoth's onslaught count >= 2 parks the continuation; count == 1 is synchronous

What a future PR needs

  1. Runtime measurement of all three rows — none exists today. Both arms of each head need coverage
    (continuation and synchronous), because they reach the publish through different code.
  2. A control that pins the continuation publish not double-publishing the synchronous path (the
    count == 1 manifest and the random spellbook arm already emit their own events).
  3. A rich projection: tracked_object_sets plus per-object zone/counters and the pending WaitingFor.
    A sets-only projection has already produced wrong verdicts elsewhere in this lane.

Evidence provenance

Everything above is code-read and AST classification at BASE 9b7c66e30 — file/line citations are
direct reads. No runtime measurement exists for any of these three rows, and no runtime symptom is
asserted for them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions