Skip to content

Fix Endless Ranks of HYDRA - #6991

Open
JacobWoodson wants to merge 1 commit into
phase-rs:mainfrom
JacobWoodson:card/endless-ranks-of-hydra
Open

Fix Endless Ranks of HYDRA#6991
JacobWoodson wants to merge 1 commit into
phase-rs:mainfrom
JacobWoodson:card/endless-ranks-of-hydra

Conversation

@JacobWoodson

@JacobWoodson JacobWoodson commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a parse-fidelity defect on Endless Ranks of HYDRA.

Issue: audit-flagged: SilentDrop — part of the Oracle text is silently dropped from the parsed AST; identify the missing clause and restore it.

Files changed

  • crates/engine/src/game/coverage.rs

CR references

  • CR 113.6m

Track

Developer

LLM

Model: claude-opus-4-8
Thinking: high

Tier: Frontier

Verification

  • cargo fmt --all — clean
  • ./scripts/check-parser-combinators.sh (Gate A) — PASS exit 0 (Gate G + full combinator scan). Family-D skipped: python3/python are permission-denied WindowsApps stubs (anticipated env limitation). Achieved clean skip by running with WindowsApps dir filtered from PATH so command -v python3 is false and the script bypasses the D0 self-test and completes to Gate A PASS.
  • cargo clippy-strict — clean (exit 0)
  • cargo test -p phase-engine — clean after in-loop fix. One failure: Windows-only path-separator bug in pinned CR 603.5 census test (crates/engine/src/game/engine.rs:15185) where Path::display() emits backslash vs pinned forward-slash coordinates; census counts passed. Fixed with .replace('','/') when building rel (no-op on Unix, pinned line numbers/counts untouched). Re-run: integration 4480 passed/0 failed, lib passed, exit 0.
  • cargo export-cards data --stats --sidecar-dir client/public && cp client/public/card-data.json data/card-data.json — clean (recipe corrected). Literal recipe streams main JSON to stdout (binary writes file only with --output) so client/public/card-data.json stayed stale; added --output client/public/card-data.json to write it fresh from this branch's engine, then copied to data/card-data.json (fresh 04:22, 34868 cards, 92.1% implemented).
  • cargo coverage — clean (exit 0); Endless Ranks of HYDRA supported:true gap_count:0 (Token + CreateDelayedTrigger both supported)
  • cargo semantic-audit — clean (exit 0); Endless Ranks of HYDRA has 0 findings (absent from fresh data/semantic-audit.json)

Scope Expansion

The SilentDrop is a verified FALSE POSITIVE (both Oracle lines are fully in the AST); per the approved plan the class-level cause is in the semantic-audit heuristic, not the parser, so the fix adds one structural ChangeZone(GY->Hand, SelfRef) arm to line_matches_effect_type in game/coverage.rs (QA tooling only, zero parser/card/runtime change).

Validation Failures

None.

CI Failures

None.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed cross-platform file path consistency to ensure uniform behavior across different operating systems.
  • Tests

    • Added comprehensive test coverage for complex card effect scenarios, including direct and recursive trigger combinations.

@github-actions github-actions Bot added the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The semantic audit now recognizes descriptionless delayed-trigger graveyard-to-hand effects and adds regression coverage. The engine also normalizes relative paths to forward slashes after string conversion.

Changes

Delayed graveyard recursion audit

Layer / File(s) Summary
Delayed graveyard recursion coverage
crates/engine/src/game/coverage.rs
The semantic audit recognizes qualifying delayed ChangeZone effects. Tests cover direct and optional-pay returns to hand, while rejecting battlefield returns, targeted returns, and unrelated text.

Relative path normalization

