Skip to content

Let the AI use Thespian's Stage - #11393

Open
liamiak wants to merge 2 commits into
Card-Forge:masterfrom
liamiak:let-ai-use-thespians-stage
Open

Let the AI use Thespian's Stage#11393
liamiak wants to merge 2 commits into
Card-Forge:masterfrom
liamiak:let-ai-use-thespians-stage

Conversation

@liamiak

@liamiak liamiak commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Logic is 30 lines; the rest is a test in a second commit you can drop.

Thespian's Stage ({2}, {T}: becomes a copy of target land, keeping this ability) is
AI:RemoveDeck:All, and CloneAi says why — its default target branch is

// Those can just use
// AI:RemoveDeck:All until this can do a reasonably good job of picking
// a good target
return false;

Picking a land

GameStateEvaluator.evaluateLand is what AiController.chooseBestLandToPlay already uses to
rank lands, so CloneBestLand ranks with it too. Scores on a board with four Swamps:

Land ours opponent's
Swamp 106 106
Thespian's Stage 156 156
Cabal Coffers 306 6
Ancient Tomb 209 209
Mishra's Factory 181 181
Wasteland 116 116
Gaea's Cradle 6 6

Two things follow from that table. Only lands we control are considered — a land is scored
under whoever controls it now, and Cabal Coffers swings between 6 and 306 depending on whose
Swamps get counted, so reading an opponent's board misjudges in both directions while the copy
arrives under ours either way. And our own legendary lands are skipped, since copying one
just means sacrificing one of the two.

The bar is the Stage's own score, which is deliberately conservative: the copy keeps the clone
ability, so a target really only has to beat a plain land (106) rather than the Stage's 156. It
passes on marginal upgrades like Wasteland (116) instead of risking a bad one.

getBestLandAI would have been the obvious helper, but it returns a random non-basic, which
is fine for "which land to fetch or destroy" and not for this.

When

checkPhaseRestrictions currently allows an activated clone only during the opponent's
declare-attackers step with attackers, which suits a temporary combat clone but not this. The
upgrade is permanent, so there is nothing to gain by taking it early, and the cost taps the
Stage — activating at the end of the turn before ours keeps the mana up as a possible threat
for as long as possible and still unlocks the Stage in our own untap step. That is exactly the
base class's AtOppEOT rule, so this delegates to it rather than restating the condition.

Existing cards are untouched: the override only diverges on CloneBestLand, and otherwise
calls super, which dispatches to the same method as before.

As an aside, that gate looks like it was meant for temporary clones — the block above it and
the one below it are both conditioned on Duration, and none of the 24 cards with an
activated or cast Clone has one, so the middle block is the only one that ever applies to
them. Happy to look at that separately if it's worth changing.

Measured

End of the opponent's turn, four Swamps and enough mana:

Board Before After
Our Ancient Tomb CantPlayAi copies Ancient Tomb
Our Cabal Coffers CantPlayAi copies Cabal Coffers
Our Mishra's Factory CantPlayAi copies Mishra's Factory
Our Wasteland CantPlayAi declines (116 < 156)
Our Gaea's Cradle CantPlayAi declines (legendary)
Opponent's Ancient Tomb CantPlayAi declines (not ours)
Basics only CantPlayAi declines
Any of the above, at any other point in the turn cycle MissingPhaseRestrictions unchanged

Testing

  • mvn -pl forge-gui-desktop -am test: 289 tests, 0 failures.
  • The test was verified to fail without the change — the AI never activates the ability, so the
    Stage keeps its own name — and one of the three cases fails if the timing is loosened back to
    our own main phase.
  • No other card reaches either branch: CloneBestLand is new and only Thespian's Stage uses it.

🤖 Implemented with the assistance of Claude Code (Opus).

liamiak1 and others added 2 commits July 25, 2026 15:59
Thespian's Stage was AI:RemoveDeck:All because CloneAi's default target
branch just returns false - it has no way to choose a land, and the comment
there suggests the flag for cards in exactly that position.

Add a CloneBestLand logic that picks the best land the AI already controls
by GameStateEvaluator.evaluateLand, the same scoring AiController uses to
decide which land to play, and activates only when that beats the Stage.

Our own lands on purpose: evaluateLand scores a land under whoever controls
it now, so an opponent's Cabal Coffers reads 6 with no Swamps on their side
and 306 with four on ours. Judging their board gets it wrong in both
directions, and the copy arrives under our control either way.

Timed at the end of the turn before ours, reusing the base class's AtOppEOT
rule: the upgrade is permanent so there is nothing to gain by taking it
earlier, the mana stays available as a threat until then, and since the cost
taps the Stage it unlocks again in our own untap step.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Plays until the AI activates Thespian's Stage of its own accord and checks
that it became the better land it controls, that it waits for the turn
before ours rather than taking it in our own main phase, and that it leaves
the Stage alone with only basics around.

Drop this commit if you would rather not carry the test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@liamiak
liamiak force-pushed the let-ai-use-thespians-stage branch from eedf15c to bd8d4d6 Compare July 25, 2026 22:04
@liamiak

liamiak commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Force-pushed a timing change: the Stage is now taken at the end of the turn before ours, rather than in our own main phase.

Two reasons. The upgrade is permanent, so there is nothing to gain by taking it early, and holding the mana until then leaves a possible threat open. The second one I had missed: the cost taps the Stage, so activating in our own main phase costs us the copied land's mana for that whole turn, while at the opponent's end step it unlocks again in our untap step.

That is exactly the base class's AtOppEOT rule, so the override delegates to it rather than restating the condition. Added a third test asserting the AI declines during its own main phase.

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.

2 participants