Skip to content

Fix Ardenvale Paladin - #6780

Merged
matthewevans merged 4 commits into
phase-rs:mainfrom
keloide:claude/phase-developer-track-card-o1e67k
Aug 1, 2026
Merged

Fix Ardenvale Paladin #6780
matthewevans merged 4 commits into
phase-rs:mainfrom
keloide:claude/phase-developer-track-card-o1e67k

Conversation

@keloide

@keloide keloide commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix Ardenvale Paladin and its class. The Adamant cycle parsed its "enters with a +1/+1 counter" clause as a CR 614.1c replacement effect but silently dropped the sentence-initial If <condition>, gate, leaving ReplacementDefinition.condition = None. The engine therefore gave every copy of these creatures the counter regardless of what mana was actually spent — silently-wrong game behaviour that nothing flagged at runtime. Coverage surfaced it as Swallow:Condition_If on six cards.

Root cause (crates/engine/src/parser/oracle_replacement.rs): the module peeled a trailing " unless <cond>" gate (extract_enters_with_unless_suffix) and a trailing " if <cond>" gate (extract_enters_with_only_if_suffix), but nothing peeled a leading one — and the payload scan skips everything before "enters with".

Fixed for the class, not the cards:

  • extract_enters_with_leading_if_gate mirrors the existing trailing extractor one-for-one, including its fail-closed tri-state. Condition recognition delegates to parse_inner_condition (the single authority), so the entire static-condition grammar is now reachable from the sentence-initial position, where previously zero shapes were. An unrecognised gate returns Unparsed and the clause falls through to Effect::unimplemented rather than committing a replacement with the gate erased.
  • Ability-word labels are peeled with parse_known_ability_word_name — the curated ABILITY_WORD_NAMES list (the CR 207.2c ability words plus three curated CR 207.2d markers), not the loose 4-word strip_ability_word heuristic. That distinction is load-bearing: the heuristic would peel any short em-dash label, including Scarlet Spider, Ben Reilly's "Sensational Save", exposing its body to the "if " test and regressing a green card to Unparsed. The three curated flavour markers are peelable, so this is a bounded widening rather than a strict CR 207.2c gate — accepted explicitly: no corpus card writes that shape, and a future one would fail closed to Effect::unimplemented rather than silently drop its gate.
  • CastManaSpentMetric::OfColor { color } parameterises the existing metric axis (Total | DistinctColors | FromSource) instead of adding a ReplacementCondition sibling, so the parsed condition flows through the untouched replacement_condition_from_static into the existing OnlyIfQuantity. Zero new ReplacementCondition variants, zero new condition-evaluation arms.
  • parse_color_mana_spent_threshold reuses the shared parse_amount_threshold authority, so the comparator axis (at least N / N or more / N or fewer) is carried rather than hardcoded to GE.

Cards fixed (6): Ardenvale, Embereth, Garenbrig, Locthwain and Vantress Paladin, plus Necromantic Summons. Dust Animus keeps supported=true and gains its gate — it is correctly conditional for the first time. Henge Walker ("mana of the same color" — a max-over-colors metric, out of scope) and Red and Black Legacy now fail closed and stay honestly red instead of silently granting their counters.

Files changed

  • crates/engine/src/types/ability.rs
  • crates/engine/src/game/quantity.rs
  • crates/engine/src/game/layers.rs
  • crates/engine/src/game/ability_utils.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/parser/oracle_replacement.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/tests/integration/adamant_enters_with_leading_if_gate.rs
  • crates/engine/tests/integration/main.rs
  • docs/parser-misparse-backlog.md

Track

Developer

LLM

Model: claude-opus-5
Tier: Frontier
Thinking: high

Implementation method (required)

Method: /engine-implementer