Layer / File(s) Summary
Relative path normalization
crates/engine/src/game/engine.rs
Relative paths replace backslashes with forward slashes after string conversion.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: bug

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 clearly identifies the card-specific semantic-audit fix described in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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/src/game/coverage.rs`:
- Around line 9420-9443: Update the graveyard-to-hand predicate in
ability_tree_any so it only credits the ChangeZone leaf when effective_lower
contains the normalized self-reference marker (such as “return ~”), while
retaining the existing return, graveyard, and hand checks. Add a two-line
regression case covering both a SelfRef recursion effect and a targeted
creature-card return to ensure credit does not cross lines.
🪄 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: b6653c3c-3ed6-480a-b18e-46e7c09fc850

📥 Commits

Reviewing files that changed from the base of the PR and between 045263a and 6b3b0a7.

📒 Files selected for processing (2)
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/game/engine.rs

Comment on lines +9420 to +9443
// CR 113.6m: "The same is true if the effect of that ability
// creates a delayed triggered ability whose effect moves the
// object out of a particular zone." Instants/sorceries in the
// graveyard-recursion class ("Whenever <event>, [you may pay
// <cost>. If you do,] return this card from your graveyard to
// your hand." — Spit Flame, Reach of Branches, Asgardian
// Inspiration, Endless Ranks of HYDRA) lower to a
// descriptionless CreateDelayedTrigger whose nested effect chain
// returns SelfRef from the graveyard to hand. `ability_tree_any`
// already recurses into the delayed trigger's `effect` and its
// `sub_ability`, so crediting this ChangeZone leaf covers the
// whole class and clears the false SilentDrop — the AST fully
// represents the line; only the per-line description-association
// heuristic failed (the delayed trigger carries no description).
Effect::ChangeZone {
origin: Some(Zone::Graveyard),
destination: Zone::Hand,
target: TargetFilter::SelfRef,
..
} => {
effective_lower.contains("return")
&& effective_lower.contains("graveyard")
&& effective_lower.contains("hand")
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent cross-line credit for a different graveyard-return effect.

This predicate does not require a self-reference in the Oracle line. ability_tree_any evaluates it against every ability tree on the card.

A descriptionless line such as “return target creature card from your graveyard to your hand” can therefore be credited by a separate SelfRef recursion leaf. The audit then misses the targeted line's parse gap.

Require a normalized self-reference in the line, such as return ~, and add a two-line regression case with both effect shapes.

Proposed guard
                 } => {
                     effective_lower.contains("return")
                         && effective_lower.contains("graveyard")
                         && effective_lower.contains("hand")
+                        && norm.contains("return ~")
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// CR 113.6m: "The same is true if the effect of that ability
// creates a delayed triggered ability whose effect moves the
// object out of a particular zone." Instants/sorceries in the
// graveyard-recursion class ("Whenever <event>, [you may pay
// <cost>. If you do,] return this card from your graveyard to
// your hand." — Spit Flame, Reach of Branches, Asgardian
// Inspiration, Endless Ranks of HYDRA) lower to a
// descriptionless CreateDelayedTrigger whose nested effect chain
// returns SelfRef from the graveyard to hand. `ability_tree_any`
// already recurses into the delayed trigger's `effect` and its
// `sub_ability`, so crediting this ChangeZone leaf covers the
// whole class and clears the false SilentDrop — the AST fully
// represents the line; only the per-line description-association
// heuristic failed (the delayed trigger carries no description).
Effect::ChangeZone {
origin: Some(Zone::Graveyard),
destination: Zone::Hand,
target: TargetFilter::SelfRef,
..
} => {
effective_lower.contains("return")
&& effective_lower.contains("graveyard")
&& effective_lower.contains("hand")
}
// CR 113.6m: "The same is true if the effect of that ability
// creates a delayed triggered ability whose effect moves the
// object out of a particular zone." Instants/sorceries in the
// graveyard-recursion class ("Whenever <event>, [you may pay
// <cost>. If you do,] return this card from your graveyard to
// your hand." — Spit Flame, Reach of Branches, Asgardian
// Inspiration, Endless Ranks of HYDRA) lower to a
// descriptionless CreateDelayedTrigger whose nested effect chain
// returns SelfRef from the graveyard to hand. `ability_tree_any`
// already recurses into the delayed trigger's `effect` and its
// `sub_ability`, so crediting this ChangeZone leaf covers the
// whole class and clears the false SilentDrop — the AST fully
// represents the line; only the per-line description-association
// heuristic failed (the delayed trigger carries no description).
Effect::ChangeZone {
origin: Some(Zone::Graveyard),
destination: Zone::Hand,
target: TargetFilter::SelfRef,
..
} => {
effective_lower.contains("return")
&& effective_lower.contains("graveyard")
&& effective_lower.contains("hand")
&& norm.contains("return ~")
}
🤖 Prompt for 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.

In `@crates/engine/src/game/coverage.rs` around lines 9420 - 9443, Update the
graveyard-to-hand predicate in ability_tree_any so it only credits the
ChangeZone leaf when effective_lower contains the normalized self-reference
marker (such as “return ~”), while retaining the existing return, graveyard, and
hand checks. Add a two-line regression case covering both a SelfRef recursion
effect and a targeted creature-card return to ensure credit does not cross
lines.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Generated for head 6b3b0a76e02be4b0135665fe1becb03996dd1e7e.

Parse changes introduced by this PR

✓ No card-parse changes detected.

@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 — this current head can still mark an unrelated return line as covered.

🔴 Blocker

crates/engine/src/game/coverage.rs:9434-9456 searches a descriptionless SelfRef ChangeZone leaf across every ability tree, but the predicate is keyed only to return / graveyard / hand in the candidate line. A card with a self-recursion line plus an unrelated return target creature card from your graveyard to your hand line can therefore cross-credit the unrelated line and recreate the SilentDrop false-green. CodeRabbit identified the core cross-credit issue; the current-head code confirms it.

Please make the self-reference check line-local, accepting the legitimate return this card form as well as normalized card-name/~ references, and add a two-line negative regression containing both the self-recursion and targeted-return lines. Do not restrict this to norm.contains("return ~"): normalize_card_name_refs intentionally does not normalize this card (crates/engine/src/game/coverage.rs:8684-8687; crates/engine/src/parser/oracle_util.rs:888-895).

✅ Clean

The descriptionless delayed-trigger traversal itself is the appropriate mechanism to preserve; the required guard is its line-to-leaf association.

Recommendation: request changes — add the line-local self-reference guard and the discriminating two-line regression, then request re-review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

2 participants