Skip to content

[agent-loop: claude-opus-5-5] Minimum count on graveyard exile-X costs for Dragons of Tarkir - #4695

Merged
wingedsheep merged 3 commits into
mainfrom
loop-dtk-exile-x-min
Nov 15, 2026
Merged

wingedsheep merged 3 commits into
mainfrom
loop-dtk-exile-x-min

Conversation

@wingedsheep

Copy link
Copy Markdown
Owner

Closes DTK gap G8 — minimum count on the variable graveyard exile-X cost ("Exile one or more creature cards from your graveyard"). Unlocks Corpseweft (G8's only card), shipped here.

What it does

No new SDK type. The cost is the existing Costs.ExileXFromGraveyard(filter) under the activated ability's existing minimumXValue = 1 (until now it was honoured only for X paid in {X} mana). With no {X} mana, where X is the size of the graveyard selection:

  • Selection pause (ActivationChoicePauses): minSelections is floored at minimumXValue. It still prompts when the candidates exactly meet the floor, so X is bound by the pick and never paid silently as 0. Prompt: "Select 1 or more cards to exile…".
  • Legality: ActivatedAbilityEnumerator marks the ability unpayable (greyed out; omitted when the exile is the whole cost) while fewer matching cards are in the graveyard. ActivationValidator.checkExileXSelection rejects it engine-direct too.
  • Pre-filled piles (a review finding, for every exile-X cost): a client ActivateAbility whose exiledCards don't match its xValue is rejected. Before, exiledCards with no xValue paid X = 0 and exiled nothing.
  • CostEnumerationUtils.exileXCandidateCount is shared by the enumerator's floor and the maxAffordableX cap.
  • Docs: the SDK reference (Costs.ExileXFromGraveyard, minimumXValue) and the minimumXValue KDoc.

Cards

  • Corpseweft (DTK Add void cards from EOE #92, canonical — DTK is its only printing): {1}{B}, ExileXFromGraveyard(Creature), minimumXValue = 1, a tapped black Zombie Horror token sized xValue * 2. CorpseweftScenarioTest covers the 2-card case (4/4, tapped, creature cards only), an empty graveyard (greyed out and rejected), one candidate (still prompts, 2/2), and a pre-filled pile with no X (rejected).

Tests

  • ExileXMinimumCountTest (engine) uses a floor of 2 to show the floor is general. It covers: not enumerated/rejected below the floor; selection floored with X = the count; prompts at exactly the floor; a pile below the floor rejected; a pre-filled pile with a mismatched X rejected, with or without a floor; floor 0 keeps the old any-number behaviour (Winter, Cursed Rider).
  • ExileXGraveyardCostReadingAiTest (AI).

AI

  • Corpseweft — AI: reads 1/1 · pick 1.6 (DTK table) · intent Makes tokens, activated (+3.0 in play) · synergy feeds tokens/creatures enter/death, rewards graveyard
  • Gap closed in this PR: GraveyardSynergy didn't read AbilityCost.ExileXFromGraveyard as a graveyard payoff (Corpseweft, Winter, Cursed Rider, Necropolis Fiend). It does now, pinned by ExileXGraveyardCostReadingAiTest, with an ai/changelog.d/loop-dtk-exile-x-min.md entry.

Gate

  • just loop-gate --rebless passed on the first head. Only the DTK golden moved (Corpseweft added).
  • After the review fixes, the re-run under load average ~30 ended with timeouts (AbandonReasonScenarioTest, CastFromCollectionAdditionalManaCostTest, SealedDeckGeneratorTest) and 3 AI assertion flakes (ChooseActionReadingAiTest, RepeatableProducerAiTest, ArenaHarnessTest). Each of these except SealedDeckGeneratorTest passed when re-run alone with just test-class; SealedDeckGeneratorTest was a plain timeout and was not re-run. All 10 of this PR's new cases passed. CI runs the full suite.
  • just assay-differential: unchanged, 8788 compared / 111 divergent. Assay declines Corpseweft's line, so a band for "exile one or more … from your graveyard" is new vocabulary for assay-report --rank tail.
  • just check-card-printing "Corpseweft": canonical in DTK.

Manual UI check

Headless Chromium against just ui-dev:

Selection prompt, Confirm disabled at 0/1–2
Two Bears selected, Confirm enabled
No creature card in graveyard: ability greyed out

(Screenshots are hosted on the throwaway branch loop-dtk-exile-x-min-screenshots.) The flow put the ability on the stack with X=2.

Review (split mode, fresh-context: triage → engine + cards reviewers → weigh)

  • Fixed (Important): a pre-filled pile with no xValue paid X = 0. The fix is the xValue == pile size check, with tests that now reach it (the earlier "rejected" tests were caught by the older X-below-minimum check).
  • Fixed (Minor): doc said "hidden" where a composite cost is greyed out; the new helper was placed between hasPlayerChosenNonManaX and its KDoc, and the stray "Calculate max affordable X" KDoc is now on calculateMaxAffordableX.
  • Declined: a de-duplicated candidate counter (the validator uses CostHandler.findMatchingCardsUnified, the enumerator the new helper). Both use the same projected state, context and filter, and they agree today.

Nothing was play-tested beyond the manual UI check above.

🤖 Generated with Claude Code

wingedsheep and others added 3 commits November 15, 2026 15:01
…eft (DTK)

"Exile one or more creature cards from your graveyard" is ExileXFromGraveyard under the
ability's existing minimumXValue = 1. The no-{X} path now floors the selection at the
minimum, keeps prompting when candidates exactly meet it (X is bound by the pick), greys
the ability out / rejects it without enough matching cards, and rejects a smaller submitted
pile. AI: exile-X graveyard costs read as graveyard payoffs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review: an ActivateAbility carrying exiledCards but no xValue paid X = 0 and exiled
nothing (Corpseweft made a 0/0 for {1}{B}). Tests now reach that branch; doc wording
and stray KDoc fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wingedsheep

Copy link
Copy Markdown
Owner Author

loop-reviewed efb5947

Reviewed in split mode at 87d81f7 (findings fixed there). Since then, only the ai/changelog.d entry's #TBD was replaced with #4695.

@wingedsheep
wingedsheep merged commit 10ab786 into main Nov 15, 2026
15 checks passed
@wingedsheep
wingedsheep deleted the loop-dtk-exile-x-min branch November 15, 2026 15:15
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