Skip to content

Fix Alacrian Jaguar#4

Closed
keloide wants to merge 1 commit into
mainfrom
claude/phase-developer-track-fjqa6b
Closed

Fix Alacrian Jaguar#4
keloide wants to merge 1 commit into
mainfrom
claude/phase-developer-track-fjqa6b

Conversation

@keloide

@keloide keloide commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the "attacks while saddled" trigger-gate misparse for Alacrian Jaguar and its whole 27-card class (parser-misparse-backlog categories 2 and 22). "Whenever this creature attacks while saddled, …" parsed to TriggerMode::Attacks with condition: null — the while-gate was silently dropped, so the trigger fired even when the Mount was unsaddled. Official ruling (2025-02-07): the ability triggers only if the creature was saddled when declared as an attacker (CR 508.1); the saddled designation persists until end of turn or battlefield exit (CR 702.171b).

Design: an elided-subject participle leaf combinator (parse_elided_subject_state_condition, bare "saddled"StaticCondition::SourceIsSaddled) composed only at the existing strip_while_state_clause while-gate seam; the SourceIsSaddled bridge arm lowers to the existing TriggerCondition::SourceMatchesFilter + FilterProp::IsSaddled via the shared source_saddled_filter() helper — the /add-engine-variant gate rejected a bespoke TriggerCondition::SourceIsSaddled sibling (REFUSE_WITH_EXISTING_SLOT), so zero new enum variants. The saddled designation is additionally threaded through last-known information (LKISnapshot.is_saddled / ZoneChangeRecord.is_saddled, #[serde(default)], mirroring is_suspected) so the CR 603.4-machinery resolution recheck survives battlefield exit (CR 608.2h + CR 113.7a) — without it, a Mount destroyed in response would wrongly nullify its own trigger (Ornery Tumblewagg / Giant Beaver shapes). Backlog list hygiene included: 19 confirmed-fixed entries removed (11 from category 2, 8 from category 22), counts decremented, category-22 fix hint updated to the landed design.

Implementation method (required)

Method: /engine-implementer

Plan-review ran 2 rounds (round 1: one blocker — the original plan's LKI claim was factually wrong, and was redesigned into the Part-B LKI thread — plus 2 minor findings; round 2: approvable with one reviewer-prescribed mechanical amendment — the loop_shortcut.rs golden Debug string — folded in verbatim). Implementation-review ran 1 full round returning exactly one LOW finding (a comment referencing a plan-internal test label), fixed with code; the mandatory final read-only review pass then ran clean against the committed head (PASS line below).

Files changed

  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/parser/oracle_trigger_tests.rs
  • crates/engine/src/types/game_state.rs
  • crates/engine/src/game/{zones,game_object,filter,stack}.rs, game/effects/mod.rs (production is_saddled threading)
  • crates/engine/src/game/{quantity,layers,derived_views,specialize,ability_utils,log,triggers}.rs, game/effects/{copy_spell,delayed_trigger,counters,manifest}.rs (compiler-forced test-literal fills)
  • crates/engine/tests/integration/attacks_while_saddled_trigger.rs (new) + main.rs
  • crates/engine/tests/integration/{loop_shortcut,issue_2890_reality_shift,issue_3277_captain_nghathrod_eliminated_opponent,issue_5332_gandalf_trigger_doubling,madame_null_integration}.rs (forced literal/golden updates)
  • docs/parser-misparse-backlog.md

CR references

CR 113.7a, CR 508.1, CR 603.2, CR 603.4, CR 608.2h, CR 701.60b, CR 702.171a, CR 702.171b, CR 702.171c, CR 704.5m/n (mirrored comment) — every number grep-verified against docs/MagicCompRules.txt before annotation.

Track

Developer

LLM

Model: claude-fable-5
Thinking: high

Tier: Frontier

Verification

  • Required checks ran clean, or the exact CI-owned alternative is stated below.
  • Gate A output below is for the current committed head.
  • Final review-impl below is clean for the current committed head.
  • Both anchors cite existing analogous code at the same seam.

Tilt is down in this environment; direct cargo used (sanctioned fallback). Container is disk-limited, so the full 145-binary integration link was not run locally — the touched integration binaries were run individually; CI covers the rest.

  • cargo fmt --all — clean (no diff)
  • cargo clippy -p engine --lib --tests -- -D warnings — exit 0
  • cargo test -p engine --lib17,165 passed / 0 failed
  • cargo test -p engine --test integration attacks_while_saddled4 passed / 0 failed (real saddle → declare-attackers → stack pipeline; the unsaddled negative carries a condition-presence reach-guard and fails on revert; the Ornery Tumblewagg dies-in-response fixture proves the LKI recheck: counters still double 3→6 after the Mount leaves the battlefield)
  • cargo test -p engine --test integration -- saddle_state_model attack_qualifier loop_shortcut52 passed / 0 failed (sibling guards)
  • ./scripts/gen-card-data.sh — all 27 "attacks while saddled" cards now carry SourceMatchesFilter{Typed([IsSaddled])} on their attacks trigger; 26/27 fully clean; Calamity, Galloping Inferno retains only its pre-existing, unrelated copy-ability Unimplemented
  • cargo coverage — Alacrian Jaguar: supported: true, gap_count: 0; global supported count unchanged 31454 / 35425 before vs after (misparse fix — no support flips, coverage stays honest)
  • ./scripts/check-parser-combinators.sh — exit 0 (output below)

Gate A

Gate A PASS head=a7e100b3cf91622b4f26500fbe8838fbad1645de base=f741e3ab819a2e9ee4a04bc9c16a27eccdc7a0bd

Anchored on

  • crates/engine/src/parser/oracle_trigger.rs:4252 — SourceIsEnchanted => Some(TriggerCondition::SourceMatchesFilter { … }): the existing designation/state bridge arm in static_condition_to_trigger_condition whose exact shape the new SourceIsSaddled arm follows (same SourceMatchesFilter lowering, no bespoke variant).
  • crates/engine/src/parser/oracle_trigger.rs:8158 — strip_while_state_clause: the existing "while [state]" trigger-gate seam (Fire Lord Azula class) that delegates to parse_inner_condition; the fix widens exactly this call site with an alt() fallback.
  • crates/engine/src/parser/oracle_nom/condition.rs:1859 — parse_source_is_saddled: the subject-ful sibling combinator (same value/tag family) beside which the new elided-subject leaf lives.
  • crates/engine/src/types/game_state.rs:318 + crates/engine/src/game/filter.rs:5106 — is_suspected LKI field + FilterProp::Suspected => record.is_suspected evaluator arm: the precedent the is_saddled LKI thread mirrors field-for-field.

Final review-impl

Final review-impl PASS head=a7e100b3cf91622b4f26500fbe8838fbad1645de

Claimed parse impact

Alacrian Jaguar, Autarch Mammoth, Bounding Felidar, Bridled Bighorn, Brightfield Glider, Brightfield Mustang, Bulwark Ox, Calamity Galloping Inferno (gate fixed; unrelated pre-existing copy gap remains), Congregation Gryff, District Mascot, Dracosaur Auxiliary, Drover Grizzly, Fortune Loyal Steed, Giant Beaver, Gila Courser, Gilded Ghoda, Gloryheath Lynx, Guardian Sunmare, Lagorin Soul of Alacria, Ornery Tumblewagg, Quilled Charger, Rambling Possum, Routeway Moose, Seraphic Steed, Trained Arynx, Unswerving Sloth, Venomsac Lagac

Validation Failures

None.

CI Failures

None.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KckwxCXXvTgVgwLq2tQkyb


Generated by Claude Code

…ignation

"Whenever this creature attacks while saddled" parsed to TriggerMode::Attacks
with condition: null — the while-gate was silently dropped, so the trigger
fired even when the Mount was unsaddled. Fixes the whole 27-card class
(Alacrian Jaguar target; parser-misparse-backlog categories 2 and 22).

Parser: new elided-subject participle leaf combinator
(parse_elided_subject_state_condition, bare "saddled" ->
StaticCondition::SourceIsSaddled) composed only at the strip_while_state_clause
seam via alt() after the subject-ful parse_inner_condition authority; the
SourceIsSaddled bridge arm in static_condition_to_trigger_condition lowers to
the existing TriggerCondition::SourceMatchesFilter + FilterProp::IsSaddled via
the shared source_saddled_filter() helper (no new enum variants; CR 603.2 +
CR 702.171b, official ruling 2025-02-07: the gate binds at attack declaration,
CR 508.1).

Runtime: thread the saddled designation through last-known information so the
CR 603.4-machinery resolution recheck survives battlefield exit —
LKISnapshot.is_saddled + ZoneChangeRecord.is_saddled (#[serde(default)],
mirroring is_suspected), populated at every production snapshot site, and
FilterProp::IsSaddled moved out of the zone-change fail-closed arm to read
record.is_saddled (CR 608.2h + CR 113.7a). The recheck is truth-preserving:
saddle activates only as a sorcery (CR 702.171a) and the designation persists
until end of turn or exit (CR 702.171b).

Tests: parser shape test with no-Unimplemented reach-guard plus four
real-pipeline combat tests (saddled pump, unsaddled negative with
condition-presence reach-guard, per-source gating in a shared attack, and the
Ornery Tumblewagg dies-in-response LKI fixture: counters still double after
the Mount leaves the battlefield).

Backlog hygiene: 19 confirmed-fixed entries removed (11 from category 2,
8 from category 22); counts decremented; category-22 fix hint updated to the
landed design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KckwxCXXvTgVgwLq2tQkyb

keloide commented Jul 20, 2026

Copy link
Copy Markdown
Owner Author

Superseded by the upstream contribution: phase-rs#6232 (same branch, same head a7e100b). Closing this fork-internal PR to avoid a double merge.


Generated by Claude Code

@keloide keloide closed this Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants