fix(parser): Cybership combat-damage trigger — top-two to battlefield as 2/2 Cybermen under controller - #3461
Conversation
…yer's library onto the battlefi
…-combat-damage-trigger-put # Conflicts: # crates/engine/src/game/engine_resolution_choices.rs # crates/engine/src/game/morph.rs
The upstream merge refactored the manifest/morph zone-move pipeline. Two post-merge fixes keep cluster phase-rs#19's Cybership behavior compiling against the new API: - Pass `ability.source_id` to `manifest_card` (upstream added the required `source_id` attribution parameter to the zone-move call). - Handle the new `ControllerRef::ParentTargetOwner` variant in `resolve_enters_under`, mapping it to `TargetFilter::ParentTargetOwner` (CR 108.3 owner resolution + CR 110.2a entry-controller designation), mirroring the existing `ParentTargetController` arm. Behavior is unchanged: Cybership's combat-damage trigger still moves the top two cards of the damaged player's library onto the battlefield as 2/2 Cyberman artifact creatures under the Cybership controller (CR 110.2a). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
matthewevans
left a comment
There was a problem hiding this comment.
Maintainer review — changes requested (one blocking item)
Thanks — this is built on the right seam (reuses morph::manifest_card + the zone-change face_down() / under_control_of() builders rather than re-implementing face-down creation, routes the put-clause through Effect::Manifest, and enters_under is an established parameterization already on ChangeZone/ChangeZoneAll). CR annotations (110.2a / 708.2a / 701.40a / 603.7c) all verify. This is inline-mergeable once the blocking item is fixed — not engine-implementer-tier.
BLOCKING — single-authority violation (controller resolution)
crates/engine/src/game/effects/manifest.rs (resolve_enters_under) reinvents the canonical ControllerRef → PlayerId resolver. The engine already has the single authority, and there is already a wrapper for exactly this purpose:
crates/engine/src/game/filter.rs:678—controller_ref_player(state, source_id, source_controller, ability, cref)(exhaustive over everyControllerRefvariant).crates/engine/src/game/effects/change_zone.rs:61—resolve_enters_under_player, which already wraps it to resolve anenters_under: Option<&ControllerRef>on a zone-change (matching CR 110.2a annotation).
The hand-rolled version introduces latent semantic bugs the canonical resolver doesn't have:
- It collapses
ControllerRef::TargetPlayer | TriggeringPlayerboth toTargetFilter::TriggeringPlayer, whereascontroller_ref_playerkeeps them distinct (readsability.targetsforTargetPlayervstriggering_event_playerforTriggeringPlayer). - It silently falls
DefendingPlayer/ChosenPlayer/SourceChosenPlayerback toability.controller, while the canonical resolver resolves all three.
For Cybership today only ControllerRef::You is produced, so the bug is dormant — but per the codebase's single-authority hard rule, a divergent second implementation of player-reference resolution can't merge. Fix: delete resolve_enters_under and call crate::game::filter::controller_ref_player(state, ability.source_id, Some(ability.controller), Some(ability), &cref), or make change_zone::resolve_enters_under_player pub(crate) and reuse it (mirrors change_zone.rs:76).
Strong recommendation — add a discriminating Cybership test
There's no Cybership-named or combat-damage-driven integration test (grep -rln cybership crates/engine/tests is empty). The new manifest_with_profile_and_controller_override test drives resolve() with a manually-set current_trigger_event, which is shape-ish for the load-bearing trigger binding. Please add an integration test that crews/attacks Cybership and asserts the top two cards enter face-down 2/2 Cybermen under the Cybership controller's control (ownership staying with the library's owner) — that's the discriminating end-to-end guard for the mechanic this PR adds.
Happy to re-review promptly once the resolver is unified. The seam and the CR work are solid.
…ority Address maintainer review: delete the hand-rolled `resolve_enters_under` in manifest.rs and route the CR 110.2a `enters_under` override through the single canonical authority `change_zone::resolve_enters_under_player` (promoted to pub(crate)), which wraps `filter::controller_ref_player`. This eliminates the two latent bugs of the divergent resolver (TargetPlayer/TriggeringPlayer collapse; silent DefendingPlayer/ChosenPlayer fallback) so Manifest resolves controller overrides identically to ChangeZone/ChangeZoneAll. Add a discriminating end-to-end integration test that crews and attacks Cybership, asserting the top two cards of the damaged player's library enter face-down 2/2 Cyberman artifact creatures under the Cybership controller's control (CR 110.2a) while ownership stays with the library owner. Fix two semantic merge collisions from origin/main: zones.rs and issue_3285_face_down_public_zone.rs each call the now-7-arg `manifest_card` with the pre-controller 6-arg signature; pass `None` (CR 701.40a default).
Maintainer follow-up — resolved & enqueuingPushed a commit addressing both review items. BLOCKING (single-authority) — fixed. Deleted the hand-rolled Strong recommendation (integration test) — added. New Also fixed two semantic merge collisions from Verification: |
matthewevans
left a comment
There was a problem hiding this comment.
Single-authority controller resolution unified via the canonical resolver; discriminating Cybership combat-damage integration test added. Full engine suite green.
- oracle_static/dispatch.rs: compose the optional comma in the
GrantsExtraVillainousChoice and GrantsExtraVote phrase recognizers as a single
`opt(tag(","))` axis instead of two flat full-sentence `tag()` permutations,
per CLAUDE.md "compose combinators, don't enumerate permutations" (Gemini R1).
Applied to both twins so they stay consistent.
- Merged current origin/main (resolved the oracle_effect/mod.rs conflicts by
keeping both sides' independent helpers and tests; phase-rs#3419/phase-rs#3461 etc. have since
landed on main).
Verification: fmt + parser gate clean; clippy -p engine --all-targets -D
warnings clean; cargo test -p engine green (0 failed), incl.
valeyard_grants_extra_villainous_choice_static.
Summary
WHO misparse cluster #19. Cybership's combat-damage trigger previously misparsed. This branch parses and resolves it correctly: when this Vehicle deals combat damage to a player, the top two cards of that player's library are put onto the battlefield face down, under the Cybership controller's control (not the library owner's), as 2/2 Cyberman artifact creatures.
The parser now emits an
Effect::Manifestwithcount: 2, aprofileof a 2/2 Cyberman artifact creature,target: TriggeringPlayer(the damaged player's library), andenters_under: You(the CR 110.2a controller redirect). The runtime routes the face-down entry through the shared morph/manifest zone-move pipeline with the controller override applied.This branch was merged with
upstream/main, which concurrently refactored the manifest/morph zone-move pipeline to aZoneMoveRequestbuilder. Two conflicts were reconciled and two follow-on compile fixes were applied so cluster #19's behavior is preserved against the new API.Cards
Whenever this Vehicle deals combat damage to a player, put the top two cards of that player's library onto the battlefield face down under your control. They're 2/2 Cyberman artifact creatures.Files changed
Parser / AST:
parser/oracle_effect/imperative.rs,parser/oracle_effect/mod.rs,parser/oracle_effect/sequence.rs— parse the "put the top N cards of that player's library onto the battlefield face down under your control" manifest clause with controller redirect.parser/oracle_trigger.rs,parser/oracle_ir/ast.rs— trigger plumbing for the combat-damage manifest.types/ability.rs—Effect::Manifestcarriescount,profile, andenters_under(controller redirect).Runtime:
game/effects/manifest.rs—Effect::Manifestresolver: resolves the library owner, the optional CR 110.2a controller override, and manifests each card through the shared morph helper.game/effects/manifest_dread.rs,game/effects/mod.rs— registration/plumbing.game/morph.rs—manifest_cardapplies theunder_control_ofcontroller override when present.game/engine_resolution_choices.rs— Manifest Dread choice path (reconciled to the new builder API).Test:
tests/integration/issue_2890_reality_shift.rs— signature update.Merge reconciliation
game/morph.rs: kept upstream's correctsource_idattribution on the zone-move call AND preserved cluster chore: update coverage stats and badges #19's.under_control_of(controller)redirect (CR 110.2a).game/engine_resolution_choices.rs: took upstream's refactoredZoneMoveRequest::effect(...).face_down(...)builder form for the Manifest Dread path (no controller redirect there — owner == controller).game/effects/manifest.rs: passedability.source_idto the now-source_id-bearingmanifest_card, and added the newControllerRef::ParentTargetOwner → TargetFilter::ParentTargetOwnerarm inresolve_enters_under.CR refs
ParentTargetOwnercontroller-ref maps to the parent target object's owner).Verification
cargo fmt --all— clean.cargo clippy -p engine --all-targets -- -D warnings— clean.cargo test -p engine— all suites green.cargo run --profile tool --features cli --bin oracle-gen -- data --filter "cybership"— emits theDamageDone(CombatOnly) trigger withEffect::Manifest { target: TriggeringPlayer, count: 2, profile: 2/2 Cyberman artifact, enters_under: You }.🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com