Skip to content

Auto-tap: Add a tracer to easily see what it selected#11208

Open
Eradev wants to merge 2 commits into
Card-Forge:masterfrom
Eradev:autotap-payment-plan
Open

Auto-tap: Add a tracer to easily see what it selected#11208
Eradev wants to merge 2 commits into
Card-Forge:masterfrom
Eradev:autotap-payment-plan

Conversation

@Eradev

@Eradev Eradev commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

It adds a debug trace when it enters the player's payment prompt to display the ordered steps in which it will attempt to pay for the cost. It will help with ordering, colors, and amount tracking. No logic change in this PR.

Disabled by default, enabled with the VM flag -Dforge.debugManaPayment.plan=true. [test] is the dry run and [prod] is the actual actions.

Example display:

MANA_PAYMENT_PLAN [test] {1}{B} for Feed the Swarm
  1. Tap Terramorphic Vista (13) -> B (paying {B} for {1}{B})
  2. Tap Swamp (14) -> B (paying {1} for {1}{B})

Next will be mana source sorting + SVar:AIManaReserve:True support.

@Eradev Eradev requested a review from tool4ever July 11, 2026 09:12

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

so much overhead:
we could possibly cherry pick this if you'd share a more detailed description of your general approach for the parent and how the logic flows, I don't feel comfortable complicating production code if it's for a dead end

something building out plan steps closer to how simulation works sounds like a possible direction though

but we don't need another parallel debugging route, the code is already full of DEBUG_MANA_PAYMENT - I can see how that output wouldn't be clear enough for approaching nested paying

for this IHasForgeLog.aiLog is already available and TinyLog gives you support to manage the logging levels in a cleaner fashion

@Eradev

Eradev commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

so much overhead: we could possibly cherry pick this if you'd share a more detailed description of your general approach for the parent and how the logic flows, I don't feel comfortable complicating production code if it's for a dead end

something building out plan steps closer to how simulation works sounds like a possible direction though

but we don't need another parallel debugging route, the code is already full of DEBUG_MANA_PAYMENT - I can see how that output wouldn't be clear enough for approaching nested paying

for this IHasForgeLog.aiLog is already available and TinyLog gives you support to manage the logging levels in a cleaner fashion

There is indeed overhead for preparation for the rest (nested checks).

I'll check IHasForgeLog see if I can't reuse it.

@Eradev Eradev force-pushed the autotap-payment-plan branch from d67aa69 to 865ce8f Compare July 11, 2026 11:28
@Eradev

Eradev commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

The whole plan is to have everything funnel through one planner, sharing the same decision path so the plan and execution match.

  • Lighter checks for quick castability checks (canPayManaCost). Doesn't run the more expensive probes as you just want to know if you can cast it in any way.
  • Heavier checks for payManaCost/payManaCostFromPaymentPrompt which takes into account cards in your hand and command zone to maximize the number of plays. (CastabilityProbe, which is toggleable if performance is ever an issue) It only runs in the prompt preview for the user, and the actual cast (What is called [prod]).