CR references

  • CR 614.1c — "[This permanent] enters with . . ." effects are replacement effects (the authorizing rule)
  • CR 614.12 — how enters-the-battlefield replacements apply; co-authorizes the self-ETB scope
  • CR 207.2c — ability words have no rules meaning; the list contains "adamant" and "spell mastery"
  • CR 207.2d — flavour words are tailored per-ability and not listed in the CR; "Sensational Save" is one, and is absent from the curated peel list
  • CR 106.3 + CR 601.2h — mana production and paying the total cost (CastManaSpentMetric::OfColor)
  • CR 400.7d — "An ability of a permanent can reference information about the spell that became that permanent as it resolved, including … what mana was spent to pay those costs" (authorizes reading the entering object's payment record)

CR 603.4 is deliberately not cited in code. Its intervening-if rule is explicitly scoped to triggered abilities ("this rule only applies to an 'if' that immediately follows a trigger condition"); this gates a replacement effect. CR 616.1 was dropped after review — it had been cited for a multi-replacement fixture that does not exist.

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.

  • cargo fmt --all — clean

  • cargo clippy -p engine --all-targets -- -D warnings — exit 0, zero warnings

  • cargo test -p engine — exit 0; 21,943 passed, 0 failed (17828 lib + 4098 integration + 8 + 9; 15 ignored)

  • cargo test -p engine --test integration adamant_enters_with_leading_if_gate — 10 passed, 0 failed

  • ./scripts/gen-card-data.sh — regenerated; coverage delta measured against a pre-change baseline of all 35,516 cards: 0 true→false, 6 false→true, net +6

  • Revert-discrimination proof — neutralising extract_enters_with_leading_if_gate to always return NoLeadingIf makes the negative-polarity tests fail (expected 0, got 1 for Ardenvale below threshold; expected 0, got 2 for Dust Animus) while the positive reach-guards stay green. The fix's failure mode is demonstrated, not assumed.

Gate A

Gate A PASS head=8ca079a1f6c6e8a9cc6911a111129ece38b8b253 base=04e9c93c3c4f4f2d8cd7cd8884f370392759c15e

Anchored on

  • crates/engine/src/parser/oracle_replacement.rs:4366extract_enters_with_unless_suffix, the existing trailing-gate extractor for enters-with clauses; the new leading-gate peel at :4469 mirrors it one-for-one, including the fail-closed tri-state (EntersWithUnlessOutcome at :4346EntersWithLeadingIfOutcome at :4421).
  • crates/engine/src/parser/oracle_nom/condition.rs:6598parse_colors_of_mana_spent_threshold, the sibling mana-spent threshold combinator (parse_amount_threshold + subject anaphora → QuantityComparison{ManaSpentToCast{…}}); the new parse_color_mana_spent_threshold at :6639 differs only in the metric leaf.

Final review-impl

Final review-impl PASS head=8ca079a1f6c6e8a9cc6911a111129ece38b8b253

Claimed parse impact

Ardenvale Paladin, Embereth Paladin, Garenbrig Paladin, Locthwain Paladin, Vantress Paladin, Necromantic Summons (supported: false → true). Dust Animus gains a replacement condition while staying supported: true, gap_count: 0. Henge Walker and Red and Black Legacy keep their gap counts; their handler changes from Swallow:Condition_If to Effect:unimplemented (silently-wrong → honestly red). Eleven Adamant rider cards (Once and Future, Foreboding Fruit, Silverflame Ritual, Cauldron's Gift, Slaying Fire, Sundering Stroke, Rally for the Throne, Unexplained Vision, Searing Barrage, Turn into a Pumpkin, Outmuscle) change condition AST shape from AbilityCondition::ManaColorSpent to the equivalent QuantityCheck{ManaSpentToCast{OfColor}} with no behaviour change — pinned by the Slaying Fire runtime test in both polarities.

Scope Expansion

None. One leaf variant on an already-parameterized axis, one parser seam, its single runtime arm, and three compiler-forced match joins.

Validation Failures

None blocking. Two disclosures the maintainer should have, because both concern the reliability of self-reports rather than the code:

  1. An implementation agent's progress report was wrong twice. It stated "probe fully restored, no residue" and "all green". Neither was true: a TEMP-AB-PROBE early-return was still disabling the entire fix, and 8 of 10 tests were failing. Because card data had been regenerated before that probe was re-inserted, coverage still looked correct — so this would have shipped as a PR that silently did nothing. Caught by re-running the tests rather than trusting the report; the probe and two throwaway tmp_probe_* tests were removed, and every claim above was then re-verified first-hand.
  2. A reported accept-criterion violation was a false positive. That same agent diffed against the published R2 staging snapshot rather than the local 04e9c93 baseline — a 127-commit gap. Its flagged list included Aven Courier, which is upstream commit 7a35ef2 "Fix Aven Courier (#6686)". Re-measured against the correct baseline, all criteria pass.

Review process actually run: 2 plan-review rounds (/review-engine-plan) and 4 implementation-review rounds (/review-impl), each in an isolated agent context, never self-review. Severity decayed monotonically: BLOCKER+3 MAJOR → 3 MAJOR → 3 LOW → 2 LOW → 1 LOW → CLEAN. The plan rounds caught a green card (Dust Animus) sitting unexamined in the new code's blast radius and a set of fail-closed guards that were dead code; the implementation rounds caught only documentation-accuracy defects (a false CR-207.2c invariant, and "revert-fail" annotations on two tests that never invoke the parser). No finding after the probe removal changed program behaviour.

Scope note on the review rounds: the /review-impl agents read the code and verified claims against docs/MagicCompRules.txt and data/card-data.json, but did not execute tests (Tilt is down; they correctly declined to shell out to cargo and reported that as "could not find out", not as a pass). All execution evidence in the Verification section was produced by the caller directly.

CI Failures

None.

Summary by CodeRabbit

  • New Features

    • Added support for color-specific mana-spent-to-cast conditions.
    • Improved parsing of Adamant-style “if” gates and cast-mana requirements.
    • Added support for color-specific effects, including counters, damage, vigilance, and other conditional outcomes.
  • Bug Fixes

    • Prevented malformed or conflicting conditions from being treated as unconditional.
    • Improved handling of spell-specific mana colors and cast scopes.
  • Tests

    • Added comprehensive coverage for Adamant abilities, leading conditions, mana colors, and payment scenarios.

@keloide
keloide requested a review from matthewevans as a code owner July 29, 2026 07:10
@github-actions github-actions Bot added the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 497c4b50-7fb5-408f-8ba5-1eebd073a84a

📥 Commits

Reviewing files that changed from the base of the PR and between ff1aed6 and 9f77865.

📒 Files selected for processing (8)
  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/ability_utils.rs
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/game/layers.rs
  • crates/engine/src/game/quantity.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
🚧 Files skipped from review as they are similar to previous changes (8)
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/game/layers.rs
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/quantity.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/game/ability_utils.rs

📝 Walkthrough

Walkthrough

Changes

Adamant parser and runtime flow

Layer / File(s) Summary
Color-specific mana condition contract
crates/engine/src/types/ability.rs, crates/engine/src/parser/oracle_nom/condition.rs, crates/engine/src/parser/oracle_effect/conditions.rs, crates/engine/src/game/quantity.rs, crates/engine/src/game/coverage.rs
Adds CastManaSpentMetric::OfColor, parses colored mana thresholds, resolves per-color payment totals, and updates canonical-form tests and formatting.
Leading-if enters-with reconciliation
crates/engine/src/parser/oracle_replacement.rs
Extracts sentence-initial if gates, reconciles them with other gate positions, and rejects malformed or conflicting conditions.
Runtime metric and condition classification
crates/engine/src/game/ability_utils.rs, crates/engine/src/game/layers.rs, crates/engine/src/game/triggers.rs, crates/engine/src/game/ability_scan.rs, crates/engine/src/game/ability_rw.rs
Updates metric filtering and condition-axis handling. Regression tests compare generic and legacy Adamant condition shapes.
Integration scenarios
crates/engine/tests/integration/adamant_enters_with_leading_if_gate.rs, crates/engine/tests/integration/main.rs
Adds runtime coverage for Adamant replacements, color-specific damage, untapped-land gates, continuous vigilance grants, and structural gate attachment.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OracleText
  participant ConditionParser
  participant ReplacementParser
  participant GameRuntime
  participant IntegrationTests
  OracleText->>ConditionParser: parse colored mana threshold
  ConditionParser->>ReplacementParser: provide canonical quantity condition
  ReplacementParser->>GameRuntime: attach parsed enters-with gate
  GameRuntime->>IntegrationTests: apply mana-dependent replacement or rider
Loading

Possibly related PRs

  • phase-rs/phase#5858: Both changes extend Oracle condition parsing into quantity-comparison conditions.
  • phase-rs/phase#6352: Both changes update mana-spent-to-cast metric resolution and related trigger handling.

Suggested labels: quality

Suggested reviewers: matthewevans

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title names a real affected card, but the pull request primarily fixes parser support for leading conditional gates across multiple cards.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/engine/tests/integration/adamant_enters_with_leading_if_gate.rs`:
- Around line 132-368: Extend the integration coverage around the existing cast
helpers and gate assertions to include Adamant cases for blue, black, and green,
with both qualifying and non-qualifying color payments verifying the expected
payload behavior. Add production-pipeline rejection tests for the documented
Henge Walker/Red and Black Legacy unsupported gates, confirming their gated
payloads do not apply unconditionally. Prefer table-driven rows and reuse
existing scenario/cast helpers and assertion patterns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 11a29477-0ff0-471d-838e-f6351bf05480

📥 Commits

Reviewing files that changed from the base of the PR and between eeb8c49 and 8ca079a.

📒 Files selected for processing (14)
  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/ability_utils.rs
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/game/layers.rs
  • crates/engine/src/game/quantity.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/parser/oracle_replacement.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/tests/integration/adamant_enters_with_leading_if_gate.rs
  • crates/engine/tests/integration/main.rs
  • docs/parser-misparse-backlog.md

@matthewevans matthewevans self-assigned this Jul 29, 2026
@matthewevans

Copy link
Copy Markdown
Member

Maintainer update status for current head 8ca079a1f6c6e8a9cc6911a111129ece38b8b253: GitHub correctly reports a textual conflict with main. I resolved the sole conflict locally in docs/parser-misparse-backlog.md by retaining current-main generated counts and prepared merge commit 3425d945d9, but the maintainer credential cannot push to keloide/phase (Invalid username or token).

Next step: grant/update maintainer write access for this fork or apply that merge resolution from a credential with fork push access; then the PR must return to implementation review on its new head before approval.

@matthewevans matthewevans removed their assignment Jul 29, 2026
@matthewevans

Copy link
Copy Markdown
Member

Update to the prior hold: the initial fork push used an unavailable environment token. Repeating the same already-reviewed local resolution through the authenticated GitHub CLI credential succeeded. Current head is now 3425d945d9f422cda2d033b6bb017c4a66a85de8; the textual conflict is resolved. The PR is awaiting its new-head checks and implementation re-review before any approval.

@matthewevans

matthewevans commented Jul 29, 2026

Copy link
Copy Markdown
Member

Parse changes introduced by this PR · 22 card(s), 21 signature(s) (baseline: main 5674d7578d05)

🟢 Added (2 signatures)

  • 2 cards · ➕ replacement/Moved · added: Moved (scope=self, to zone=battlefield)
    • Affected (first 3): Apocalypse Hydra, Bringer of Green Zenith's Twilight
  • 2 cards · ➕ ability/replacement_structure · added: replacement_structure
    • Affected (first 3): Henge Walker, Red and Black Legac

🔴 Removed (1 signature)

  • 4 cards · ➖ replacement/Moved · removed: Moved (scope=self, to zone=battlefield)
    • Affected (first 3): Apocalypse Hydra, Bringer of Green Zenith's Twilight, Henge Walker (+1 more)

🟡 Modified fields (18 signatures)

  • 1 card · 🔄 ability/Bounce · changed field conditional: instead if (3+ Green spent)instead if (mana spent to cast (SelfObject, Green mana) ≥ 3)
    • Affected (first 3): Once and Future
  • 1 card · 🔄 ability/DamageAll · changed field conditional: instead if (7+ Red spent)instead if (mana spent to cast (SelfObject, Red mana) ≥ 7)
    • Affected (first 3): Sundering Stroke
  • 1 card · 🔄 ability/DealDamage · changed field conditional: 3+ Red spentmana spent to cast (SelfObject, Red mana) ≥ 3
    • Affected (first 3): Searing Barrage
  • 1 card · 🔄 ability/DealDamage · changed field conditional: instead if (3+ Red spent)instead if (mana spent to cast (SelfObject, Red mana) ≥ 3)
    • Affected (first 3): Slaying Fire
  • 1 card · 🔄 ability/GainLife · changed field conditional: 3+ White spentmana spent to cast (SelfObject, White mana) ≥ 3
    • Affected (first 3): Rally for the Throne
  • 1 card · 🔄 ability/Mill · changed field conditional: 3+ Black spentmana spent to cast (SelfObject, Black mana) ≥ 3
    • Affected (first 3): Cauldron's Gift
  • 1 card · 🔄 replacement/Moved · changed field condition: OnlyIfQuantity { lhs: Ref { qty: ManaSpentToCast { scope: SelfObject, metric: OfColor { color: Black } } }, comparator:…
    • Affected (first 3): Locthwain Paladin
  • 1 card · 🔄 replacement/Moved · changed field condition: OnlyIfQuantity { lhs: Ref { qty: ManaSpentToCast { scope: SelfObject, metric: OfColor { color: Blue } } }, comparator: …
    • Affected (first 3): Vantress Paladin
  • 1 card · 🔄 replacement/Moved · changed field condition: OnlyIfQuantity { lhs: Ref { qty: ManaSpentToCast { scope: SelfObject, metric: OfColor { color: Green } } }, comparator:…
    • Affected (first 3): Garenbrig Paladin
  • 1 card · 🔄 replacement/Moved · changed field condition: OnlyIfQuantity { lhs: Ref { qty: ManaSpentToCast { scope: SelfObject, metric: OfColor { color: Red } } }, comparator: G…
    • Affected (first 3): Embereth Paladin
  • 1 card · 🔄 replacement/Moved · changed field condition: OnlyIfQuantity { lhs: Ref { qty: ManaSpentToCast { scope: SelfObject, metric: OfColor { color: White } } }, comparator:…
    • Affected (first 3): Ardenvale Paladin
  • 1 card · 🔄 replacement/Moved · changed field condition: OnlyIfQuantity { lhs: Ref { qty: ObjectCount { filter: Typed(TypedFilter { type_filters: [Land], controller: Some(You),…
    • Affected (first 3): Dust Animus
  • 1 card · 🔄 replacement/Moved · changed field condition: OnlyIfQuantity { lhs: Ref { qty: ZoneCardCount { zone: Graveyard, card_types: [Instant, Sorcery], filter: None, scope: …
    • Affected (first 3): Necromantic Summons
  • 1 card · 🔄 ability/Scry · changed field conditional: 3+ Blue spentmana spent to cast (SelfObject, Blue mana) ≥ 3
    • Affected (first 3): Unexplained Vision
  • 1 card · 🔄 ability/Token · changed field conditional: 3+ Black spentmana spent to cast (SelfObject, Black mana) ≥ 3
    • Affected (first 3): Foreboding Fruit
  • 1 card · 🔄 ability/Token · changed field conditional: 3+ Blue spentmana spent to cast (SelfObject, Blue mana) ≥ 3
    • Affected (first 3): Turn into a Pumpkin
  • 1 card · 🔄 ability/grant Vigilance · changed field conditional: 3+ White spent
    • Affected (first 3): Silverflame Ritual
  • 1 card · 🔄 ability/the · changed field conditional: 3+ Green spentmana spent to cast (SelfObject, Green mana) ≥ 3
    • Affected (first 3): Outmuscle

@matthewevans

Copy link
Copy Markdown
Member

Current-head review hold for 3425d945d9f422cda2d033b6bb017c4a66a85de8: required checks are green, but the engine/parser parse-diff artifact (<!-- coverage-parse-diff -->) is absent for this head. The prior Gate A/final review references 8ca079a1, so it cannot substitute for current-head parse-impact evidence.

Next step: regenerate or reconcile the current-head parse-diff artifact, then this PR will receive the full implementation review before any approval or queue action.

@matthewevans matthewevans added the enhancement New feature or request label Jul 29, 2026
@matthewevans matthewevans self-assigned this Jul 29, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changes requested — the implementation is at the right generic seam, but its runtime evidence does not cover the full newly supported color axis.

🟡 Test coverage gap

[MED] CastManaSpentMetric::OfColor { color } is parameterized for all five colors, while the production cast-pipeline integration test exercises only white and red. Evidence: crates/engine/src/types/ability.rs:5428 introduces the generic ManaColor parameter; crates/engine/tests/integration/adamant_enters_with_leading_if_gate.rs:132-245 contains white/red rows only; the current-head CodeRabbit review identifies the same gap. Why it matters: blue, black, or green lowering/resolution can regress without a runtime failure, despite this PR changing their parser output in the parse-diff artifact. Suggested fix: add table-driven qualifying and below-threshold pipeline rows for blue, black, and green, plus cast-pipeline witnesses that the documented unsupported Henge Walker and Red and Black Legac gates do not publish their payloads unconditionally.

Recommendation: request changes for the complete parameter-range runtime matrix; retain the existing shared OfColor design.

@matthewevans matthewevans removed their assignment Jul 29, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/engine/tests/integration/adamant_enters_with_leading_if_gate.rs`:
- Around line 369-466: Extend the static-grant coverage around
cast_silverflame_ritual and the R9 tests beyond white by adding a
different-color positive/negative gate case, preserving the counter assertion
and verifying vigilance remains gated. Add a production-pipeline regression
fixture with a malformed or unparseable StaticDefinition.condition and assert
the static grant fails closed rather than applying unconditionally.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 401c939a-88b0-4cb3-bf03-47b87a3209a9

📥 Commits

Reviewing files that changed from the base of the PR and between 3425d94 and ff1aed6.

📒 Files selected for processing (1)
  • crates/engine/tests/integration/adamant_enters_with_leading_if_gate.rs

@matthewevans matthewevans self-assigned this Jul 29, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changes requested — the new generic per-color runtime path lacks discriminating coverage for most of its accepted class.

🔴 Blocker

[MED] CastManaSpentMetric::OfColor is parameterized for every ManaColor, but the production integration test exercises only white and red. Evidence: crates/engine/src/types/ability.rs:5442-5446 exposes the generic OfColor { color } metric, and crates/engine/src/parser/oracle_nom/condition.rs:6639-6654 accepts every color through parse_color; the current-head parse-diff adds Apocalypse Hydra and Bringer of Green Zenith's Twilight, while crates/engine/tests/integration/adamant_enters_with_leading_if_gate.rs:132-245 covers only white/red cast-pipeline rows. A blue/black/green mapping or threshold regression can therefore ship despite the shared parser/metric being claimed as a general building block. Suggested fix: add runtime cast-pipeline rows for the remaining color families, including a below-threshold/nonqualifying payment and the X >= 5 Apocalypse Hydra/Bringer path; assert the gate payload is applied only when the selected color qualifies.

✅ Clean

The generic parser seam is appropriate: it delegates the color axis to the existing parse_color combinator rather than enumerating Oracle strings.

Recommendation: add the missing production-pipeline class coverage, then request a new review on the updated head.

@matthewevans matthewevans removed their assignment Jul 29, 2026
@matthewevans matthewevans self-assigned this Aug 1, 2026
@matthewevans matthewevans added bug Bug fix and removed enhancement New feature or request labels Aug 1, 2026
claude and others added 4 commits August 1, 2026 10:27
…s-with replacements

The Adamant cycle parsed its "enters with a +1/+1 counter" clause as a
CR 614.1c replacement but dropped the sentence-initial "If <condition>, "
gate, leaving ReplacementDefinition.condition = None. The engine therefore
gave every copy of these creatures the counter regardless of what mana was
actually spent — silently-wrong behaviour that nothing flagged at runtime.
Coverage reported it as Swallow:Condition_If on six cards.

Root cause: oracle_replacement.rs peeled a TRAILING " unless <cond>" gate
(extract_enters_with_unless_suffix) and a trailing " if <cond>" gate
(extract_enters_with_only_if_suffix), but nothing peeled a LEADING one, and
the payload scan skips everything before "enters with".

Fixed for the class, not the cards:

* extract_enters_with_leading_if_gate mirrors the existing trailing
  extractor one-for-one, including its fail-closed tri-state. Condition
  recognition delegates to parse_inner_condition (the single authority), so
  the ENTIRE static-condition grammar is now reachable from the
  sentence-initial position, where previously zero shapes were. An
  unrecognised gate returns Unparsed and the clause falls through to
  Effect::unimplemented rather than committing a replacement with the gate
  erased.
* CR 207.2c ability-word labels are peeled via parse_known_ability_word_name,
  which enumerates exactly the CR 207.2c list. CR 207.2d flavour words are
  deliberately NOT peeled: the loose 4-word heuristic would peel "Sensational
  Save" and regress Scarlet Spider, Ben Reilly to Unparsed.
* CastManaSpentMetric::OfColor parameterises the existing metric axis
  (Total | DistinctColors | FromSource) rather than adding a
  ReplacementCondition sibling, so the parsed condition flows through the
  untouched replacement_condition_from_static into OnlyIfQuantity. Zero new
  ReplacementCondition variants, zero new condition-evaluation arms.
* parse_color_mana_spent_threshold reuses parse_amount_threshold, so the
  comparator axis (at least N / N or more / N or fewer) is carried rather
  than hardcoded to GE.

Coverage: six cards flip to supported (Ardenvale, Embereth, Garenbrig,
Locthwain and Vantress Paladin, plus Necromantic Summons), zero cards
regress. Dust Animus keeps supported=true and gains its gate, so it is
correctly conditional for the first time. Henge Walker ("mana of the same
color" — a max-over-colors metric) and Red and Black Legacy now fail closed
and stay honestly red instead of silently granting their counters.

Measured against a pre-change baseline of all 35516 cards: 0 true->false,
6 false->true, net +6.

CR 614.1c, CR 614.12, CR 207.2c, CR 207.2d, CR 106.3, CR 601.2h, CR 400.7d.
CR 603.4 is deliberately not cited in code: its intervening-if rule is
scoped to triggered abilities, and this gates a replacement effect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Silverflame Ritual's Adamant rider grants a continuous static effect
("creatures you control gain vigilance") rather than an ability-level
effect, so its gate lands on `StaticDefinition.condition` (CR 613 layer
6) instead of the ability's own `condition`. The CI parse-diff bot reads
the ability level only, so that layer move renders as
`conditional: 3+ White spent -> ∅`, which reads like a dropped gate --
the exact bug class this file guards.

It is not a drop: the condition is present and enforced. Verified at
runtime in both polarities, because nothing pinned this subclass. The
existing R8 rider guard (Slaying Fire) cannot catch it -- that payload
is ability-level -- so a future refactor really could drop the static's
condition with every other test here staying green.

Both new tests carry a positive reach-guard asserting the card's UNGATED
first line resolved (the +1/+1 counter), so the "no vigilance" negative
cannot pass vacuously on a spell that never resolved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@matthewevans
matthewevans force-pushed the claude/phase-developer-track-card-o1e67k branch from 7c931d6 to 9f77865 Compare August 1, 2026 17:27

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maintainer completion verified on 9f77865: current CI is green, parser artifact matches the intended colored-mana Adamant conditions, and all review threads are resolved.

@matthewevans
matthewevans added this pull request to the merge queue Aug 1, 2026
Merged via the queue into phase-rs:main with commit 616ee41 Aug 1, 2026
15 checks passed
@keloide
keloide deleted the claude/phase-developer-track-card-o1e67k branch August 10, 2026 10:39
lgray added a commit to lgray/phase that referenced this pull request Aug 28, 2026
Found while sweeping arm citations for the rule added in the previous commit:
these two line-number pins are the rot that rule predicts, already realized.

`(`:2572`)` now lands on a `FilterReadContext::LiveBoardCensus` argument inside a
`scan_target_filter` call, and `(`:2452`)` on an `if let Some(x) = filter {`.
Neither is an arm. The `Axes::NONE` arm the sentence means is
`AbilityCondition::ManaColorSpent { color: _, minimum: _ }` in
`scan_ability_condition`, which is where the citation now points.

The second pin's parenthetical also carried the reachability prose; only the
number is removed, since "reached via `QuantityCheck` -> `scan_quantity_expr`"
is the part that tells a reader anything.

Both pins predate this branch — `git log -L` puts them in 616ee41 (phase-rs#6780) —
so this is drift repair in a file the branch already touches, not a claim this
branch introduced.

Comment-only: no line outside `///`, `//!` or `//` is added or removed.

Assisted-by: ClaudeCode:claude-opus-4.8
lgray added a commit to lgray/phase that referenced this pull request Aug 28, 2026
Found while sweeping arm citations for the rule added in the previous commit:
these two line-number pins are the rot that rule predicts, already realized.

`(`:2572`)` now lands on a `FilterReadContext::LiveBoardCensus` argument inside a
`scan_target_filter` call, and `(`:2452`)` on an `if let Some(x) = filter {`.
Neither is an arm. The `Axes::NONE` arm the sentence means is
`AbilityCondition::ManaColorSpent { color: _, minimum: _ }` in
`scan_ability_condition`, which is where the citation now points.

The second pin's parenthetical also carried the reachability prose; only the
number is removed, since "reached via `QuantityCheck` -> `scan_quantity_expr`"
is the part that tells a reader anything.

Both pins predate this branch — `git log -L` puts them in 616ee41 (phase-rs#6780) —
so this is drift repair in a file the branch already touches, not a claim this
branch introduced.

Comment-only: no line outside `///`, `//!` or `//` is added or removed.

Assisted-by: ClaudeCode:claude-opus-4.8
lgray added a commit to lgray/phase that referenced this pull request Aug 28, 2026
Found while sweeping arm citations for the rule added in the previous commit:
these two line-number pins are the rot that rule predicts, already realized.

`(`:2572`)` now lands on a `FilterReadContext::LiveBoardCensus` argument inside a
`scan_target_filter` call, and `(`:2452`)` on an `if let Some(x) = filter {`.
Neither is an arm. The `Axes::NONE` arm the sentence means is
`AbilityCondition::ManaColorSpent { color: _, minimum: _ }` in
`scan_ability_condition`, which is where the citation now points.

The second pin's parenthetical also carried the reachability prose; only the
number is removed, since "reached via `QuantityCheck` -> `scan_quantity_expr`"
is the part that tells a reader anything.

Both pins predate this branch — `git log -L` puts them in 616ee41 (phase-rs#6780) —
so this is drift repair in a file the branch already touches, not a claim this
branch introduced.

Comment-only: no line outside `///`, `//!` or `//` is added or removed.

Assisted-by: ClaudeCode:claude-opus-4.8
lgray added a commit to lgray/phase that referenced this pull request Aug 28, 2026
Found while sweeping arm citations for the rule added in the previous commit:
these two line-number pins are the rot that rule predicts, already realized.

`(`:2572`)` now lands on a `FilterReadContext::LiveBoardCensus` argument inside a
`scan_target_filter` call, and `(`:2452`)` on an `if let Some(x) = filter {`.
Neither is an arm. The `Axes::NONE` arm the sentence means is
`AbilityCondition::ManaColorSpent { color: _, minimum: _ }` in
`scan_ability_condition`, which is where the citation now points.

The second pin's parenthetical also carried the reachability prose; only the
number is removed, since "reached via `QuantityCheck` -> `scan_quantity_expr`"
is the part that tells a reader anything.

Both pins predate this branch — `git log -L` puts them in 616ee41 (phase-rs#6780) —
so this is drift repair in a file the branch already touches, not a claim this
branch introduced.

Comment-only: no line outside `///`, `//!` or `//` is added or removed.

Assisted-by: ClaudeCode:claude-opus-4.8
lgray added a commit to lgray/phase that referenced this pull request Aug 29, 2026
Found while sweeping arm citations for the rule added in the previous commit:
these two line-number pins are the rot that rule predicts, already realized.

`(`:2572`)` now lands on a `FilterReadContext::LiveBoardCensus` argument inside a
`scan_target_filter` call, and `(`:2452`)` on an `if let Some(x) = filter {`.
Neither is an arm. The `Axes::NONE` arm the sentence means is
`AbilityCondition::ManaColorSpent { color: _, minimum: _ }` in
`scan_ability_condition`, which is where the citation now points.

The second pin's parenthetical also carried the reachability prose; only the
number is removed, since "reached via `QuantityCheck` -> `scan_quantity_expr`"
is the part that tells a reader anything.

Both pins predate this branch — `git log -L` puts them in 616ee41 (phase-rs#6780) —
so this is drift repair in a file the branch already touches, not a claim this
branch introduced.

Comment-only: no line outside `///`, `//!` or `//` is added or removed.

Assisted-by: ClaudeCode:claude-opus-4.8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants