Repository navigation
The Darkhold (MDD) and other MDD spoilers - #12202
Merged
Merged
Conversation
Fulgur14
marked this pull request as ready for review
October 9, 2026 20:45
Fulgur14
marked this pull request as draft
October 10, 2026 05:50
tool4ever
reviewed
Oct 10, 2026
Contributor
Author
|
It seems that Hela's Arcanum using different symbol than Bloomburrow Season cycle is just a cosmetic change. I put a placeholder in the Oracle text until we know the official name of the symbol, maybe the mechanic will have to be adjusted to include the symbol as a variable, but anyway, it should work for now. |
Fulgur14
marked this pull request as ready for review
October 10, 2026 08:12
tool4ever
approved these changes
Oct 10, 2026
BramTeurlings
added a commit
to BramTeurlings/forge-archipelago
that referenced
this pull request
Oct 13, 2026
* fixes not able find image for funny cards that have variants
* Use shared SpriteBatch for Adventure and Classic (#12011)
* Use shared SpriteBatch for Adventure and Classic
* disable transistion overlay, fixes android slow screen updates
* set camera update before rendering
* Update HudMap
* reuse existing graphics for FBufferedImage
* remove unused vars
* reduce cap, dont compute for map if not visible
* Client-side automatic reconnect for in-game disconnects (#10466)
- Reconnect modals for desktop and mobile (cancel / rejoin / return-
to-main), with proper lobby-state teardown on both platforms
- /simulatedisconnect test hook (OUTBOUND/INBOUND) to exercise both
detection paths
- Server echoes HeartbeatEvent to keep the client's READER_IDLE alive
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Change DBSeek defined player to 'You'
This fixes a bug where all players seek the creature card instead of just a player controlling Emperor.
* Drive floating hand reveal from the game thread (#12016)
Revealing an opponent's hand as a floating zone handed updateZones,
tempShowZones and hideZones to the UI thread. For a network player the
GUI is RemoteClientGuiGame, which walks the game state before sending, so
the hide walked the state on the UI thread while the game thread resolved
the next effect. Repeated reveals (storm-copied Ignite Memories) hit a
ConcurrentModificationException in Tracker.getDelayedPropsFor.
Co-authored-by: Claude <noreply@anthropic.com>
* Fix macro replay after auto-yield input flow changes (#10930)
* Fix sim directory
* Edition updates: PF27
* Give each card test its own database through the singleton too
StaticData's constructor assigns the process-wide instance, so a test class
that builds a database of its own leaves StaticData.instance() pointing at
it for the rest of the JVM. The lazily loading one empties its database
before every method, so a class that runs later and reads the singleton
rather than FModel finds no cards at all and reports every lookup as an
unknown card. DeckRecognizerTest reads the singleton, which is why it has
been failing in batches on CI while passing when run on its own.
CardMockTestCase now installs the database it means to use as both the
FModel mock and the singleton, before each method, and the two classes that
build a database of their own go through the same call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* remove unused var, iterate backwards for safety
* Trim the javadoc on useStaticData
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* add getLabelKey for Reward enum
* fix fbufferedImage, refactor audit to work on android device (it will take some minutes to finish on android)
* fix planar genesis (#12028)
* edit
* Code cleanup
* Loot the Anomaly
* Add FRA rankings for card collection
Added rankings for various cards in the FRA set.
* Add percentage when auditing cards, check scissor on FBufferedImage
* Add guard when auditing cards, fix dark sprites frambuffer
* typo
* resolve conflicts
* Update budding_insurgent.txt
* Update living_library.txt
* Update venser_fervent_forger.txt
* Update elvish_elegy.txt
* Fix Sanctum Lurker's IgnorePlaneswalkerZeroLoyaltyRule param name (#12035)
* Update flatman.txt
* Update graphics, utils, fskinfont and apply reusable vars (#12034)
* Update graphics, utils, fskinfont and apply reusable vars
* rename var
* replace FSkinFont TextBounds legacy code with GlyphLayout
* update TextBounds
* update TextRenderer, FLabel, FButton, CardRenderer, fix watermark
* Command the Stage (FRA) (#11958)
Co-authored-by: tool4EvEr <tool4EvEr@>
* Update Player.java
fix getMonarchSet
* Update FRA.rnk
* remove-function
* Fix two card-script param bugs found porting to Crucible
the_disciple_of_vess.txt: deleted a trailing line
"(There are currently thirteen. Perhaps look up the list and roll a D20?)\nPERSON" -- a leftover authoring note, not a
script line. Forge's own FileSection parser rejects it as an unknown key.
ginger_queen_of_sweets.txt: renamed TokenOwner$ to Controller$ on its
DB$ CopyPermanent line. CopyPermanentEffect.java reads
sa.getParam("Controller") (resolve(), line ~136), never "TokenOwner" --
the param never reached the effect. Renamed to match the corpus's own
convention for CopyPermanent's controller param (5 other cards).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsS1N7fkNqkDRYXM3ZskjZ
* fix(card): address PR review comments on two-param-name-bugs
the_disciple_of_vess.txt: restore the real reminder text (verified against
Scryfall's oracle_text) instead of dropping it entirely; only the bogus
"PERSON" leftover was junk.
ginger_queen_of_sweets.txt: drop Controller$ You entirely per Hanmac's
review comment. CopyPermanentEffect.java:134-142 defaults controllers to
the activator when Controller is unset, which is the same player for this
triggered ability, so the param was redundant.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* guard FSkinFont glyph computation and add fallback
* update extra check
* Add Match Duration to GameLog (#12044)
* Add Match Duration to GameLog
* add space
* fix match duration
* Load Commander precons and genetic AI decks read-only (#12045)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Update Missing-Cards-in-Forge.md
* Remove Trample ability from A.I.M. Scientists
* update LibGDXImageFetcher, ImageCache, RewardActor (#12054)
* update LibGDXImageFetcher, ImageCache, RewardActor
* Deferred rendering
* update tooltips
* disable load card lazily on mobile
* forge-gui: characterize PlayerControllerHuman.assignCombatDamage (#10827)
Add a test harness to forge-gui and characterization tests for the
first engine-to-UI seam in PlayerControllerHuman, ahead of moving UI
logic out of the controller. No production code changes.
* Harness: TestNG, Mockito and AssertJ as test-scoped dependencies
(same TestNG and Mockito versions as forge-game and
forge-gui-desktop), surefire picking up *Should classes, and
Mockito's subclass mock maker.
* FakeGuiGame: a strict stand-in for IGuiGame. Any prompt (non-void
call) fails the test unless scripted; notifications (void calls) are
accepted. Prompts are scripted and recorded through typed methods, so
the fake does not have to track every change to IGuiGame.
* HumanControlledGame: a two-player game with human controllers, built
on a real Game without FModel, the card database or preferences.
Cards are created from inline script text. Runs in about two seconds.
* PlayerControllerHumanShould: pins when assignCombatDamage prompts the
player (trample with a defender, several blockers) and when it decides
alone (one blocker, no blockers, trample without a defender), and how
the answer is mapped back to engine cards (null key for the defender,
non-blocker entries dropped, null answer passed through).
Verified by mutating the trample check in assignCombatDamage: 5 of the
8 tests fail.
Validate from the command line:
mvn test -pl forge-gui --also-make --no-transfer-progress
Code written with AI support.
* Fix card script errors found by the card-script linter (#12048)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Move zone display decisions from host to client (#12023)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Edition updates: SLD
* IGuiGame cleanup (#12056)
* Fix default Slingshot Bandit deck (#11735)
Co-authored-by: Codex <codex@openai.com>
* Replace weak Master Blue Wizard deck with tempo list (#11732)
Co-authored-by: Codex <codex@openai.com>
* fixes Killer Cosplay (#12062)
* update FrameRate, ScreenUtil capture (#12060)
* update FrameRate, ScreenUtil capture
* remove redundant vars
* Update captured frame
* Update stinging_vitriol.txt
Closes #12070
* Update match transition screen (#12068)
* Update match transition screen
* update ui background
* update event transition bg
* update Reward type for ArenaScene and EventScene
* update RewardActor
* update DuelScene, use lastScreenTexture on exit
* Fix hand reveal not opening when the revealed card is a copy (#12065)
Co-authored-by: tool4ever <tool4ever@>
* [maven-release-plugin] prepare release forge-2.0.15
* [maven-release-plugin] prepare for next development iteration
* Restore POM files for preparation of next release
* Restoring pom.xml
* fix preview spam
* Update flare_of_fortitude.txt
* Migrate upcoming card scripts
* fix radius, reduce graphics draw call (#12073)
* Some cleanup (#12075)
* Pick the commander in the Commander lobby (#12052)
A Commander deck can now be led by another card for one match, without
copying or editing the deck. Each human or AI player in the lobby gets a
"Commander" button that opens a picker with card preview, listing:
1. the deck's default commander(s), and each half of a default pair
2. alternates suggested by the new AltCommanders= deck metadata
3. every other card in the deck that is a legal commander for it
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* add lastpreview check, update booster reward type
* Help AI choose better for Memories Returning
* Fix ordering (again) when undo casting from library
* Small fix for conditional
* Batch TargetingOverlay Arrows for faster FPS (#12080)
* Fix GameState parsing of token images causing malformed keys on ImageFetcher, update ImageCache
* Update TargetingOverlay, update comment (#12082)
* Update adventure data backup (#12088)
* Update adventure data backup
* add selector box and check zip
* update layout and text wrap
* Update restorebackup file check
simple check for the backup
* Add puzzle PS_FRA1 - Possibility Storm - Reality Fracture #01 (#12092)
* - Add puzzles PS_HOB2 and PS_HOB3.
* - Fix Zidane event in Planar Conquest FIN plane.
* - Add puzzle PS_FRA1
* Edition updates: SLD, UNK
* Unfinity stickers and sticker sheets (#12047)
* Unfinity sticker sheets: data, deck section and pregame choice
Adds the 48 Unfinity sticker sheets as a new Stickers card type and deck
section, and the CR 123.2a pregame step that reveals three of them.
No card can place a sticker yet; this is the foundation that phase 2 builds
the sticker registry and the place-a-sticker effect on.
Each sheet script carries its ten stickers: three name, three art, two
ability and two power/toughness. Ability stickers record their ticket cost
and printed text; the Forge ability itself follows in a later phase, and a
sticker with no Ability$ is not offered.
Sheets live in the [eternal] section of the new SUNF edition so the funny-card
filter in StaticData keeps them - the eternal-legal Unfinity cards need sticker
sheets to function.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Show the sticker sheets zone in the desktop client
Adds the deck editor section for sticker sheets and the in-game zone, so a
deck can be built with them and the three revealed sheets can be seen.
The zone icon FSkinProp.IMG_ZONE_STICKER was already defined and unused.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Assert the deck editor's sticker sheet pool is populated
The editor only shows its sticker sheet section when the pool is non-empty,
so an empty pool would drop the section with no other symptom.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Read the stickers off a sticker sheet
Adds Sticker, StickerKind and StickerSheet: a sheet names its stickers in a
StickerSheet keyword and defines each in an SVar, the way a dungeon names its
rooms, so AbilityFactory.getMapParams reads them.
A sticker is identified by its sheet and slot rather than its text, since
CR 123.3a makes two stickers distinct even when they read the same.
The sheet data tests now go through this parser instead of matching SVar
strings, and check the ticket costs and name words as parsed values.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Put stickers on cards, and keep them across public zones
A card holds the stickers on it with the two things placing one decides: the
timestamp, and for a name sticker the word position from CR 123.6b.
Name stickers are not applied one at a time. Every name sticker on a card
contributes to one name, so the card rebuilds the whole name from its own name
plus each sticker in timestamp order (CR 123.6c), clamping a position that is
now past the end of the name.
GameAction.changeZone carries stickers to the new object only when the
destination is a public zone, which is CR 123.5. Removing that gate fails
StickerApplyTest.
Ability stickers still grant nothing - their sheet scripts carry no Forge
ability yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Place a sticker, and the first card that does
Adds the PutSticker effect, the two choices placing a sticker needs on
PlayerController, and _____ Goblin as the card that exercises them.
The pool a player chooses from is derived rather than tracked: a sticker is
available when it is on one of their revealed sheets and is on no object they
own. That makes CR 123.5 fall out for free - a sticker on a card that moves to
a hidden zone is on nothing, so it is choosable again - with no bookkeeping to
get wrong.
Ticket costs gate the choice as well as being paid, per CR 123.3c, and an
ability sticker with no Forge ability yet is never offered.
After placing a name sticker the effect records StickerWord, StickerLetters
and StickerUniqueVowels on the ability, so a sub-ability reads them the way a
die roll's sub-abilities read its result.
DeckRecognizer's card-name pattern had no underscore, so a deck list naming
_____ Goblin would not parse. That is what DeckRecognizerTest caught, and the
pattern now allows it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* File _____ Goblin under b, following +2 Mace and 1996 World Champion
Forge files a card whose name starts with a non-letter under the first letter
of how the name is spoken: "+2 Mace" sits in p, "1996 World Champion" in n.
A blank reads "blank", which is also the comprehensive rules' own wording for
it, so the five other cards in the set that begin with blanks go here too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Three more name sticker cards
_____ Bird Gets the Worm, _____-o-saurus and Wizards of the _____ all read the
unique vowels of the sticker they just placed, so they need nothing beyond the
PutSticker effect.
All three begin with blanks or would file under a blank, so they sit in
cardsfolder/b alongside _____ Goblin.
Note for the test: DigAi declines any dig before main 2 that names no
DestinationZone, so Wizards of the _____ is asked for in main 2. That also
means the AI passes up the free name sticker in main 1, since declining the
trigger declines the whole thing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* A sticker-placed trigger, a sticker count, and four more cards
TriggerType.StickerPlaced fires from PutStickerEffect with the object, the
player and the sticker's kind. StickerKind takes a comma separated list, so a
card reading "if it's an art sticker, instead ..." is two triggers - one on
Art, one on the rest - rather than a trigger-level condition, which cannot
reach the triggering object anyway.
Count$CardStickers counts stickers on a card, by kind and, for the cards that
care about the letters on their name stickers, by NameMinLetters,
NameMaxLetters and NameLetter.
The card property is stickeredWith, not withStickerKind: the broad
"with<keyword>" branch in CardProperty matches anything starting with "with"
and would have swallowed it, leaving the new branch unreachable.
Cards: Goblin Airbrusher, Wee Champion, _____ _____ _____ Trespasser and
Sword-Swallowing Seraph.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Four more sticker cards
Baaallerina and A Good Day to Pie, plus Make a _____ Splash and
_____ Balls of Fire, which count a single letter across the name stickers on
themselves through Count$CardStickers.NameLetter.
A Good Day to Pie triggers from the graveyard, so it is the first user of
StickerPlaced with a TriggerZones other than the battlefield.
Twelve of the forty-six in-scope cards now work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Twelve more cards, and choose rather than target
These cards say "a nonland permanent you own", not "target", so PutSticker now
takes Choices$ and only falls back to ValidTgts$ for the few abilities that
really do target. Baaallerina and Sword-Swallowing Seraph move over too.
PutStickerAi gained target selection, which it never had: it picks its own
best legal permanent, preferring one that is not stickered yet.
The twelve cards are generated from the Scryfall record so their mana cost,
type line and power/toughness cannot be mistyped - three of five hand-typed
P/T values were wrong before the generator replaced them.
Twenty-four of the forty-six in-scope cards now work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Five more cards, including the first sticker on a graveyard card
Proficient Pyrodancer, Unlawful Entry, Bioluminary, Prize Wall and Scampire.
Scampire is the first user of ChoiceZone$ Graveyard, and the first card where
a sticker goes on something that is not a permanent - a creature card in the
graveyard, which its activated ability then looks for by the stickered
property. A test covers that path.
Its delayed exile uses Animate's AtEOT$ Exile, the idiom Ach! Hans, Run! uses,
rather than the hand-rolled delayed trigger it started as.
Twenty-nine of the forty-six in-scope cards now work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Ability stickers grant their ability
CR 123.7: placing an ability sticker now grants the object what is printed on
it. Keywords are granted directly; anything with its own script is built from
an SVar on the sheet the sticker came from, so a written ability can refer to
other SVars there.
Twenty-seven of the ninety-six ability stickers are keyword-only and are
generated with the keywords they grant. The other sixty-nine need a written
ability and stay unofferable until they have one, which is what isImplemented
already gated on.
This makes two of the ten stickers on a sheet live for the first time, so it
changes what every card that places a sticker can actually do, not just the
three cards that ask for ability stickers by name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Ability stickers can grant a static ability
CR 123.7 gives the object whatever is printed on the sticker, and six of the
ninety-six ability stickers print a static ability - Hellbent, Threshold,
Metalcraft, ward, a protection, and casting from the graveyard for 2 life.
They had nowhere to go: grantAbility passed null for the statics slot of
addChangedCardTraits, so a sticker could only ever grant keywords, activated
abilities and triggers.
The granted traits are now built against the sheet's card state rather than
the sheet card. That is the wiring a static's AddAbility already uses, and it
matters for the same reason: an ability's own SVars are looked up through its
card state when it resolves, and the card state was the stickered card, which
has none of them. Only SVars read at parse time worked before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Count generic mana, a card's types, and its creature types
Three sticker abilities ask for numbers Forge could not produce. Each is a
one-case addition next to the counts it sits with:
Count$TriggeredManaCostGeneric - the generic mana in the cost of the spell
that triggered, for Misunderstood Trapeze Elf's sticker. It goes next to
TriggeredManaCostDevotion, which walks the same cost for coloured pips.
Count$CardTypeCount - supertypes plus card types plus subtypes, for Unique
Charmed Pants' sticker.
numCreatureTypes - a card property, so Notorious Sliver War's sticker can
grant protection from creatures with two or more creature types. It joins
numTypes, which counts only the core types. Changelings are handled by
CardType.getCreatureTypes, which already expands them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* The sixty-nine written ability stickers
Phase 4. Every ability sticker on every sheet now grants what it prints, so
all ten stickers on a sheet are live and any card that places an ability
sticker can reach all ninety-six.
The shapes are thirty-nine triggers, twenty-four activated abilities and six
statics, and almost every one was transcribed from an existing Forge card
with the same ability rather than written fresh. Helper SVars are prefixed
with their slot, since a sheet holds two ability stickers and they share a
namespace.
A sticker's script is only parsed when the sticker is placed, so a sheet
carrying a broken one loads without complaint and falls over mid-game.
StickerAbilityTest therefore places all ninety-six and checks each grants
something, and covers the cases the parse cannot: the three conditional
statics turning on and off, the two new counts, an SVar read off the sheet
from inside a card filter, and the graveyard-cast sticker, which only does
anything in the zone the sticker was carried into.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Eight more cards that hand out tickets and stickers
Finishing Move, Robo-Pinata, Command Performance, Costume Shop, Done for the
Day, Park Bleater, Lineprancers and Tusk and Whiskers - 37 of the 46 in-scope
Unfinity cards now work. None of them needed anything new: the ticket counter,
PutSticker's Kind$ and Choices$, the StickerPlaced trigger and the stickeredWith
property were all already there, and Costume Shop is an ordinary attraction
script whose six printings the edition file already lists.
The nine left all want a mechanism that does not exist yet - stickers granting
their abilities to a different object, a sticker distributed across two
permanents, a base power from stickers' total power, a sticker kicker keyword,
choosing a letter, or an enchantment that becomes an Aura mid-resolution.
StickerTicketCardTest builds each of the eight, since a script's abilities are
parsed when the card is built, and covers what the harness can drive: the
kind restriction on Lineprancers' sticker, Park Bleater paying out only for
other creatures, and Tusk and Whiskers rewarding the creature an ability
sticker went on. Dies and end step triggers do not fire in this harness even
for plain Forge cards, so Robo-Pinata is checked for its two modes instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Wolf in _____ Clothing and Fight the _____ Fight
Both say "you may put a name sticker on it. When you do, ..." - a reflexive
trigger that has to read the sticker that was just placed, which is why they
waited. DB$ ImmediateTrigger already does this for a die roll, and
PutStickerEffect already recorded the word, its letters and its unique vowels,
so the two cards are the same shape as Ancient Bronze Dragon: the sticker's
value rides along in RememberSVarAmount and the trigger's targets are chosen
after the sticker exists.
PutStickerEffect now also records StickersPlaced, and clears all four values
at the start of each resolution. Without it, "when you do" had nothing to
condition on, and a second resolution of the same ability would have read the
first one's word - the ability object is reused.
39 of the 46 in-scope cards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Ambassador Blorpityblorpboop and Roxi
Roxi needed nothing new: her power is two existing counts added together, one
over the battlefield and one over the graveyard, both keyed on the
stickeredWith property, and "distribute up to two art stickers" is two
optional PutSticker sub-abilities restricted to Kind$ Art.
The Ambassador needed one thing Forge could not do: add up what is printed on
the power and toughness stickers a player has in play. Count$StickerPower and
Count$StickerToughness take a card filter the way Count$Valid does, so the
card asks for Permanent.YouCtrl and the opponent's stickers stay theirs.
41 of the 46 in-scope cards. The five left are Pin Collection and Clandestine
Chameleon (an object gaining the abilities of stickers on a different object),
Wicker Picker (a sticker kicker keyword), _____ _____ Rocketship (choosing a
letter) and Last Voyage of the _____ (an enchantment that becomes an Aura).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Pin Collection and Clandestine Chameleon
Both say an object has the abilities of ability stickers that are on a
different object, which is one mechanism and so one commit. Statics take
GainsStickerAbilitiesOf$ / GainsStickerAbilitiesOfDefined$, reusing the card
list and the zone parameter GainsAbilitiesOf already has. It cannot reuse
GainsAbilitiesOf itself: that copies activated abilities only, and a sticker
can print a keyword, a trigger or a static just as easily.
AppliedSticker now builds what a sticker grants without applying it, so the
static hands the affected card exactly what the sticker would have given the
card it sits on.
PutSticker also takes MaxTickets$ and NoTicketCost$, for Pin Collection's
"an ability sticker with ticket cost X or less ... without paying that
sticker's ticket cost". The cap has to reach the offer, not just the payment,
so the pool is built against what the card allows rather than what the player
has: StickerSheet.getAvailableStickers takes that budget as an argument.
43 of the 46 in-scope cards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* The last three cards: all 46 in scope now work
_____ _____ Rocketship needed a letter chosen and then the name stickers that
begin with it. The chooser is DB$ ChooseType with the alphabet as its
ValidTypes, which stores the letter where the count can read it; the count is
a new CardStickers.NameStartsWith, since the existing NameLetter counts how
often a letter appears rather than what a word begins with.
Wicker Picker's sticker kicker is a kicker variant, so it grants Kicker:1 to
the creature spells you own - the static reaches them in hand, where casting
reads its costs - and pays out from a SpellCast trigger gated on Spell.Kicked.
Last Voyage of the _____ is Animate Dead's shape: reanimate, animate into an
Aura with enchant creature, attach, and leave behind a delayed trigger holding
the creature's LKI so it can be sacrificed when the Aura goes. The only new
part is the name sticker placed before any of that happens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Sticker sheets in the mobile client
The mobile deck editor already offered the section - GameType hands it the same
supplemental sections the desktop editor uses - but nothing downstream knew
what to do with it, so it fell through to the "unsupported extra deck section"
branch. It now has its page config, its catalog filter, its card pool, its
add/remove labels and tap-to-eject, like attractions and contraptions.
In a match, the sticker sheets zone joins the extra zones dropdown, which is
all it needed: the zone is already tracked, so the view fills itself.
Choosing a sticker needed no mobile code at all, since the prompt goes through
the shared dialog. Two things were wrong with it for both platforms, and are
fixed here: an optional sticker could not be declined, because the prompt
demanded exactly one choice, and the list showed nothing about what a sticker
costs, which is usually the whole decision.
The mobile UI has no test harness. What guards it is that the three things
both editors build their tab from - the section being offered, an icon for it
and a tracked zone - are asserted in StickerSheetTest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Clean up the sticker code after review
Four review passes over the branch - reuse, simplification, efficiency and
altitude - and this is what they found. No behaviour changes.
Reuse of what Forge already has:
- AppliedSticker now implements PerpetualInterface, which it had been
re-declaring method for method, and hands back a CardTraitChanges instead of
filling four out-parameters. applyEffect is now the same line
PerpetualAbilities uses.
- Count$CardTypeCount is gone: handlePaid's AllTypes is the same sum, and
Embiggen already scripts this very Unfinity wording that way.
- Count$StickerPower/StickerToughness move into handlePaid, so the card writes
Count$Valid Permanent.YouCtrl$StickerPower and inherits the zone and
last-state handling every other Count$Valid has.
- The sheet setup uses Aggregates.random rather than shuffling a list, and so
builds three cards instead of every sheet in the deck.
- CardPredicates.isOwner, and the ticket cap constant now lives once, in
DeckFormat, where the deck rule that shares it already was.
Simplification:
- The letters and vowels of a sticker's word were written three times in three
modules; they are now on Sticker, which owns the word.
- nonStickered is gone - Forge negates any property with a leading !, which is
what 300-odd scripts already do, and nothing used it.
- clearStickers, StickerWord and StickerLetters had no callers or readers.
- chooseStickerNamePosition no longer takes a word count the caller derives and
the implementation recomputes, and picks a position rather than recovering
one with indexOf.
- countStickers had been inserted between xCount's javadoc and xCount.
Efficiency:
- The keyword pass through a sticker was parsing its abilities, triggers and
statics only to discard them, on every state check.
- The list of sticker abilities was allocated for every static ability in every
game, sticker or not.
- A sheet's stickers are read from its script once and kept on the card; the AI
asks for them every time it weighs placing one, and now stops at the first
available sticker instead of building the whole list.
- Looking for a player's placed stickers walks the five zones a sticker can
survive in rather than all seventeen.
One thing found and fixed that was not a cleanup: Card.setStickers aliased the
old card's list instead of copying it, so adding a sticker after a zone change
would have mutated both cards' lists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* The AI hints the cards were missing
Two conventions the branch had skipped.
HasAttackEffect tells AiAttackController a creature is worth attacking with for
something other than its damage; 762 cards set it. _____ _____ Rocketship sets
it now, and - the part that needed code - so does any creature a sticker grants
an attack trigger to, which is thirteen of the ninety-six ability stickers. A
static that grants a trigger carries the SVar across with AddSVar$; a sticker
now does the same thing when what it grants is an attack trigger, on both the
path that puts it on the card it sits on and the one that hands it to another
object.
DeckHas tells deck generation what a card does that other cards care about.
Fourteen of the forty-six had something worth declaring - counters, tokens,
life gain, mana, sacrifice, graveyard - and each line is derived from what the
script actually does rather than from the card's prose. Nothing is claimed for
a ticket counter, which goes on a player and has no synergy to offer, and
nothing for a dies trigger, which mentions the graveyard without caring about
it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Playtest fixes: six of seven
- The AI activated Park Bleater with nothing to sticker. It asked whether a
sticker was available but never whether the ability had anything to put one
on; an ability that chooses rather than targets can have an empty pool.
- The stack read "puts a sticker on Park Bleater" for an ability that does not
know what it will be on until it resolves. It now says what it will choose
from.
- "Choose a sticker to put on %s" never filled in the name: Forge formats with
{0}, not %s.
- Three art stickers read identically, so a player choosing between them had
nothing to go on. They are numbered by the slot they came from now; they are
still interchangeable (CR 123.9), but at least the list says so.
- The ticket cost went to the front of each choice, the way the sheets print it.
- The AI chose a letter for _____ _____ Rocketship by taking the first of the
alphabet, because nothing told it what a letter was worth. It now picks the
one the most name stickers on the card begin with.
Two tests, for the two that are behaviour rather than wording. Worth knowing
for anything similar: AITest's addCard stamps turn-in-zone on the current turn,
so on turn one everything counts as having just entered - the test has to cross
a turn boundary before "entered this turn" means anything.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* A name sticker fills the blank it was printed for
CR 123.6a: a blank line is not a word of a card's name. So it never counts
towards a sticker's position, and a card printed with one has somewhere the
sticker goes - that is what the blank is for. Each name sticker now fills the
leftmost blank still empty, and only once there are none left does it go after
the number of words chosen when it was placed.
_____ _____ Rocketship with two name stickers reads "Eldrazi Guacamole
Rocketship" rather than keeping both blanks in front of them. A blank nobody
has stickered stays as it is, so the card reads as printed in hand, and a blank
inside a word works the same way: _____-o-saurus becomes Eldrazi-o-saurus.
The prompt that asks where a word goes is skipped when the word is going in a
blank, because there is nothing to choose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Playtest round two
Wee Champion was 1/1 and Goblin Airbrusher 1/1; they are printed 0/1 and 2/1.
Both are from the batch I typed by hand before switching to generating card
data from the printed record. Finding one meant checking all of them, so every
card and sheet this branch adds was compared against Scryfall - name, mana
cost, power, toughness and oracle text. Those two power/toughness values were
the only errors in ninety-four cards. A test had encoded the wrong one, which
is how it survived until someone played the card.
An art sticker changes nothing else a player can see (CR 123.9), so two
creatures that differ only by one were indistinguishable. The details pane now
lists what is on a card, and a sticker sheet lists which of its stickers have
been taken - both tracked the way attraction lights already are.
The AI declined every mode of Done for the Day and took nothing, because
CharmAi only takes an optional mode its own AI wants and nothing in Forge knew
what a ticket counter was worth. A ticket has no downside and nothing to aim,
so the AI now always accepts one.
Not fixed, and not ours: the AI blocked the creature with no combat trigger
over the one that pays its controller tickets. HasCombatEffect is read by the
attack controller only - the block controller does not consult it - so making
that better is a change to Forge's blocking AI, not to these cards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Score what a sticker is worth before choosing where it goes
The AI put a 4/2 power and toughness sticker on a Pin Collection. Two
separate choices were wrong.
None of these cards target - they say "a nonland permanent you own" - so
the object arrives through chooseSingleCard, which PutStickerAi never
overrode. The base class returns the first card in the pool, which is
battlefield order: the AI's oldest permanent, whatever it is.
chooseSticker then scored every power and toughness sticker above every
other kind, against net power and toughness, which a noncreature has
none of. On a creature it was just as happy to shrink one, since a
sticker sets power and toughness rather than adding to them.
Both choices now score the same (sticker, object) pairs, in the units
evaluateCreature counts in, and a placement worth nothing is declined
where the card allows it. The ticket term only exists to stop a
placement that gains zero: tickets buy stickers and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Don't take a set power and toughness that is worse than the current one
Ambassador Blorpityblorpboop's begin-combat trigger sets its base power
and toughness to the total on the stickers you control. It is a "may",
and the AI always said yes - with no stickers out that is 0/0, so the
Ambassador killed itself the turn it landed.
AnimateAi's doTriggerNoCost accepted any non-targeting animate without
looking at it. It now declines one that only sets power and toughness,
only on its own creatures, and only when the animated copy evaluates no
better than what is there. Anything that also grants types, keywords or
abilities, anything with a sub-ability, and anything mandatory is left
alone: those can be worth having at any size.
Four scripts can reach that check - the Ambassador plus Eldrazi Mimic,
Geometric Weird and Mirkwood Meditator, all the same shape. Verified in
a game that the Ambassador now keeps its 3/3 and blocks with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Second review pass over the branch
The one that changed play: PutStickerAi.chkDrawback answered
AiAbilityDecision(0, WillPlay), and a rating of 0 is not willing - the
floor is 30. canPlayWithSubs hands a sub-ability's decision back as the
whole ability's, so a player with no sticker to place would not cast
Finishing Move at all, removal and everything. The override is gone:
SpellAbilityAi.chkDrawback already returns (100, WillPlay) for an ability
that does not target.
Correctness:
- StickerSheet listed the public zones by hand and left out Merged, Ante,
Flashback and Junkyard, where GameAction keeps a sticker but the scan
could not see it - so the pool would offer the same sticker again.
Derived from ZoneType.isHidden() now, the test GameAction itself uses.
- The sheet panel went stale after a stickered card changed zone: the
refresh ran from setStickers, while the card was in neither zone.
- addChangedSVars replaced rather than merged, so a static with both
AddSVar$ and a sticker-granted HasAttackEffect would lose one.
- Granted keywords are trimmed, the way granted traits already were.
Simplicity and reuse:
- PutStickerEffect was the only effect in the tree draining the trigger
queue itself; the stack does that.
- The targeting path in PutStickerAi was dead - no PutSticker script
targets - so it and its three call sites are gone.
- Tusk and Whiskers carries AILogic$ AtOppEOT like the other three ticket
abilities, and a sticker ability that costs mana waits for main 2:
a sticker is permanent, so there is no reason to buy one early.
Left alone on purpose: a sticker's traits are still rebuilt on each state
check, because the cache Forge uses for granted traits resolves SVars
against the granting static rather than the sheet. The reason is now a
comment at the site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Sticker prompts send what a remote player can receive
A remote player's prompt is serialized, and a Sticker is not, so both
choices now go over as positions with their labels rendered up front.
Also: the new UI strings in every language file, lowercase deck editor
labels to match their neighbours, and two stray blank lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address first review: sheets, Done for the Day, Wicker Picker, tests, comments
- Revealed sticker sheets list their stickers from the start; the details
pane was empty until the first sticker was placed.
- Done for the Day uses GenericChoice, which also makes the CountersPutAi
ticket special case unnecessary, so that is reverted.
- Wicker Picker is dropped: sticker kicker is a kicker variant, not a
trigger, and has its own issue.
- Tests cut from 68 to 18: the all-96 ability sticker build, the core
rules, and the AI tests that fail without their fix.
- Comments trimmed to CR references and the few non-obvious notes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stickers on merged and melded permanents (CR 123.5b/c)
A stickered card that mutates onto a permanent moves its stickers to the
merged permanent. When a merged or melded permanent leaves for another
public zone, its owner chooses which card keeps the stickers, and the
effects move with them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name stickers follow a name that changes underneath them
A name sticker's name was built once when placed, so a later mutate or
copy effect left the old name showing. It is rebuilt whenever the clone
states change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name stickers across transform and face-down; melded stickers (CR 123.5a, 123.6b/c)
The name is now rebuilt whenever the card's state changes, not only its
clone states, so a transformed card gets the back face's name with the
sticker, and a face-down permanent is named only by its stickers rather
than showing its real name. Both cards' stickers are on a melded
permanent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Done for the Day: the ticket-or-Treasure choice can be declined, and its trigger text is written out
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: liamiak <liamiak1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: tool4ever <therealtoolkit@hotmail.com>
* Check the lure requirement before the block cost (#11949)
mustBlockAnAttacker skips an attacker when blocking it costs something, and
again when its lure requirement is already met. The cost check ran first, and
getBlockCost walks every card in the static ability source zones and rebuilds
each one's static abilities, while the lure check only reads the attacker's own
keywords and passes for any attacker without a lure requirement. Both are
side-effect free, so running the cheap one first gives the same answer.
The loop runs per attacker for every candidate blocker, so a wide board pays
the scan for every pair. On a board of sixty distinct creatures a side, one
turn with an attack: 80189 ms to 44800 ms, and calls to
CardState.getStaticAbilities over two turns fall from 1007936214 to 362382774.
A seeded four-player commander table plays the same games two to three percent
faster.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Fix adventure quest dialogs repeating after load (#11719)
* Fix adventure quest dialogs repeating after load
* Preserve quest dialog state across platforms
---------
Co-authored-by: Codex <codex@openai.com>
* Make background transparent when selecting commander (#12084)
* Make background transparent when selecting commander
* remove comment
* Make the remote GUI protocol usable without the network stack (#12091)
* Create the server's event loops when it starts
HostedMatch.startGame and InputPassPriority.showAndWait call
FServerManager.getInstance(), so every offline human game built two
NioEventLoopGroups it never used. startServer now creates them and
stopServer tolerates a server that never started.
* Split the transport out of RemoteClientGuiGame
ProtocolGuiGame holds the protocol and delta sync against any IRemote.
RemoteClientGuiGame extends it and keeps what the network needs: the
codec tracker, Java-serialized event wrapping, bandwidth logging and
the lobby back-pointer. HostedMatch and PlayerControllerHuman check
the base class. Network behaviour is unchanged.
* Let the GUI decide how an input waits
InputSyncronizedBase now waits through IGuiGame.awaitInput. The
default awaits the latch as before. A single-threaded host, where
answers can only arrive on the waiting thread, overrides it to take
them in place.
* Keep offline games off FServerManager
HostedMatch.startGame and InputPassPriority.showAndWait called
FServerManager.getInstance() in every game. That class cannot link
without Netty, so any match loaded the network stack. The running
server now registers itself in HostingServer, which the match and
input code ask instead. AfkTimeout moves there too: its NOOP lambda
compiled into FServerManager and loaded it.
* Add in-process protocol GUI tests
A stock PlayerControllerHuman plays the AI through ProtocolGuiGame and
an in-process IRemote, first threaded, then with every answer taken on
the waiting thread. Neither path starts a server, and the in-process
test checks the game loads no Netty, codec or FServerManager class.
* Make River Song's draw ability to a static ability (#11870)
* Goblin Blastronauts (UNF) (#12039)
* Goblin Blastronauts (UNF)
* create sa copy outside the loop
* fix typo
* move check if a die roll result is valid to its own method
* remove copy since it has become unnecessary
* Fix syntax in trigger mode for card effect
* - Add FRA/FRC achievements by Marek14. (#12100)
* add sticker support to quest mode and cleanup
* Revert "prevent funny sets from creating drafts in quest mode, since they crash the game"
This reverts commit c0557e50638090fe565b3b1afb0b454e443027db.
* Add files via upload (#12099)
* Remove card-script params the effect never reads (#12111)
* Let a may-play grant cover more than one player (#12107)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Some cleanup (#12112)
* Dack Fayden, Helping Hand: only choose opponents for permanents that entered (#12113)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Don't fire menu shortcuts for keys typed into a text field (#12117)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: don't bid life on Itazura with nobody to outbid, and cap the bid otherwise (#12115)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Update WorldBackground (#12098)
Dispose chunk textures that leave the view area, reuse a single scratch pixmap per chunk build, and dispose the placeholder pixmap. Hopefully it fixes white world background caused by exhausted texture memory.
* update graphics, reduce draw calls for rectangles specially using art/card image renders
* use freetypefont generator and move counterfont generation to assetmanager
* lazy load counterfonts
* AI: don't activate abilities into lethal activation triggers (#12125)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix inverted close on RewardActor, update endCLip check
* update counter font generation, update filter
* Fix token creation for cards from an edition that is not installed (#12109)
A printing keeps its edition code when it is read from another machine:
PaperCard.readObject finds the rules by name when the edition is missing
here and leaves the code as sent. A deck that pins a custom edition
therefore puts cards into a network game whose set code the host cannot
look up, and StaticData.getCardEdition returns null for it.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Edition updates: FIN, PRM, SLD
* Add card flight/play animation (#12138)
* Add card flight/play animation
* update animation
* Draw the game when mandatory triggers loop without end (#12127)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* iOS: fix build and launch/runtime crashes on iOS 27 (#12132)
* iOS: fix simulator build and runtime crashes on iOS 27 - ios-pipeline.sh: stamp sim binary with SDK 26.0 to avoid iOS 27's
UIScene-lifecycle launch trap (workaround; real fix is UIScene adoption)
- ios-pipeline.sh: find robovm-dist/compiler in the build m2 clone
- bridge.cfg + JMisc8: bridge Collections.emptyNavigableMap (card loading hang)
- CdnUuidCache: avoid ConcurrentHashMap.newKeySet (crash entering Classic)
- ScryfallBulkDataSync: use getContentLength (missing on MobiVM)
---------
Co-authored-by: David Ayres <dwayres@Davids-Mac-Mini.local>
* AI: check Saddle and Teamwork costs by total power (#12124)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: don't pay to keep an empty "exile until leaves" permanent (#12141)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: don't reanimate creatures that would die right away (#12142)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add the capability to due Commander Draft
* Add CMR Rankings file
* Set free commander in CardEdition
* Update Card Play Options, Add leave animation (#12148)
* Add leave animation
* update overlay
* Update Animation options
* Update Adventure SettingsScene, Update CardFlightOverlay
* minor optimization
* update prefs
* localization
* Edition updates: NAU, PF26, SLD, UNK, YFRA
* Add Coin Flip Animation (#12155)
* Add Coin Toss
* unused import
* update to use existing event
* try to prevent writeMatchPreferences on lifecycleClosing
* CharmAi: fix evaluating modes with different X (#12156)
* Old Border Shandalar: Add Svyelunite Temple quest (#12145)
- Add the Svyelunite Temple dungeon in the blue biome with a
faction-choice quest between Vodalians and Homarids
- Add 35 Fallen Empires enemies with their quest decks, sprites
and named boss reward cards
- Add quest and ally dialogue for all NPCs; homarid-kin lines use
clicks, whirrs and gestures
- Grant six Fallen Empires boosters with both completion rewards
alongside gold, shards and faction cards
- Add the temple icon to the shared buildingsbosses atlas and
reference it from the point of interest
- Face the Floodwater Homarid left via native Left animation
aliases so it greets players approaching the dam
* Adventure: repair two broken map dialogs (#12128)
Co-authored-by: liamiak <liamiak1@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Use a required multi-mana source first (#12119)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Count additional triggers when the waiting triggers are collected (#12106)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Refresh entry snapshots once a simultaneous batch is on the battlefield (#12121)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cards): correct three scripts that disagree with the printed card (#12153)
* Demonic Pact: the discard mode targets an opponent (#12160)
* Update wiccan_rising_magician.txt (#12161)
* Move coin textures to Assets
* Quest: selecting every color no longer generates an empty starting pool
BoosterUtils.populateBalancedFilters uses the NON-selected colors as the
repetition multiplier for the preferred-color filters (to keep them
proportionate to the unselected colors its else branch mixes in). With
every color selected, otherColors is empty, so neither branch builds a
filter and generateCards silently produces nothing: a new quest whose
whole card pool is the automatic basic-land grant and a deck editor with
no cards to offer. Reproduced headlessly with BALANCED distribution and
all six colors selected; any five of the six generates the full pool.
Both clients, any world.
Fix: floor the multiplier at one pass.
QuestStartingPoolTest pins the all-colors case (fails before the fix,
passes after) with two-color and no-preference controls.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Allocate network test ports instead of hard coding them (#12165)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Edition updates: SLD, SLP
* FRA: fix empower jace token image
* Review: drop the test class and the long comment
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Mana refund: do not reverse the payment of a mana ability that itself could not be reversed (#12162)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Fix hotseat draw offer dialog (#12173)
Co-authored-by: Damir Tomic <viperus.oblivion@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: pay for mana abilities that cost mana (Signets, filter lands) (#12149)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Dice Animation (#12167)
* Dice Animation
* update GameEventRollDie
* update DiceEventBridge
* update to support multi dice and support d4 to d20
* add texture
* update dice shape
* use forge graphics batch
* update texture, lighting, update coin
* update prefs
* localization
* update RewradActor hideTooltip
* commented out failing test, this halts build process
@Blackvipe99 update/fix if needed
* update CardFlight animation
* Booster slots draw from MyRandom, so a seeded pack is reproducible (#12184)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix typos in Reality Fracture edition names (#12185)
* AI: pay for a mana ability with a mana cost that is activated on its own (#12179)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Some cleanup (#12186)
* Simultaneous entry: don't let permanents entering together change how each other enters (#12171)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add FlipOntoBattlefieldAnimation (#12183)
* add FlipOntoBattlefieldAnimation
* update
* update fields
* update animation, fix tapped angle
* Fix netplay host bugs (#12181)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* update Coin3D, CoinFlipOverlay, Dice3D params
* Player must attack: don't count on an attack that has a cost (#12178)
Trove of Temptation and Seeker of Slaanesh make a player attack with at
least one creature if able. That requirement was given to one attack
without looking at its cost. With Propaganda out, the attack on the
player was then skipped for its cost and the free attack on a
planeswalker because nothing asked for it, so not attacking was accepted.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Old Border Shandalar: Fix Svyelunite Temple quest offers and rewards (#12177)
* Fix lethalDamage update
* add comment
* fix soft lock (#12191)
* fix soft lock
* override fOption
* trigger button
* fix clashing of boss dialog and coinflip (#12192)
* fix clashing of boss dialog and coinflip
* wait for transition to finish
* TRK/TRC cards from New York Comic Con (#12187)
* Fix seven card-script param bugs found porting to Crucible (#12193)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix logic for two hit cards
* increase hit chance for FlipOntoBattlefieldEffect
* fix logic for desktop swing since its not possible with the corner hit and don't have animation.
* Fix stale boss dialogue portraits between Adventure duels (#12176)
* Update net deck archive lists
* Update net deck archive lists
* Edition updates: SLD, TRC, TRK
* Rework GameEventForwarder to batch events at a game loop checkpoint (#12189)
Batches of events were cut by a timer and a size limit, so a cut could fall anywhere. A spell could be cast and resolve in one batch, and a card could leave play in one batch with its move in the next.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix specialized downloading base image
* make CoinFlipOverlay auto close
* Simultaneous entry: what sees a permanent enter is decided after everything entering with it has (#12188)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* The Darkhold (MDD) and other MDD spoilers (#12202)
* Show the waiting prompt after blockers are declared (#12203)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Edition updates: MDC, MDD, PMEI
* Eureka, Hypergenesis: end the process when no card was actually put onto the battlefield (#12204)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Set default section to Commander
* fix(cards): AddType$ comma lists and the g_1_1_elf token type (#12212)
* Some cleanup (#12213)
* Update CardZoom layout, MatchScreen layout and CommandZone (#12199)
* Update CardZoom landscape layout
* update controls
* add nestable view for specialized/mutate, update controls, update ease-in/out.
* update default layout matchscreen, add support magnifier on drag
* update commandZone
* restore portrait
* add hide phase stop for landscape mode (default off)
* update zonetab size
* move touchdrag to VCardDisplayArea
* update cardzoom close logic
* minor optimization
* Add deck statistics (#12221)
* Add deck statistics
* unused import
* update panel and border colors for adventure
* move DeckStatistics
* add blocked gate check for castle
* update deckstats icon
* fix clipping
* YSOS: Summitfest Closing Ceremony (#12217)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Compose the "Waiting for" prompt client-side (#12205)
The host no longer builds a remote player's "Waiting for X" prompt on a timer thread. It forwards awaitNextInput as a notice, and the client composes the prompt, buttons and elapsed timer from its own game view and yield state. The host sends the same notice where it used to repaint the yield prompt, because the client shows an active yield as part of its waiting prompt. The client cancels a pending wait when the next prompt arrives. In the protocol, awaitNextInput replaces showWaitingTimer.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Network logging changes for readability and size (#12224)
* Count delta packets with a negative saving in the network log analyzer
The packet pattern only matched a non-negative Savings figure, so a
packet larger than the full state was skipped.
A delta can be larger than the full state on a client's first sync.
The events sent with it cost more than the full-state figure allows
for them, because a delta serializes each event on its own while the
full-state figure measures them together as one list.
The effect on reports is marginal. In a 10-game run it dropped 11 of
10,012 packets and 2.5% of delta bytes, and the overall savings figure
moved from 97.1% to 97.0%.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add UndoManager, fix Undo (#12226)
* Update GameMemento
* Update UndoManager
* Fix animation when using undo
* StickerSheet: fix toList (#12232)
* Update undo for netplay and request undo each phase
* Nashi, Illusion Gadgeteer is rebalanced
* Edition updates: YFRA
* Edition updates: YFRA (reorder)
* Quick Gauntlet: name every generated opponent deck (#12233)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Adventure: fix Landscape Sketchbooks whose names match no edition (#12227)
Co-authored-by: liamiak <liamiak1@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Agetian <stavdev@mail.ru>
* AI: don't goad its own creatures (#12239)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Vengeance and Hela, Mistress of Darkness (MDD) (#12216)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: judge dungeon rooms as triggers when choosing where to venture (#12237)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Undo Mode prefs
* Update Dice texture
* Fixes to lobby, deck generation, and netplay (#12230)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Xanathar, Guild Kingpin; Whispersteel Dagger: match the other player's cards by owner (#12247)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Reckless Detective: only pump when the cost was paid (UnlessResolveSubs$ WhenPaid) (#12245)
---------
Co-authored-by: Rensen3 <renesmdt@gmail.com>
Co-authored-by: kevlahnota <anthonycalosa@gmail.com>
Co-authored-by: MostCromulent <201167372+MostCromulent@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Darthseid <jared.mcvey@yahoo.com>
Co-authored-by: Paul Hammerton <18243520+paulsnoops@users.noreply.github.com>
Co-authored-by: Madwand99 <Madwand99@gmail.com>
Co-authored-by: Paul Hammerton <paulhammerton1@hotmail.co.uk>
Co-authored-by: Chris H <zenchristo@gmail.com>
Co-authored-by: Rensen3 <127029481+Rensen3@users.noreply.github.com>
Co-authored-by: Alexey <121340072+alex-dmt@users.noreply.github.com>
Co-authored-by: Jetz72 <Jetz722@gmail.com>
Co-authored-by: tool4ever <therealtoolkit@hotmail.com>
Co-authored-by: Churrufli <churruflis@gmail.com>
Co-authored-by: Hans Mackowiak <hanmac@gmx.de>
Co-authored-by: jczastkiewicz <jc@archlab.pl>
Co-authored-by: Fulgur14 <54345051+Fulgur14@users.noreply.github.com>
Co-authored-by: Simisays <67333662+Simisays@users.noreply.github.com>
Co-authored-by: Leandro Doctors <ldoctors@gmail.com>
Co-authored-by: Nerdslayer <49656879+Nerdslayer@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Blackvipe99 <byron.gibson99@gmail.com>
Co-authored-by: GitHub Actions <actions@github.com>
Co-authored-by: DamirTomic <viperus.oblivion@gmail.com>
Co-authored-by: ldeluigi <44567586+ldeluigi@users.noreply.github.com>
Co-authored-by: Agetian <stavdev@mail.ru>
Co-authored-by: liamiak <liamiak@yahoo.com>
Co-authored-by: liamiak <liamiak1@gmail.com>
Co-authored-by: Cagliostro <286431257+khaliostr@users.noreply.github.com>
Co-authored-by: BigCrunch22 <59816726+BigCrunch22@users.noreply.github.com>
Co-authored-by: Nikolai Weidt <aramus92@gmail.com>
Co-authored-by: Justin <jalirkan@gmail.com>
Co-authored-by: Abishai James <18426277+haksanlulz@users.noreply.github.com>
Co-authored-by: David Ayres <101526453+Dalamar32@users.noreply.github.com>
Co-authored-by: David Ayres <dwayres@Davids-Mac-Mini.local>
Co-authored-by: Vanja <56617195+vanja-ivancevic@users.noreply.github.com>
Co-authored-by: Nikolai Weidt <nw@uni-kassel.de>
Co-authored-by: Shaun Adams <shaun.adams@volunteers.acasi.info>
Co-authored-by: tyrathalis <tmwise97@yahoo.com>
Co-authored-by: Douglas Bergentz <carl.douglas.bergentz@gmail.com>
Co-authored-by: dvirbuc <dvirbuc@gmail.com>
Co-authored-by: Shaun Adams <s.adams@f5.com>
delebedev
added a commit
to delebedev/forge
that referenced
this pull request
Oct 16, 2026
* add getLabelKey for Reward enum
* fix fbufferedImage, refactor audit to work on android device (it will take some minutes to finish on android)
* fix planar genesis (#12028)
* edit
* Code cleanup
* Loot the Anomaly
* Add FRA rankings for card collection
Added rankings for various cards in the FRA set.
* Add percentage when auditing cards, check scissor on FBufferedImage
* Add guard when auditing cards, fix dark sprites frambuffer
* typo
* resolve conflicts
* Update budding_insurgent.txt
* Update living_library.txt
* Update venser_fervent_forger.txt
* Update elvish_elegy.txt
* Fix Sanctum Lurker's IgnorePlaneswalkerZeroLoyaltyRule param name (#12035)
* Update flatman.txt
* Update graphics, utils, fskinfont and apply reusable vars (#12034)
* Update graphics, utils, fskinfont and apply reusable vars
* rename var
* replace FSkinFont TextBounds legacy code with GlyphLayout
* update TextBounds
* update TextRenderer, FLabel, FButton, CardRenderer, fix watermark
* Command the Stage (FRA) (#11958)
Co-authored-by: tool4EvEr <tool4EvEr@>
* Update Player.java
fix getMonarchSet
* Update FRA.rnk
* remove-function
* Fix two card-script param bugs found porting to Crucible
the_disciple_of_vess.txt: deleted a trailing line
"(There are currently thirteen. Perhaps look up the list and roll a D20?)\nPERSON" -- a leftover authoring note, not a
script line. Forge's own FileSection parser rejects it as an unknown key.
ginger_queen_of_sweets.txt: renamed TokenOwner$ to Controller$ on its
DB$ CopyPermanent line. CopyPermanentEffect.java reads
sa.getParam("Controller") (resolve(), line ~136), never "TokenOwner" --
the param never reached the effect. Renamed to match the corpus's own
convention for CopyPermanent's controller param (5 other cards).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsS1N7fkNqkDRYXM3ZskjZ
* fix(card): address PR review comments on two-param-name-bugs
the_disciple_of_vess.txt: restore the real reminder text (verified against
Scryfall's oracle_text) instead of dropping it entirely; only the bogus
"PERSON" leftover was junk.
ginger_queen_of_sweets.txt: drop Controller$ You entirely per Hanmac's
review comment. CopyPermanentEffect.java:134-142 defaults controllers to
the activator when Controller is unset, which is the same player for this
triggered ability, so the param was redundant.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* guard FSkinFont glyph computation and add fallback
* update extra check
* Add Match Duration to GameLog (#12044)
* Add Match Duration to GameLog
* add space
* fix match duration
* Load Commander precons and genetic AI decks read-only (#12045)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Update Missing-Cards-in-Forge.md
* Remove Trample ability from A.I.M. Scientists
* update LibGDXImageFetcher, ImageCache, RewardActor (#12054)
* update LibGDXImageFetcher, ImageCache, RewardActor
* Deferred rendering
* update tooltips
* disable load card lazily on mobile
* forge-gui: characterize PlayerControllerHuman.assignCombatDamage (#10827)
Add a test harness to forge-gui and characterization tests for the
first engine-to-UI seam in PlayerControllerHuman, ahead of moving UI
logic out of the controller. No production code changes.
* Harness: TestNG, Mockito and AssertJ as test-scoped dependencies
(same TestNG and Mockito versions as forge-game and
forge-gui-desktop), surefire picking up *Should classes, and
Mockito's subclass mock maker.
* FakeGuiGame: a strict stand-in for IGuiGame. Any prompt (non-void
call) fails the test unless scripted; notifications (void calls) are
accepted. Prompts are scripted and recorded through typed methods, so
the fake does not have to track every change to IGuiGame.
* HumanControlledGame: a two-player game with human controllers, built
on a real Game without FModel, the card database or preferences.
Cards are created from inline script text. Runs in about two seconds.
* PlayerControllerHumanShould: pins when assignCombatDamage prompts the
player (trample with a defender, several blockers) and when it decides
alone (one blocker, no blockers, trample without a defender), and how
the answer is mapped back to engine cards (null key for the defender,
non-blocker entries dropped, null answer passed through).
Verified by mutating the trample check in assignCombatDamage: 5 of the
8 tests fail.
Validate from the command line:
mvn test -pl forge-gui --also-make --no-transfer-progress
Code written with AI support.
* Fix card script errors found by the card-script linter (#12048)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Move zone display decisions from host to client (#12023)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Edition updates: SLD
* IGuiGame cleanup (#12056)
* Fix default Slingshot Bandit deck (#11735)
Co-authored-by: Codex <codex@openai.com>
* Replace weak Master Blue Wizard deck with tempo list (#11732)
Co-authored-by: Codex <codex@openai.com>
* fixes Killer Cosplay (#12062)
* update FrameRate, ScreenUtil capture (#12060)
* update FrameRate, ScreenUtil capture
* remove redundant vars
* Update captured frame
* Update stinging_vitriol.txt
Closes #12070
* Update match transition screen (#12068)
* Update match transition screen
* update ui background
* update event transition bg
* update Reward type for ArenaScene and EventScene
* update RewardActor
* update DuelScene, use lastScreenTexture on exit
* Fix hand reveal not opening when the revealed card is a copy (#12065)
Co-authored-by: tool4ever <tool4ever@>
* [maven-release-plugin] prepare release forge-2.0.15
* [maven-release-plugin] prepare for next development iteration
* Restore POM files for preparation of next release
* Restoring pom.xml
* fix preview spam
* Update flare_of_fortitude.txt
* Migrate upcoming card scripts
* fix radius, reduce graphics draw call (#12073)
* Some cleanup (#12075)
* Pick the commander in the Commander lobby (#12052)
A Commander deck can now be led by another card for one match, without
copying or editing the deck. Each human or AI player in the lobby gets a
"Commander" button that opens a picker with card preview, listing:
1. the deck's default commander(s), and each half of a default pair
2. alternates suggested by the new AltCommanders= deck metadata
3. every other card in the deck that is a legal commander for it
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* add lastpreview check, update booster reward type
* Help AI choose better for Memories Returning
* Fix ordering (again) when undo casting from library
* Small fix for conditional
* Batch TargetingOverlay Arrows for faster FPS (#12080)
* Fix GameState parsing of token images causing malformed keys on ImageFetcher, update ImageCache
* Update TargetingOverlay, update comment (#12082)
* Update adventure data backup (#12088)
* Update adventure data backup
* add selector box and check zip
* update layout and text wrap
* Update restorebackup file check
simple check for the backup
* Add puzzle PS_FRA1 - Possibility Storm - Reality Fracture #01 (#12092)
* - Add puzzles PS_HOB2 and PS_HOB3.
* - Fix Zidane event in Planar Conquest FIN plane.
* - Add puzzle PS_FRA1
* Edition updates: SLD, UNK
* Unfinity stickers and sticker sheets (#12047)
* Unfinity sticker sheets: data, deck section and pregame choice
Adds the 48 Unfinity sticker sheets as a new Stickers card type and deck
section, and the CR 123.2a pregame step that reveals three of them.
No card can place a sticker yet; this is the foundation that phase 2 builds
the sticker registry and the place-a-sticker effect on.
Each sheet script carries its ten stickers: three name, three art, two
ability and two power/toughness. Ability stickers record their ticket cost
and printed text; the Forge ability itself follows in a later phase, and a
sticker with no Ability$ is not offered.
Sheets live in the [eternal] section of the new SUNF edition so the funny-card
filter in StaticData keeps them - the eternal-legal Unfinity cards need sticker
sheets to function.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Show the sticker sheets zone in the desktop client
Adds the deck editor section for sticker sheets and the in-game zone, so a
deck can be built with them and the three revealed sheets can be seen.
The zone icon FSkinProp.IMG_ZONE_STICKER was already defined and unused.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Assert the deck editor's sticker sheet pool is populated
The editor only shows its sticker sheet section when the pool is non-empty,
so an empty pool would drop the section with no other symptom.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Read the stickers off a sticker sheet
Adds Sticker, StickerKind and StickerSheet: a sheet names its stickers in a
StickerSheet keyword and defines each in an SVar, the way a dungeon names its
rooms, so AbilityFactory.getMapParams reads them.
A sticker is identified by its sheet and slot rather than its text, since
CR 123.3a makes two stickers distinct even when they read the same.
The sheet data tests now go through this parser instead of matching SVar
strings, and check the ticket costs and name words as parsed values.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Put stickers on cards, and keep them across public zones
A card holds the stickers on it with the two things placing one decides: the
timestamp, and for a name sticker the word position from CR 123.6b.
Name stickers are not applied one at a time. Every name sticker on a card
contributes to one name, so the card rebuilds the whole name from its own name
plus each sticker in timestamp order (CR 123.6c), clamping a position that is
now past the end of the name.
GameAction.changeZone carries stickers to the new object only when the
destination is a public zone, which is CR 123.5. Removing that gate fails
StickerApplyTest.
Ability stickers still grant nothing - their sheet scripts carry no Forge
ability yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Place a sticker, and the first card that does
Adds the PutSticker effect, the two choices placing a sticker needs on
PlayerController, and _____ Goblin as the card that exercises them.
The pool a player chooses from is derived rather than tracked: a sticker is
available when it is on one of their revealed sheets and is on no object they
own. That makes CR 123.5 fall out for free - a sticker on a card that moves to
a hidden zone is on nothing, so it is choosable again - with no bookkeeping to
get wrong.
Ticket costs gate the choice as well as being paid, per CR 123.3c, and an
ability sticker with no Forge ability yet is never offered.
After placing a name sticker the effect records StickerWord, StickerLetters
and StickerUniqueVowels on the ability, so a sub-ability reads them the way a
die roll's sub-abilities read its result.
DeckRecognizer's card-name pattern had no underscore, so a deck list naming
_____ Goblin would not parse. That is what DeckRecognizerTest caught, and the
pattern now allows it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* File _____ Goblin under b, following +2 Mace and 1996 World Champion
Forge files a card whose name starts with a non-letter under the first letter
of how the name is spoken: "+2 Mace" sits in p, "1996 World Champion" in n.
A blank reads "blank", which is also the comprehensive rules' own wording for
it, so the five other cards in the set that begin with blanks go here too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Three more name sticker cards
_____ Bird Gets the Worm, _____-o-saurus and Wizards of the _____ all read the
unique vowels of the sticker they just placed, so they need nothing beyond the
PutSticker effect.
All three begin with blanks or would file under a blank, so they sit in
cardsfolder/b alongside _____ Goblin.
Note for the test: DigAi declines any dig before main 2 that names no
DestinationZone, so Wizards of the _____ is asked for in main 2. That also
means the AI passes up the free name sticker in main 1, since declining the
trigger declines the whole thing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* A sticker-placed trigger, a sticker count, and four more cards
TriggerType.StickerPlaced fires from PutStickerEffect with the object, the
player and the sticker's kind. StickerKind takes a comma separated list, so a
card reading "if it's an art sticker, instead ..." is two triggers - one on
Art, one on the rest - rather than a trigger-level condition, which cannot
reach the triggering object anyway.
Count$CardStickers counts stickers on a card, by kind and, for the cards that
care about the letters on their name stickers, by NameMinLetters,
NameMaxLetters and NameLetter.
The card property is stickeredWith, not withStickerKind: the broad
"with<keyword>" branch in CardProperty matches anything starting with "with"
and would have swallowed it, leaving the new branch unreachable.
Cards: Goblin Airbrusher, Wee Champion, _____ _____ _____ Trespasser and
Sword-Swallowing Seraph.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Four more sticker cards
Baaallerina and A Good Day to Pie, plus Make a _____ Splash and
_____ Balls of Fire, which count a single letter across the name stickers on
themselves through Count$CardStickers.NameLetter.
A Good Day to Pie triggers from the graveyard, so it is the first user of
StickerPlaced with a TriggerZones other than the battlefield.
Twelve of the forty-six in-scope cards now work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Twelve more cards, and choose rather than target
These cards say "a nonland permanent you own", not "target", so PutSticker now
takes Choices$ and only falls back to ValidTgts$ for the few abilities that
really do target. Baaallerina and Sword-Swallowing Seraph move over too.
PutStickerAi gained target selection, which it never had: it picks its own
best legal permanent, preferring one that is not stickered yet.
The twelve cards are generated from the Scryfall record so their mana cost,
type line and power/toughness cannot be mistyped - three of five hand-typed
P/T values were wrong before the generator replaced them.
Twenty-four of the forty-six in-scope cards now work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Five more cards, including the first sticker on a graveyard card
Proficient Pyrodancer, Unlawful Entry, Bioluminary, Prize Wall and Scampire.
Scampire is the first user of ChoiceZone$ Graveyard, and the first card where
a sticker goes on something that is not a permanent - a creature card in the
graveyard, which its activated ability then looks for by the stickered
property. A test covers that path.
Its delayed exile uses Animate's AtEOT$ Exile, the idiom Ach! Hans, Run! uses,
rather than the hand-rolled delayed trigger it started as.
Twenty-nine of the forty-six in-scope cards now work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Ability stickers grant their ability
CR 123.7: placing an ability sticker now grants the object what is printed on
it. Keywords are granted directly; anything with its own script is built from
an SVar on the sheet the sticker came from, so a written ability can refer to
other SVars there.
Twenty-seven of the ninety-six ability stickers are keyword-only and are
generated with the keywords they grant. The other sixty-nine need a written
ability and stay unofferable until they have one, which is what isImplemented
already gated on.
This makes two of the ten stickers on a sheet live for the first time, so it
changes what every card that places a sticker can actually do, not just the
three cards that ask for ability stickers by name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Ability stickers can grant a static ability
CR 123.7 gives the object whatever is printed on the sticker, and six of the
ninety-six ability stickers print a static ability - Hellbent, Threshold,
Metalcraft, ward, a protection, and casting from the graveyard for 2 life.
They had nowhere to go: grantAbility passed null for the statics slot of
addChangedCardTraits, so a sticker could only ever grant keywords, activated
abilities and triggers.
The granted traits are now built against the sheet's card state rather than
the sheet card. That is the wiring a static's AddAbility already uses, and it
matters for the same reason: an ability's own SVars are looked up through its
card state when it resolves, and the card state was the stickered card, which
has none of them. Only SVars read at parse time worked before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Count generic mana, a card's types, and its creature types
Three sticker abilities ask for numbers Forge could not produce. Each is a
one-case addition next to the counts it sits with:
Count$TriggeredManaCostGeneric - the generic mana in the cost of the spell
that triggered, for Misunderstood Trapeze Elf's sticker. It goes next to
TriggeredManaCostDevotion, which walks the same cost for coloured pips.
Count$CardTypeCount - supertypes plus card types plus subtypes, for Unique
Charmed Pants' sticker.
numCreatureTypes - a card property, so Notorious Sliver War's sticker can
grant protection from creatures with two or more creature types. It joins
numTypes, which counts only the core types. Changelings are handled by
CardType.getCreatureTypes, which already expands them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* The sixty-nine written ability stickers
Phase 4. Every ability sticker on every sheet now grants what it prints, so
all ten stickers on a sheet are live and any card that places an ability
sticker can reach all ninety-six.
The shapes are thirty-nine triggers, twenty-four activated abilities and six
statics, and almost every one was transcribed from an existing Forge card
with the same ability rather than written fresh. Helper SVars are prefixed
with their slot, since a sheet holds two ability stickers and they share a
namespace.
A sticker's script is only parsed when the sticker is placed, so a sheet
carrying a broken one loads without complaint and falls over mid-game.
StickerAbilityTest therefore places all ninety-six and checks each grants
something, and covers the cases the parse cannot: the three conditional
statics turning on and off, the two new counts, an SVar read off the sheet
from inside a card filter, and the graveyard-cast sticker, which only does
anything in the zone the sticker was carried into.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Eight more cards that hand out tickets and stickers
Finishing Move, Robo-Pinata, Command Performance, Costume Shop, Done for the
Day, Park Bleater, Lineprancers and Tusk and Whiskers - 37 of the 46 in-scope
Unfinity cards now work. None of them needed anything new: the ticket counter,
PutSticker's Kind$ and Choices$, the StickerPlaced trigger and the stickeredWith
property were all already there, and Costume Shop is an ordinary attraction
script whose six printings the edition file already lists.
The nine left all want a mechanism that does not exist yet - stickers granting
their abilities to a different object, a sticker distributed across two
permanents, a base power from stickers' total power, a sticker kicker keyword,
choosing a letter, or an enchantment that becomes an Aura mid-resolution.
StickerTicketCardTest builds each of the eight, since a script's abilities are
parsed when the card is built, and covers what the harness can drive: the
kind restriction on Lineprancers' sticker, Park Bleater paying out only for
other creatures, and Tusk and Whiskers rewarding the creature an ability
sticker went on. Dies and end step triggers do not fire in this harness even
for plain Forge cards, so Robo-Pinata is checked for its two modes instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Wolf in _____ Clothing and Fight the _____ Fight
Both say "you may put a name sticker on it. When you do, ..." - a reflexive
trigger that has to read the sticker that was just placed, which is why they
waited. DB$ ImmediateTrigger already does this for a die roll, and
PutStickerEffect already recorded the word, its letters and its unique vowels,
so the two cards are the same shape as Ancient Bronze Dragon: the sticker's
value rides along in RememberSVarAmount and the trigger's targets are chosen
after the sticker exists.
PutStickerEffect now also records StickersPlaced, and clears all four values
at the start of each resolution. Without it, "when you do" had nothing to
condition on, and a second resolution of the same ability would have read the
first one's word - the ability object is reused.
39 of the 46 in-scope cards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Ambassador Blorpityblorpboop and Roxi
Roxi needed nothing new: her power is two existing counts added together, one
over the battlefield and one over the graveyard, both keyed on the
stickeredWith property, and "distribute up to two art stickers" is two
optional PutSticker sub-abilities restricted to Kind$ Art.
The Ambassador needed one thing Forge could not do: add up what is printed on
the power and toughness stickers a player has in play. Count$StickerPower and
Count$StickerToughness take a card filter the way Count$Valid does, so the
card asks for Permanent.YouCtrl and the opponent's stickers stay theirs.
41 of the 46 in-scope cards. The five left are Pin Collection and Clandestine
Chameleon (an object gaining the abilities of stickers on a different object),
Wicker Picker (a sticker kicker keyword), _____ _____ Rocketship (choosing a
letter) and Last Voyage of the _____ (an enchantment that becomes an Aura).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Pin Collection and Clandestine Chameleon
Both say an object has the abilities of ability stickers that are on a
different object, which is one mechanism and so one commit. Statics take
GainsStickerAbilitiesOf$ / GainsStickerAbilitiesOfDefined$, reusing the card
list and the zone parameter GainsAbilitiesOf already has. It cannot reuse
GainsAbilitiesOf itself: that copies activated abilities only, and a sticker
can print a keyword, a trigger or a static just as easily.
AppliedSticker now builds what a sticker grants without applying it, so the
static hands the affected card exactly what the sticker would have given the
card it sits on.
PutSticker also takes MaxTickets$ and NoTicketCost$, for Pin Collection's
"an ability sticker with ticket cost X or less ... without paying that
sticker's ticket cost". The cap has to reach the offer, not just the payment,
so the pool is built against what the card allows rather than what the player
has: StickerSheet.getAvailableStickers takes that budget as an argument.
43 of the 46 in-scope cards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* The last three cards: all 46 in scope now work
_____ _____ Rocketship needed a letter chosen and then the name stickers that
begin with it. The chooser is DB$ ChooseType with the alphabet as its
ValidTypes, which stores the letter where the count can read it; the count is
a new CardStickers.NameStartsWith, since the existing NameLetter counts how
often a letter appears rather than what a word begins with.
Wicker Picker's sticker kicker is a kicker variant, so it grants Kicker:1 to
the creature spells you own - the static reaches them in hand, where casting
reads its costs - and pays out from a SpellCast trigger gated on Spell.Kicked.
Last Voyage of the _____ is Animate Dead's shape: reanimate, animate into an
Aura with enchant creature, attach, and leave behind a delayed trigger holding
the creature's LKI so it can be sacrificed when the Aura goes. The only new
part is the name sticker placed before any of that happens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Sticker sheets in the mobile client
The mobile deck editor already offered the section - GameType hands it the same
supplemental sections the desktop editor uses - but nothing downstream knew
what to do with it, so it fell through to the "unsupported extra deck section"
branch. It now has its page config, its catalog filter, its card pool, its
add/remove labels and tap-to-eject, like attractions and contraptions.
In a match, the sticker sheets zone joins the extra zones dropdown, which is
all it needed: the zone is already tracked, so the view fills itself.
Choosing a sticker needed no mobile code at all, since the prompt goes through
the shared dialog. Two things were wrong with it for both platforms, and are
fixed here: an optional sticker could not be declined, because the prompt
demanded exactly one choice, and the list showed nothing about what a sticker
costs, which is usually the whole decision.
The mobile UI has no test harness. What guards it is that the three things
both editors build their tab from - the section being offered, an icon for it
and a tracked zone - are asserted in StickerSheetTest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Clean up the sticker code after review
Four review passes over the branch - reuse, simplification, efficiency and
altitude - and this is what they found. No behaviour changes.
Reuse of what Forge already has:
- AppliedSticker now implements PerpetualInterface, which it had been
re-declaring method for method, and hands back a CardTraitChanges instead of
filling four out-parameters. applyEffect is now the same line
PerpetualAbilities uses.
- Count$CardTypeCount is gone: handlePaid's AllTypes is the same sum, and
Embiggen already scripts this very Unfinity wording that way.
- Count$StickerPower/StickerToughness move into handlePaid, so the card writes
Count$Valid Permanent.YouCtrl$StickerPower and inherits the zone and
last-state handling every other Count$Valid has.
- The sheet setup uses Aggregates.random rather than shuffling a list, and so
builds three cards instead of every sheet in the deck.
- CardPredicates.isOwner, and the ticket cap constant now lives once, in
DeckFormat, where the deck rule that shares it already was.
Simplification:
- The letters and vowels of a sticker's word were written three times in three
modules; they are now on Sticker, which owns the word.
- nonStickered is gone - Forge negates any property with a leading !, which is
what 300-odd scripts already do, and nothing used it.
- clearStickers, StickerWord and StickerLetters had no callers or readers.
- chooseStickerNamePosition no longer takes a word count the caller derives and
the implementation recomputes, and picks a position rather than recovering
one with indexOf.
- countStickers had been inserted between xCount's javadoc and xCount.
Efficiency:
- The keyword pass through a sticker was parsing its abilities, triggers and
statics only to discard them, on every state check.
- The list of sticker abilities was allocated for every static ability in every
game, sticker or not.
- A sheet's stickers are read from its script once and kept on the card; the AI
asks for them every time it weighs placing one, and now stops at the first
available sticker instead of building the whole list.
- Looking for a player's placed stickers walks the five zones a sticker can
survive in rather than all seventeen.
One thing found and fixed that was not a cleanup: Card.setStickers aliased the
old card's list instead of copying it, so adding a sticker after a zone change
would have mutated both cards' lists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* The AI hints the cards were missing
Two conventions the branch had skipped.
HasAttackEffect tells AiAttackController a creature is worth attacking with for
something other than its damage; 762 cards set it. _____ _____ Rocketship sets
it now, and - the part that needed code - so does any creature a sticker grants
an attack trigger to, which is thirteen of the ninety-six ability stickers. A
static that grants a trigger carries the SVar across with AddSVar$; a sticker
now does the same thing when what it grants is an attack trigger, on both the
path that puts it on the card it sits on and the one that hands it to another
object.
DeckHas tells deck generation what a card does that other cards care about.
Fourteen of the forty-six had something worth declaring - counters, tokens,
life gain, mana, sacrifice, graveyard - and each line is derived from what the
script actually does rather than from the card's prose. Nothing is claimed for
a ticket counter, which goes on a player and has no synergy to offer, and
nothing for a dies trigger, which mentions the graveyard without caring about
it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Playtest fixes: six of seven
- The AI activated Park Bleater with nothing to sticker. It asked whether a
sticker was available but never whether the ability had anything to put one
on; an ability that chooses rather than targets can have an empty pool.
- The stack read "puts a sticker on Park Bleater" for an ability that does not
know what it will be on until it resolves. It now says what it will choose
from.
- "Choose a sticker to put on %s" never filled in the name: Forge formats with
{0}, not %s.
- Three art stickers read identically, so a player choosing between them had
nothing to go on. They are numbered by the slot they came from now; they are
still interchangeable (CR 123.9), but at least the list says so.
- The ticket cost went to the front of each choice, the way the sheets print it.
- The AI chose a letter for _____ _____ Rocketship by taking the first of the
alphabet, because nothing told it what a letter was worth. It now picks the
one the most name stickers on the card begin with.
Two tests, for the two that are behaviour rather than wording. Worth knowing
for anything similar: AITest's addCard stamps turn-in-zone on the current turn,
so on turn one everything counts as having just entered - the test has to cross
a turn boundary before "entered this turn" means anything.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* A name sticker fills the blank it was printed for
CR 123.6a: a blank line is not a word of a card's name. So it never counts
towards a sticker's position, and a card printed with one has somewhere the
sticker goes - that is what the blank is for. Each name sticker now fills the
leftmost blank still empty, and only once there are none left does it go after
the number of words chosen when it was placed.
_____ _____ Rocketship with two name stickers reads "Eldrazi Guacamole
Rocketship" rather than keeping both blanks in front of them. A blank nobody
has stickered stays as it is, so the card reads as printed in hand, and a blank
inside a word works the same way: _____-o-saurus becomes Eldrazi-o-saurus.
The prompt that asks where a word goes is skipped when the word is going in a
blank, because there is nothing to choose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Playtest round two
Wee Champion was 1/1 and Goblin Airbrusher 1/1; they are printed 0/1 and 2/1.
Both are from the batch I typed by hand before switching to generating card
data from the printed record. Finding one meant checking all of them, so every
card and sheet this branch adds was compared against Scryfall - name, mana
cost, power, toughness and oracle text. Those two power/toughness values were
the only errors in ninety-four cards. A test had encoded the wrong one, which
is how it survived until someone played the card.
An art sticker changes nothing else a player can see (CR 123.9), so two
creatures that differ only by one were indistinguishable. The details pane now
lists what is on a card, and a sticker sheet lists which of its stickers have
been taken - both tracked the way attraction lights already are.
The AI declined every mode of Done for the Day and took nothing, because
CharmAi only takes an optional mode its own AI wants and nothing in Forge knew
what a ticket counter was worth. A ticket has no downside and nothing to aim,
so the AI now always accepts one.
Not fixed, and not ours: the AI blocked the creature with no combat trigger
over the one that pays its controller tickets. HasCombatEffect is read by the
attack controller only - the block controller does not consult it - so making
that better is a change to Forge's blocking AI, not to these cards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Score what a sticker is worth before choosing where it goes
The AI put a 4/2 power and toughness sticker on a Pin Collection. Two
separate choices were wrong.
None of these cards target - they say "a nonland permanent you own" - so
the object arrives through chooseSingleCard, which PutStickerAi never
overrode. The base class returns the first card in the pool, which is
battlefield order: the AI's oldest permanent, whatever it is.
chooseSticker then scored every power and toughness sticker above every
other kind, against net power and toughness, which a noncreature has
none of. On a creature it was just as happy to shrink one, since a
sticker sets power and toughness rather than adding to them.
Both choices now score the same (sticker, object) pairs, in the units
evaluateCreature counts in, and a placement worth nothing is declined
where the card allows it. The ticket term only exists to stop a
placement that gains zero: tickets buy stickers and nothing else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Don't take a set power and toughness that is worse than the current one
Ambassador Blorpityblorpboop's begin-combat trigger sets its base power
and toughness to the total on the stickers you control. It is a "may",
and the AI always said yes - with no stickers out that is 0/0, so the
Ambassador killed itself the turn it landed.
AnimateAi's doTriggerNoCost accepted any non-targeting animate without
looking at it. It now declines one that only sets power and toughness,
only on its own creatures, and only when the animated copy evaluates no
better than what is there. Anything that also grants types, keywords or
abilities, anything with a sub-ability, and anything mandatory is left
alone: those can be worth having at any size.
Four scripts can reach that check - the Ambassador plus Eldrazi Mimic,
Geometric Weird and Mirkwood Meditator, all the same shape. Verified in
a game that the Ambassador now keeps its 3/3 and blocks with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Second review pass over the branch
The one that changed play: PutStickerAi.chkDrawback answered
AiAbilityDecision(0, WillPlay), and a rating of 0 is not willing - the
floor is 30. canPlayWithSubs hands a sub-ability's decision back as the
whole ability's, so a player with no sticker to place would not cast
Finishing Move at all, removal and everything. The override is gone:
SpellAbilityAi.chkDrawback already returns (100, WillPlay) for an ability
that does not target.
Correctness:
- StickerSheet listed the public zones by hand and left out Merged, Ante,
Flashback and Junkyard, where GameAction keeps a sticker but the scan
could not see it - so the pool would offer the same sticker again.
Derived from ZoneType.isHidden() now, the test GameAction itself uses.
- The sheet panel went stale after a stickered card changed zone: the
refresh ran from setStickers, while the card was in neither zone.
- addChangedSVars replaced rather than merged, so a static with both
AddSVar$ and a sticker-granted HasAttackEffect would lose one.
- Granted keywords are trimmed, the way granted traits already were.
Simplicity and reuse:
- PutStickerEffect was the only effect in the tree draining the trigger
queue itself; the stack does that.
- The targeting path in PutStickerAi was dead - no PutSticker script
targets - so it and its three call sites are gone.
- Tusk and Whiskers carries AILogic$ AtOppEOT like the other three ticket
abilities, and a sticker ability that costs mana waits for main 2:
a sticker is permanent, so there is no reason to buy one early.
Left alone on purpose: a sticker's traits are still rebuilt on each state
check, because the cache Forge uses for granted traits resolves SVars
against the granting static rather than the sheet. The reason is now a
comment at the site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Sticker prompts send what a remote player can receive
A remote player's prompt is serialized, and a Sticker is not, so both
choices now go over as positions with their labels rendered up front.
Also: the new UI strings in every language file, lowercase deck editor
labels to match their neighbours, and two stray blank lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Address first review: sheets, Done for the Day, Wicker Picker, tests, comments
- Revealed sticker sheets list their stickers from the start; the details
pane was empty until the first sticker was placed.
- Done for the Day uses GenericChoice, which also makes the CountersPutAi
ticket special case unnecessary, so that is reverted.
- Wicker Picker is dropped: sticker kicker is a kicker variant, not a
trigger, and has its own issue.
- Tests cut from 68 to 18: the all-96 ability sticker build, the core
rules, and the AI tests that fail without their fix.
- Comments trimmed to CR references and the few non-obvious notes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stickers on merged and melded permanents (CR 123.5b/c)
A stickered card that mutates onto a permanent moves its stickers to the
merged permanent. When a merged or melded permanent leaves for another
public zone, its owner chooses which card keeps the stickers, and the
effects move with them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name stickers follow a name that changes underneath them
A name sticker's name was built once when placed, so a later mutate or
copy effect left the old name showing. It is rebuilt whenever the clone
states change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name stickers across transform and face-down; melded stickers (CR 123.5a, 123.6b/c)
The name is now rebuilt whenever the card's state changes, not only its
clone states, so a transformed card gets the back face's name with the
sticker, and a face-down permanent is named only by its stickers rather
than showing its real name. Both cards' stickers are on a melded
permanent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Done for the Day: the ticket-or-Treasure choice can be declined, and its trigger text is written out
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: liamiak <liamiak1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: tool4ever <therealtoolkit@hotmail.com>
* Check the lure requirement before the block cost (#11949)
mustBlockAnAttacker skips an attacker when blocking it costs something, and
again when its lure requirement is already met. The cost check ran first, and
getBlockCost walks every card in the static ability source zones and rebuilds
each one's static abilities, while the lure check only reads the attacker's own
keywords and passes for any attacker without a lure requirement. Both are
side-effect free, so running the cheap one first gives the same answer.
The loop runs per attacker for every candidate blocker, so a wide board pays
the scan for every pair. On a board of sixty distinct creatures a side, one
turn with an attack: 80189 ms to 44800 ms, and calls to
CardState.getStaticAbilities over two turns fall from 1007936214 to 362382774.
A seeded four-player commander table plays the same games two to three percent
faster.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Fix adventure quest dialogs repeating after load (#11719)
* Fix adventure quest dialogs repeating after load
* Preserve quest dialog state across platforms
---------
Co-authored-by: Codex <codex@openai.com>
* Make background transparent when selecting commander (#12084)
* Make background transparent when selecting commander
* remove comment
* Make the remote GUI protocol usable without the network stack (#12091)
* Create the server's event loops when it starts
HostedMatch.startGame and InputPassPriority.showAndWait call
FServerManager.getInstance(), so every offline human game built two
NioEventLoopGroups it never used. startServer now creates them and
stopServer tolerates a server that never started.
* Split the transport out of RemoteClientGuiGame
ProtocolGuiGame holds the protocol and delta sync against any IRemote.
RemoteClientGuiGame extends it and keeps what the network needs: the
codec tracker, Java-serialized event wrapping, bandwidth logging and
the lobby back-pointer. HostedMatch and PlayerControllerHuman check
the base class. Network behaviour is unchanged.
* Let the GUI decide how an input waits
InputSyncronizedBase now waits through IGuiGame.awaitInput. The
default awaits the latch as before. A single-threaded host, where
answers can only arrive on the waiting thread, overrides it to take
them in place.
* Keep offline games off FServerManager
HostedMatch.startGame and InputPassPriority.showAndWait called
FServerManager.getInstance() in every game. That class cannot link
without Netty, so any match loaded the network stack. The running
server now registers itself in HostingServer, which the match and
input code ask instead. AfkTimeout moves there too: its NOOP lambda
compiled into FServerManager and loaded it.
* Add in-process protocol GUI tests
A stock PlayerControllerHuman plays the AI through ProtocolGuiGame and
an in-process IRemote, first threaded, then with every answer taken on
the waiting thread. Neither path starts a server, and the in-process
test checks the game loads no Netty, codec or FServerManager class.
* Make River Song's draw ability to a static ability (#11870)
* Goblin Blastronauts (UNF) (#12039)
* Goblin Blastronauts (UNF)
* create sa copy outside the loop
* fix typo
* move check if a die roll result is valid to its own method
* remove copy since it has become unnecessary
* Fix syntax in trigger mode for card effect
* - Add FRA/FRC achievements by Marek14. (#12100)
* add sticker support to quest mode and cleanup
* Revert "prevent funny sets from creating drafts in quest mode, since they crash the game"
This reverts commit c0557e50638090fe565b3b1afb0b454e443027db.
* Add files via upload (#12099)
* Remove card-script params the effect never reads (#12111)
* Let a may-play grant cover more than one player (#12107)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Some cleanup (#12112)
* Dack Fayden, Helping Hand: only choose opponents for permanents that entered (#12113)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Don't fire menu shortcuts for keys typed into a text field (#12117)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: don't bid life on Itazura with nobody to outbid, and cap the bid otherwise (#12115)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Update WorldBackground (#12098)
Dispose chunk textures that leave the view area, reuse a single scratch pixmap per chunk build, and dispose the placeholder pixmap. Hopefully it fixes white world background caused by exhausted texture memory.
* update graphics, reduce draw calls for rectangles specially using art/card image renders
* use freetypefont generator and move counterfont generation to assetmanager
* lazy load counterfonts
* AI: don't activate abilities into lethal activation triggers (#12125)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix inverted close on RewardActor, update endCLip check
* update counter font generation, update filter
* Fix token creation for cards from an edition that is not installed (#12109)
A printing keeps its edition code when it is read from another machine:
PaperCard.readObject finds the rules by name when the edition is missing
here and leaves the code as sent. A deck that pins a custom edition
therefore puts cards into a network game whose set code the host cannot
look up, and StaticData.getCardEdition returns null for it.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Edition updates: FIN, PRM, SLD
* Add card flight/play animation (#12138)
* Add card flight/play animation
* update animation
* Draw the game when mandatory triggers loop without end (#12127)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* iOS: fix build and launch/runtime crashes on iOS 27 (#12132)
* iOS: fix simulator build and runtime crashes on iOS 27 - ios-pipeline.sh: stamp sim binary with SDK 26.0 to avoid iOS 27's
UIScene-lifecycle launch trap (workaround; real fix is UIScene adoption)
- ios-pipeline.sh: find robovm-dist/compiler in the build m2 clone
- bridge.cfg + JMisc8: bridge Collections.emptyNavigableMap (card loading hang)
- CdnUuidCache: avoid ConcurrentHashMap.newKeySet (crash entering Classic)
- ScryfallBulkDataSync: use getContentLength (missing on MobiVM)
---------
Co-authored-by: David Ayres <dwayres@Davids-Mac-Mini.local>
* AI: check Saddle and Teamwork costs by total power (#12124)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: don't pay to keep an empty "exile until leaves" permanent (#12141)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: don't reanimate creatures that would die right away (#12142)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add the capability to due Commander Draft
* Add CMR Rankings file
* Set free commander in CardEdition
* Update Card Play Options, Add leave animation (#12148)
* Add leave animation
* update overlay
* Update Animation options
* Update Adventure SettingsScene, Update CardFlightOverlay
* minor optimization
* update prefs
* localization
* Edition updates: NAU, PF26, SLD, UNK, YFRA
* Add Coin Flip Animation (#12155)
* Add Coin Toss
* unused import
* update to use existing event
* try to prevent writeMatchPreferences on lifecycleClosing
* CharmAi: fix evaluating modes with different X (#12156)
* Old Border Shandalar: Add Svyelunite Temple quest (#12145)
- Add the Svyelunite Temple dungeon in the blue biome with a
faction-choice quest between Vodalians and Homarids
- Add 35 Fallen Empires enemies with their quest decks, sprites
and named boss reward cards
- Add quest and ally dialogue for all NPCs; homarid-kin lines use
clicks, whirrs and gestures
- Grant six Fallen Empires boosters with both completion rewards
alongside gold, shards and faction cards
- Add the temple icon to the shared buildingsbosses atlas and
reference it from the point of interest
- Face the Floodwater Homarid left via native Left animation
aliases so it greets players approaching the dam
* Adventure: repair two broken map dialogs (#12128)
Co-authored-by: liamiak <liamiak1@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Use a required multi-mana source first (#12119)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Count additional triggers when the waiting triggers are collected (#12106)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Refresh entry snapshots once a simultaneous batch is on the battlefield (#12121)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cards): correct three scripts that disagree with the printed card (#12153)
* Demonic Pact: the discard mode targets an opponent (#12160)
* Update wiccan_rising_magician.txt (#12161)
* Move coin textures to Assets
* Quest: selecting every color no longer generates an empty starting pool
BoosterUtils.populateBalancedFilters uses the NON-selected colors as the
repetition multiplier for the preferred-color filters (to keep them
proportionate to the unselected colors its else branch mixes in). With
every color selected, otherColors is empty, so neither branch builds a
filter and generateCards silently produces nothing: a new quest whose
whole card pool is the automatic basic-land grant and a deck editor with
no cards to offer. Reproduced headlessly with BALANCED distribution and
all six colors selected; any five of the six generates the full pool.
Both clients, any world.
Fix: floor the multiplier at one pass.
QuestStartingPoolTest pins the all-colors case (fails before the fix,
passes after) with two-color and no-preference controls.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Allocate network test ports instead of hard coding them (#12165)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Edition updates: SLD, SLP
* FRA: fix empower jace token image
* Review: drop the test class and the long comment
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Mana refund: do not reverse the payment of a mana ability that itself could not be reversed (#12162)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Fix hotseat draw offer dialog (#12173)
Co-authored-by: Damir Tomic <viperus.oblivion@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: pay for mana abilities that cost mana (Signets, filter lands) (#12149)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Dice Animation (#12167)
* Dice Animation
* update GameEventRollDie
* update DiceEventBridge
* update to support multi dice and support d4 to d20
* add texture
* update dice shape
* use forge graphics batch
* update texture, lighting, update coin
* update prefs
* localization
* update RewradActor hideTooltip
* commented out failing test, this halts build process
@Blackvipe99 update/fix if needed
* update CardFlight animation
* Booster slots draw from MyRandom, so a seeded pack is reproducible (#12184)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix typos in Reality Fracture edition names (#12185)
* AI: pay for a mana ability with a mana cost that is activated on its own (#12179)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Some cleanup (#12186)
* Simultaneous entry: don't let permanents entering together change how each other enters (#12171)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add FlipOntoBattlefieldAnimation (#12183)
* add FlipOntoBattlefieldAnimation
* update
* update fields
* update animation, fix tapped angle
* Fix netplay host bugs (#12181)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* update Coin3D, CoinFlipOverlay, Dice3D params
* Player must attack: don't count on an attack that has a cost (#12178)
Trove of Temptation and Seeker of Slaanesh make a player attack with at
least one creature if able. That requirement was given to one attack
without looking at its cost. With Propaganda out, the attack on the
player was then skipped for its cost and the free attack on a
planeswalker because nothing asked for it, so not attacking was accepted.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Old Border Shandalar: Fix Svyelunite Temple quest offers and rewards (#12177)
* Fix lethalDamage update
* add comment
* fix soft lock (#12191)
* fix soft lock
* override fOption
* trigger button
* fix clashing of boss dialog and coinflip (#12192)
* fix clashing of boss dialog and coinflip
* wait for transition to finish
* TRK/TRC cards from New York Comic Con (#12187)
* Fix seven card-script param bugs found porting to Crucible (#12193)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix logic for two hit cards
* increase hit chance for FlipOntoBattlefieldEffect
* fix logic for desktop swing since its not possible with the corner hit and don't have animation.
* Fix stale boss dialogue portraits between Adventure duels (#12176)
* Update net deck archive lists
* Update net deck archive lists
* Edition updates: SLD, TRC, TRK
* Rework GameEventForwarder to batch events at a game loop checkpoint (#12189)
Batches of events were cut by a timer and a size limit, so a cut could fall anywhere. A spell could be cast and resolve in one batch, and a card could leave play in one batch with its move in the next.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix specialized downloading base image
* make CoinFlipOverlay auto close
* Simultaneous entry: what sees a permanent enter is decided after everything entering with it has (#12188)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* The Darkhold (MDD) and other MDD spoilers (#12202)
* Show the waiting prompt after blockers are declared (#12203)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Edition updates: MDC, MDD, PMEI
* Eureka, Hypergenesis: end the process when no card was actually put onto the battlefield (#12204)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Set default section to Commander
* fix(cards): AddType$ comma lists and the g_1_1_elf token type (#12212)
* Some cleanup (#12213)
* Update CardZoom layout, MatchScreen layout and CommandZone (#12199)
* Update CardZoom landscape layout
* update controls
* add nestable view for specialized/mutate, update controls, update ease-in/out.
* update default layout matchscreen, add support magnifier on drag
* update commandZone
* restore portrait
* add hide phase stop for landscape mode (default off)
* update zonetab size
* move touchdrag to VCardDisplayArea
* update cardzoom close logic
* minor optimization
* Add deck statistics (#12221)
* Add deck statistics
* unused import
* update panel and border colors for adventure
* move DeckStatistics
* add blocked gate check for castle
* update deckstats icon
* fix clipping
* YSOS: Summitfest Closing Ceremony (#12217)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Compose the "Waiting for" prompt client-side (#12205)
The host no longer builds a remote player's "Waiting for X" prompt on a timer thread. It forwards awaitNextInput as a notice, and the client composes the prompt, buttons and elapsed timer from its own game view and yield state. The host sends the same notice where it used to repaint the yield prompt, because the client shows an active yield as part of its waiting prompt. The client cancels a pending wait when the next prompt arrives. In the protocol, awaitNextInput replaces showWaitingTimer.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Network logging changes for readability and size (#12224)
* Count delta packets with a negative saving in the network log analyzer
The packet pattern only matched a non-negative Savings figure, so a
packet larger than the full state was skipped.
A delta can be larger than the full state on a client's first sync.
The events sent with it cost more than the full-state figure allows
for them, because a delta serializes each event on its own while the
full-state figure measures them together as one list.
The effect on reports is marginal. In a 10-game run it dropped 11 of
10,012 packets and 2.5% of delta bytes, and the overall savings figure
moved from 97.1% to 97.0%.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add UndoManager, fix Undo (#12226)
* Update GameMemento
* Update UndoManager
* Fix animation when using undo
* StickerSheet: fix toList (#12232)
* Update undo for netplay and request undo each phase
* Nashi, Illusion Gadgeteer is rebalanced
* Edition updates: YFRA
* Edition updates: YFRA (reorder)
* Quick Gauntlet: name every generated opponent deck (#12233)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Adventure: fix Landscape Sketchbooks whose names match no edition (#12227)
Co-authored-by: liamiak <liamiak1@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Agetian <stavdev@mail.ru>
* AI: don't goad its own creatures (#12239)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Vengeance and Hela, Mistress of Darkness (MDD) (#12216)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AI: judge dungeon rooms as triggers when choosing where to venture (#12237)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Undo Mode prefs
* Update Dice texture
* Fixes to lobby, deck generation, and netplay (#12230)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Xanathar, Guild Kingpin; Whispersteel Dagger: match the other player's cards by owner (#12247)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Reckless Detective: only pump when the cost was paid (UnlessResolveSubs$ WhenPaid) (#12245)
* The Earth Crystal: target only creatures you control (#12256)
* Clarify target description for PutCounter action
* Add targetableStack, Add marching selection box (#12231)
* Add targetableStack, Add marching selection box
* update
* optimize drawAlternatingDashedRect
* add support targeting stack for desktop and netplay
* Unified target highlighting, remove the separate stack-highlighting protocol message, remove the redundant selection flag
* remove unused imports
* Let the player order the two cards put back by "draw three, then put two on top in any order" (#12262)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Restore alternate landscape layout (#12258)
* Restore alternate landscape layout
* Reduce ALT_ZONE_MAX_WIDTH_RATIO
* Update Undo Hovered/Pressed Color
* Update undo button, add prefs for Target selection box
* remove unused import
* localization
* fix vprompt
* Realm of legends 1.077: Huge Reality Fracture Update (#12263)
* Added alchemy shop to otherworldly market
* fblthp maze additional setup
* fblthp maze additional setup pt2
* Dialog fix
* Banlist update
* Banlist update 2
* Deck edits
* Reality Fracture deck and reward updates
* deckfix
* Added new maps
* Gimmick cave decks
* New map update
* New map
* Overworld icon overhaul
* New mini-dungeon POI updates
* Echoverse overlay map
* Mapfixes, new landscape sketchbooks
* Hexhaven tower map
* Theorist's Sanctum Map
* Mapfix; item rebalances
* Hidden sword clearing map
* Mapfixes
* Sliver Hive rebalance
* Black Market maps
* Skeleton Ship map and gimmick decklists
* Cursed Graveyard map
* Skeleton ship poi
* Brushwagg underbrush map + edits to other new maps
* Jandor's Caravan map
* Horrible Factory
* Thieves' Hideout map
* Mtenda Mesa map
* Koskun keep map, four miniboss promotions
* Fblthp maze edit
* Rathi shadow rift map
* Old Capenna Memorial map
* Old Capenna Memorial bugfix
* Five more quest dungeons
* Domain of Night
* Edited new maps for consistency, changed some poi icons
* deckfix
* deckfix
* New precons; changed easy mode precons
* Theorist's sanctum decks
* Made easy easier; new deck/deckedit
* Ancient ruby lavafield update
* Various minidungeon decklists
* hexhaven tower decklists; a few more assorted edits
* Decklists, new shops
* Item entries
* Final item and enemy entries
* Alpha Testing Fixes
* Mapfix
* disable drag preview when panning is engage, add TODO for touch
* Quest-Mode: fix toList (#12267)
* Bilbo, Thief in the Night and nine more: offer Adventure spells (#12248)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Some cleanup (#12268)
* add check for FrameRate render
---------
Co-authored-by: Chris H <zenchristo@gmail.com>
Co-authored-by: kevlahnota <anthonycalosa@gmail.com>
Co-authored-by: Rensen3 <127029481+Rensen3@users.noreply.github.com>
Co-authored-by: Alexey <121340072+alex-dmt@users.noreply.github.com>
Co-authored-by: Jetz72 <Jetz722@gmail.com>
Co-authored-by: Paul Hammerton <paulhammerton1@hotmail.co.uk>
Co-authored-by: tool4ever <therealtoolkit@hotmail.com>
Co-authored-by: Churrufli <churruflis@gmail.com>
Co-authored-by: Hans Mackowiak <hanmac@gmx.de>
Co-authored-by: jczastkiewicz <jc@archlab.pl>
Co-authored-by: Fulgur14 <54345051+Fulgur14@users.noreply.github.com>
Co-authored-by: Paul Hammerton <18243520+paulsnoops@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: MostCromulent <201167372+MostCromulent@users.noreply.github.com>
Co-authored-by: Simisays <67333662+Simisays@users.noreply.github.com>
Co-authored-by: Leandro Doctors <ldoctors@gmail.com>
Co-authored-by: Nerdslayer <49656879+Nerdslayer@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Blackvipe99 <byron.gibson99@gmail.com>
Co-authored-by: GitHub Actions <actions@github.com>
Co-authored-by: DamirTomic <viperus.oblivion@gmail.com>
Co-authored-by: ldeluigi <44567586+ldeluigi@users.noreply.github.com>
Co-authored-by: Rensen3 <renesmdt@gmail.com>
Co-authored-by: Agetian <stavdev@mail.ru>
Co-authored-by: liamiak <liamiak@yahoo.com>
Co-authored-by: liamiak <liamiak1@gmail.com>
Co-authored-by: Cagliostro <286431257+khaliostr@users.noreply.github.com>
Co-authored-by: BigCrunch22 <59816726+BigCrunch22@users.noreply.github.com>
Co-authored-by: Nikolai Weidt <aramus92@gmail.com>
Co-authored-by: Justin <jalirkan@gmail.com>
Co-authored-by: Abishai James <18426277+haksanlulz@users.noreply.github.com>
Co-authored-by: David Ayres <101526453+Dalamar32@users.noreply.github.com>
Co-authored-by: David Ayres <dwayres@Davids-Mac-Mini.local>
Co-authored-by: Vanja <56617195+vanja-ivancevic@users.noreply.github.com>
Co-authored-by: Nikolai Weidt <nw@uni-kassel.de>
Co-authored-by: Shaun Adams <shaun.adams@volunteers.acasi.info>
Co-authored-by: tyrathalis <tmwise97@yahoo.com>
Co-authored-by: Douglas Bergentz <carl.douglas.bergentz@gmail.com>
Co-authored-by: dvirbuc <dvirbuc@gmail.com>
Co-authored-by: Shaun Adams <s.adams@f5.com>
Co-authored-by: Douglas Bergentz <tonalflowdev@gmail.com>
Co-authored-by: Serafina <serafina2880@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.