Skip to content

refactor(engine): route collect-evidence and unless-bounce cost moves through the zone pipeline#6041

Merged
matthewevans merged 2 commits into
mainfrom
ship/t156-cost-residues
Jul 16, 2026
Merged

refactor(engine): route collect-evidence and unless-bounce cost moves through the zone pipeline#6041
matthewevans merged 2 commits into
mainfrom
ship/t156-cost-residues

Conversation

@matthewevans

Copy link
Copy Markdown
Member

Migrate the last two semantic-cost raw zone movers onto
zone_pipeline::move_object with ZoneMoveRequest::cost:

  • collect_evidence::handle_choice (CR 701.59a): selected evidence cards
    exile one at a time through the replacement-aware pipeline; a
    replacement choice parks the new typed
    PendingCostMoveResume::CollectEvidencePayment root (program-counter
    resume over the unpaid suffix, completion exactly once).
  • handle_unless_bounce_choice (CR 118.12): the selected return-to-hand
    unless cost parks PendingCostMoveResume::UnlessBouncePayment; the paid
    cost keeps the avoided effect avoided across delivered, redirected,
    and prevented outcomes (CR 118.11).

Both roots resume only through drain_pending_cost_move_resume at the
delivered and prevented boundaries — the eligibility tables are
extended, no dispatch-site sequential-ifs, no PostReplacementContinuation
growth, no pending_continuation reuse.

Witnesses: redirect-pause, prevented-boundary, and synchronous
regression coverage for both roots in cost_zone_pipeline; exactly-once
completion asserted on the CollectEvidence action event.

… through the zone pipeline

Migrate the last two semantic-cost raw zone movers onto
zone_pipeline::move_object with ZoneMoveRequest::cost:

- collect_evidence::handle_choice (CR 701.59a): selected evidence cards
  exile one at a time through the replacement-aware pipeline; a
  replacement choice parks the new typed
  PendingCostMoveResume::CollectEvidencePayment root (program-counter
  resume over the unpaid suffix, completion exactly once).
- handle_unless_bounce_choice (CR 118.12): the selected return-to-hand
  unless cost parks PendingCostMoveResume::UnlessBouncePayment; the paid
  cost keeps the avoided effect avoided across delivered, redirected,
  and prevented outcomes (CR 118.11).

Both roots resume only through drain_pending_cost_move_resume at the
delivered and prevented boundaries — the eligibility tables are
extended, no dispatch-site sequential-ifs, no PostReplacementContinuation
growth, no pending_continuation reuse.

Witnesses: redirect-pause, prevented-boundary, and synchronous
regression coverage for both roots in cost_zone_pipeline; exactly-once
completion asserted on the CollectEvidence action event.
@matthewevans
matthewevans enabled auto-merge July 16, 2026 19:29

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the engine's cost-payment pipeline to support replacement-aware moves for 'Collect Evidence' and 'Unless' bounce effects. It introduces new variants to PendingCostMoveResume and updates the engine's state machine to handle these costs through the zone_pipeline, ensuring that replacement effects are correctly processed. Integration tests were added to verify that these costs pause for and correctly resume after replacement events. The review comment correctly identifies a potential non-deterministic iteration issue when processing the chosen evidence cards and provides an idiomatic Rust fix using a seen set to maintain order.

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.

// needs `Cause::Cost` with the source extracted from the resume — left for
// the cost-payment tranche.
super::super::zones::move_to_zone(state, id, Zone::Exile, events);
move_evidence_costs(state, player, chosen.to_vec(), 0, resume.clone(), events)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Non-deterministic iteration over HashSet can lead to divergent game states. When deduplicating items during iteration, use a seen membership set (such as a HashSet) inside the loop to filter duplicates rather than collecting the entire collection into a HashSet first, in order to preserve the deterministic iteration order of the original collection.

let mut seen = std::collections::HashSet::new();
let mut chosen_vec = Vec::new();
for item in chosen {
    if seen.insert(item) {
        chosen_vec.push(item);
    }
}
move_evidence_costs(state, player, chosen_vec, 0, resume.clone(), events)
References
  1. When deduplicating items during iteration, use a seen membership set (such as a HashSet) inside the loop to filter duplicates rather than collecting the entire collection into a HashSet first, in order to preserve the deterministic iteration order of the original collection.

@github-actions

Copy link
Copy Markdown

Parse changes introduced by this PR

✓ No card-parse changes detected.

@matthewevans
matthewevans added this pull request to the merge queue Jul 16, 2026
Merged via the queue into main with commit d8dfbe2 Jul 16, 2026
13 checks passed
@matthewevans
matthewevans deleted the ship/t156-cost-residues branch July 16, 2026 20:12
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