The plan

  • Add support to calculate the correct mana for various effects such as:

    • Cabal Coffers
    • Mana Flare
    • Utopia Growl
    • Goldspan Dragon
    • Gemstone Mine
    • etc.
  • Sort the mana abilities depending on criteria such as (but not limited to), and adds the pool to the context:

    • Is it a color filter?
    • Can it fulfill multiple colors? (Signets)
    • Is it a disposable source? (Lotus Petal, Treasure token)
    • Does it sacrifice itself, or another permanent?
    • Does it require to tap a creature?
    • Is it a net-negative in term of mana?
    • Number of mana it produce.
    • No untap drawback (Mana Vault).
    • Sort abilities for generic mana depending if you need {C}. (Eldrazi)
    • Takes into account utility lands you do not usually want to use for mana (Karakas, Library of Alexandria) via SVar:AIManaReserve:True

    This already existed, just made it more robust so it prefers sources that has less drawback.

  • Find the best candidates for each pip that needs to be paid with everything it learned from the context:

    • Try not not to waste mana (Gaea's Cradle vs Forest).
    • Chaining signet/filters/combo sources if necessary.
    • Prevent being stranded with unusable lands (Not left with lands that have an activation cost), and use the Castability probe, if activated, to try to maximize the number of cards you can play (e.g. Choosing a Plains vs Mountain for generic mana when you have Lightning Bolt in hand).

The final payment loop will look like

payManaCostImpl
├── pool / deposited surplus
├── while (!cost.isPaid())
│   ├── tryPayViaManaBankingChain        (signet → combo land → pool)
│   ├── tryApplyConsolidatingFilter      (multi-pip signet/Coffers/combo)
│   ├── getNextShardToPay
│   ├── sort generic candidates (GenericColorPreference)
│   ├── chooseManaAbilityForShard
│   │   ├── collectValidManaPaymentChoices
│   │   ├── CastabilityProbe.pickBest?   (hand/command + full probes)
│   │   └── preferSourceThatKeepsRestPayable (stranding, Cradle, consolidator rescue)
│   └── applyChosenManaPayment
│       ├── simulate/executeNestedActivationCost  (free source pays {1})
│       ├── setComboManaChoice (Any / AnyDifferent)
│       └── payMultipleMana → remove host from pool
└── refund on failure (prod)

Edge cases

I don't know how strict you want the tests, or if you guys want to check for edge cases. (e.g. Yuna, Grand Summoner) I think it is overkill. Sorting + chaining + maximizing plays should be enough.

I'll adjust in consequence.

@tool4ever

Copy link
Copy Markdown
Contributor

Alright, that's nice but still too much of a mixup, improving existing parts like scoring for better decisions can just be done in small steps on their own instead...

Chaining signet/filters/combo sources if necessary.

This is recursion. How will AI decide it's needed and put a viable plan together? Will it just perform brute force attempts to see if the filtering gives it the shards it needs?

@Eradev

Eradev commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

It all start by creating a context (ManaPaymentContext) that it passes through every payManaCost call. It hosts the memorization caches for the entire run, and let us know if we need to run the expensive probes or not, the current depth (e.g. if we're in a nested payment for a filter), the floating mana during the planning phase (so it doesn't touch the real pool), and the plan steps (log).

Then we go to create a screenshot of the playable mana sources (getOrBuildManaAbilityMap):

  • It indexes all the playable mana abilities (SpellAbility) and caches them in the context.
  • It then assign them to a Multimap depending on what they produce: Generic (all the sources, fallback bucket), each colored shard ({WURBG), mixed (multiple colors), etc. A Forest for example would go inside Generic and G buckets, a Signet goes into the Generic and each of the two mana it generates.

We can pass a the actual cost of the spell/ability to groupAndOrderToPayShards to get the available candidates inside the context. This handles the filtering and sorting which ability to check for which mana:

  • For each of the required shard (e.g. Red and Generic for a {2}{R} spell), it checks each candidate to see if it can be used
  • It sorts the remaining candidates depending on various properties (such as if it's a permanant source or disposable, if they have drawbacks, it is a creature that can block or attack, etc).
  • It finally trims the candidates list depending on how many pips are required (Max 5) so we don't test 20 different red sources
  • Then returns the two shard buckets of usable, sorted candidates for {R} and generic mana.

For the actual selection loop (The one in the previous comment).

Let's take a {1}{R}{R} spell with a battlefield of a Wastes, and two Rakdos Signets.

  • So we start with the non-generic shards ({R}) with lowest amount of candidates. (Two Signets)
  • We call groupAndOrderToPayShards and see that the candidates in the context for {R} are [Signet 1, Signet 2]. We select one, and it has an activation cost of {1}, so we nest the loop for {1}.
  • There's only one source we can use so it selects Wastes and remove it from the available candidates, then return.
  • Since the Signet gave multiple mana, we check if {B}{R} can pay for the entire remaining cost ({1}{R}{R}). It can't so we pay {R} and float {B}, then continue to the next pip in {1}{R}.
  • Find the remaining Signet, have a {B} floating to pay for the activation cost. It gives use {B}{R} that can pay for the {1}{R}. The loop stops because the spell has been paid.

There is still some brute forcing but it is limited as we sort and filter the candidates we test beforehand.

Heuristic sort  →  try top 5 candidates  →  simulate each locally  →  pick best  →  commit  →  next shard
                         ↑                        ↑
                   limited brute force      recursive dry-run

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