Decouple the replacement zone scan from PERFORMANCE_MODE#11328
Conversation
PERFORMANCE_MODE currently gates two unrelated things:
Spell.java:101 skips the LKI re-control when the activator is
not the controller. This is what the settings
description documents, and what its warning is
about: it breaks MayPlay on cards you do not
control (e.g. Petty Larceny's exiled cards).
ReplacementHandler:110 the Tap/Untap zone skip from Card-Forge#11160. Not
mentioned in the settings UI at all.
Measured over 3 headless 4-player games, the second is worth ~140s of a
~155s replacement-scan cost; the first is worth ~3.8s and carries a known
correctness bug. Users are told the flag will break their cards, which is
true of the half they did not ask for, so nearly nobody turns it on and
the scan optimisation is inert by default - it is not exercised by CI
either.
This separates them. The zone restriction becomes unconditional and
derives its zones from the card pool instead of a hardcoded pair:
per CR 113.6 these replacements only function from the battlefield (or
the command zone, for Effects), and any card that declares ActiveZones$
widens the scan for that event. That also fixes a gap in the current
skip, which returns before zonesCheck and so ignores such a declaration
entirely - a custom card could not opt in even by asking correctly.
PERFORMANCE_MODE keeps its default and now governs only the Spell.canPlay
branch, which is exactly what its description says it does.
Per-call measurements (game length varies between runs):
Tap 2788.6 -> 194.4 us/scan
ProduceMana 2749.2 -> 178.7 us/scan
RE-list rebuilds per scan: 1681 -> 34
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
interesting idea, but at the same time pretty over-engineered |
|
On #11058 being the right approach here — one profiling note, in case it's useful for sequencing. I profiled one
The thing that would actually retire the skip — for every event, not just these three — is making that per-card rebuild cheap: either caching the RE list per card the way keywords already are ( |
PERFORMANCE_MODEcurrently gates two unrelated behaviours:Spell.java:101ReplacementHandler.java:110The settings description reads:
That describes only the first. And it's accurate — skipping the re-control means
checkActivatorRestrictionsevaluates"You"againstc.getController(), which is still the opponent, so aMayPlaypermission granted to you doesn't match. That's the reported Petty Larceny behaviour (its exiled cards become unplayable, lands included).So the second behaviour — a large, rules-derived win — is hidden behind a checkbox users are correctly told will break their cards. Not everyone enables it, which means #11160 is inert for most users and never runs in CI.
Measurements
3 headless 4-player games, "Big 240531" mirror, normalised per call (sims aren't deterministic, so game length varies between runs):
For comparison, on the same workload the
Spell.canPlaybranch fires on 25.8% of calls at 161.8 µs each — about 3.8s, against ~140s for the scan restriction. The safe half is roughly 97% of the benefit.What this changes
The zone restriction becomes unconditional and derives its zones from the loaded card pool rather than a hardcoded pair. Per CR 113.6 these replacements only function from the battlefield (or the command zone, for Effects); a card that needs one active elsewhere declares
ActiveZones$, and that widens the scan for that event.PERFORMANCE_MODEkeeps its current default and now governs only theSpell.canPlaybranch — exactly what its description says.It also closes a gap
The current skip returns before
replacementEffect.zonesCheck(cardZone), so it ignoresActiveZones$entirely. A custom card that declaredActiveZones$ Graveyardon a Tap replacement would still be skipped — the flag's stated purpose ("in case a custom card wants one active from elsewhere") isn't actually served today; it's all-or-nothing. Verified by adding such a card and watching the computed set:Notes
getReplacementEffects()rebuild, which measured as ~78% of scan cost. Restricting the traversal is a possible follow-up.AI credit: Claude (opus 4.8) is doing a the work here.
Edit/note from non-AI: I'm mainly pushing back on things that don't quite make sense to me (this one took a bit of settling to get to something that seemed an actual fix instead of symptom remediation (which could rhyme with bloat).