Repository navigation
v1.2
Duel:
- Emote, mute, and pet interaction during a duel are now functional from the player info zone (V key). Enter on yourself opens the equipped emote wheel — read straight from
EmoteOptionsController._equippedEmoteOptionsand sent throughUIMessageHandler.TrySendEmote, so the wheel doesn't need to be visually open. Enter on the opponent toggles per-duel mute viaOpponentDialogController.UpdateIsMuted, mirroring the in-game mute icon. Shift+Enter on yourself opens a pet-interaction menu (Stroke, Tap, or Chest/Arm/Leg/Head depending on the pet) routed through the activeAccessoryController. Incoming opponent emotes are announced viaUIMessageHandler.EmoteRecievedCallback, localized throughEmoteUtils.GetFullLocKey, and gated by your current mute state. Opponent emote announcements use High priority so they aren't drowned by Immediate-priority card reads while you're arrowing through cards. - Pet action menu hides actions whose prefab UnityEvent has no persistent listeners. Some pets (e.g. the ONE_Skitterling mastery pet) wire hover events to nothing — surfacing "Stroke" would have announced "Streicheln ausgelöst" with no in-game effect at all. The menu now only surfaces actions wired to actually do something (Animator trigger, audio event, etc.); a pet with nothing wired announces "This pet has no interactions" instead of misleading the user with phantom options.
- Ctrl+F1 inside the player info zone reads a targeted hint (Left/Right to switch between you and the opponent, Up/Down to cycle through values, Enter for emote / mute, Shift+Enter for pet, Backspace to leave) instead of the full duel-keybindings dump.
Cosmetics:
- Per-deck cosmetic selection (avatar, pet, sleeve) is now fully accessible. Activating the deck-title button opens the deck-details popup; tiles announce the current selection ("Avatar: Standard, press Enter to change") and Enter opens the matching selector. Pet and sleeve sub-popups stack on top — pets, sleeves, and avatar busts are read with their localized names plus status (selected, default, owned, locked) instead of the unlabeled hitboxes the screen reader used to encounter. Backspace closes the active selector and returns to the deck-details tiles.
- Profile pet selector now actually applies the selection on Enter. The
PetPopUpV2uses a two-step flow (preview, then explicit Confirm); the mod now invokesOnConfirmafter the preview click so the chosen pet is persisted instead of dropped when the popup closes. - Avatar selector is inline rather than a separate popup. Activating an avatar bust now triggers a silent rescan so the title and bio TMP_Text on the right side update for each preview — arrow Up reads the new bio without leaving the selector.
- Cosmetic value labels refresh after closing a sub-selector so the deck-details tile reflects the just-picked avatar / pet / sleeve.
- New popup-mode infrastructure underneath: stacked-popup support in
BaseNavigatorlets any sub-popup opening on top of an existing popup be navigated cleanly, then return to the parent popup with refreshed labels. Benefits any future flow that nests popups, not just cosmetics. - Deck-builder card info blocks (Arrow Down) end with a "Style" line (e.g. "Style: Showcase Etched") read live from each tile — on both the deck side and the collection pool. Skin codes are read per row from the tile's own
MtgCardInstance.SkinCode(deck tiles) or_lastDisplayInfo.Skin(pool tiles) rather than the deck-level skin dictionary, so two rows of the same card with different skins read distinctly. When no skin override is applied, the line falls back to the printing identity (set name + collector number, e.g. "Style: Dominaria 26") so two rows of the same card from different printings — differentGrpIds — can be told apart by ear too. - New
Shift+Enteron a focused deck-builder card opens the card viewer popup, mirroring the sighted right-click. Works on collection cards, deck-list entries, sideboard, commanders, and read-only deck cards. The craft popup itself now leads with a "Style:" header so the user gets immediate confirmation of which printing they're spending wildcards on (different printings of the same card live in the inventory as separateGrpIds; the popup is single-style by design). - New
Ctrl+Enteron a focused pool card expands the title into one adjacent tile per available art-style or printing variant — invokes the game's preferred-printing expand chevron under the hood (PagesMetaCardView.ExpandClicked()→PreferredPrintingState.ExpandCard/CollapseCard), so the same fan-out a sighted player gets by clicking the tile's ribbon. Press again to collapse. Inside an active fan-out, each variant tile announces its differentiator up-front (e.g. "Sagenumwobener Durchgang, Showcase Etched" vs "Sagenumwobener Durchgang, Avatar TFTUR Extra 57") instead of repeating the bare name on every sibling; the announce reads_lastDisplayInfo.ExpandedStyle(Expanded_First/Mid/LastvsSolo/Stacked) so normal browsing stays quiet. Solo cards (no alternates) announce "No alternative styles available" instead of a misleading expanded/collapsed cue.Shift+Enteron the chosen variant then opens the craft popup for that exact (printing, skin). F1 help has a new "Deck builder card shortcuts" category covering both shortcuts; the deck-builderCtrl+F1hint mentions them too.
Bug fixes:
- Generic mana in card costs is now read as "generic" instead of "colorless" (e.g.
{5}{U}{B}→ "5 generic, blue, black"). "Colorless" has a specific Magic meaning —{C}and the Colorless color enum still read as "colorless". Contributed by @LordLuceus (PR #90). - Repeated mana symbols inside one brace pair no longer drop tokens. The game encodes pairs like
{B}{B}as{oBoB}, and the regex consumed the leadingobefore tokenizing — so Spree's+ {B}{B} —line read as+ Black —. Now reads+ Black, Black —. Contributed by @LordLuceus (PR #90). - Evoke and similar hybrid alternative costs are no longer dropped. The game encodes
{U/B}{U/B}as{o(U/B)o(U/B)}, which the parser couldn't tokenize at all (parens weren't recognized) — so Deceit's "Evoke {U/B}{U/B}" line read as just "Evoke". Now reads "Evoke Blue or Black, Blue or Black". Braced-form parser fix contributed by @LordLuceus (PR #90); subsequently extended to bare-text and parameterized hanger sequences for parity. - Store / offline: opening the store while disconnected no longer reads out the empty Welcome Bundle modal ("000000 Edelsteine, $X.XX, ..."). The store's confirmation modal is now ignored while its currency buttons hold placeholder values, so the real "Store-Fehler" / "Could not retrieve items" SystemMessage popup announces and is keyboard-navigable instead.
- SystemMessageView popups (offline / error / restart-required dialogs) now outrank routine confirmation modals that opened in the same frame, so the system message becomes the active panel instead of being masked.
- Pack reward popup no longer reads the previous purchase's set name. The rewards GameObject lives on Canvas - Screenspace Popups and stays active across purchases, so the pack-name cache and
_revealingWasSeenflag survived between popups — the second pack reused the first pack's set name and a 2 s timeout fallback briefly re-activated the navigator after each claim. Per-popup state is now reset on the True→False detection transition so each purchase re-extracts pack set names cleanly. - Translated the four 1.1 battlefield-stacking strings (
StackSelect_Unavailable,SettingBattlefieldStacking+ Desc,HelpCtrlEnterStack) into es, fr, it, ja, ko, pl, pt-BR, ru, zh-CN, zh-TW — they were English-only. - Ctrl+F1 in a duel now reads the hint for the active sub-state (Choose X, spinner, mana color picker, declare blockers, browser) instead of always reading the full duel keybindings list.
- Phase-skip warning now also fires during Declare Blockers: pressing Space to confirm "no blocks" while you still have untapped creatures and nothing assigned warns once and requires a second press to confirm. The warning is gated to the opponent's turn — that's when the user is the defending player and can actually declare blockers — so attacking on your own turn no longer triggers a false-positive guard during the opponent's blocker step.
- Mana pool announcements now reflect the real amount when one ability taps for multiple mana of the same color. The game packs same-color mana into a single
MtgManaentry withCount=N(Rofellos = one entry withCount=3); the parser was incrementing by 1 per entry, so a Rofellos tap announced "1 green" instead of "3 green". Now sumsMtgMana.Countper color. - Optional-trigger target-select prompts (Tinybones-style "may cast target" triggers) no longer trap the player. The browser presented a
SelectCardsMultiZonescaffold withAllowCancel.Noand aDoneButton-only provider dict; selecting the only valid target and then cancelling the cast looped back into the browser with a sticky selection that auto-resubmitted, with no escape reachable through the existing CancelButton path. Backspace now mirrors the Space-confirm guard: first press warns, second press deselects via per-cardSimulatePointerClickand invokesOnButtonCallback("DoneButton"), which the game flips to the FailToFind face once the count drops to 0. The two guards mutually clear their warning bits on first-press so a Space-then-Backspace intent change can't leak armed state across a later same-key press. SelectCardsMultiZonebrowsers withAllowCancel.Noand a disabledDoneButton(count below minimum, no zone tab to flip to) no longer trap the player in an Exit→Re-enter churn.ClickConfirmButtonwas setting_pendingRescan = trueunconditionally up front; every provider/workflow path then failed to click anything, but the rescan still fired on nextUpdate— re-announcing the browser entry and teleporting focus back to card 1, drowning the failure feedback. The fall-through path now clears_pendingRescan(no click happened, no scaffold-reuse to track), and the previously generic "No confirm button found" announcement is replaced with the already-translated "No target selected" when the provider dict reportsDoneButtondisabled — so the player hears the actionable game-state cause instead of a mod-error string.- Card cost announcements now reflect active cost reductions instead of always reading the printed cost. With a Warden of Evos Isle on the battlefield, a second Warden in hand now reads
1 generic, blue(matching the in-hand tile) instead of2 generic, blue(the printed cost). The mod now reads the live cast-action cost (Action.ManaCoston the per-cardActionslist, converted viaGreClient.CardData.ManaUtilities.ConvertManaCostsToList) — the same source MTGA's own card-tile renderer uses — and falls back toPrintedCastingCostwhen no cast action is available. Library / collection / deck-builder views (no live instance, so no actions) still read the printed cost as before. Contributed by @LordLuceus (PR #91). - Deck-builder Arrow Down on the first card after a search/filter now reads the card's details instead of jumping to the next card. The post-search rescan was overriding EventSystem focus to the first overall element (e.g. the Wildcards row) instead of the restored grouped element, which deactivated CardInfoNavigator and turned the first Arrow Down into a navigation step.
- Damage assignment browser is no longer hijacked by the generic SpinnerNavigator. Both navigators were activating on
SpinnerAnimatedwidgets, but SpinnerNavigator ran first and stole input — announcing blockers asCard #483(its battlefield-only resolver missed cards moved into the browser), reporting "Verteile 0" (no parent SpinnerGroup → max=0), and submitting viaPromptButton_Primaryinstead of the browser'sDoneActionso confirming the default lethal assignment did nothing. SpinnerNavigator now yields when an AssignDamage browser is active, lettingBrowserNavigator.AssignDamagehandle it cleanly (proper card names, total damage, lethal status, Space → DoneAction, Backspace → UndoAction). - Mutate Yes/No prompt (e.g. Huntmaster Liger asking "mutate onto X?") no longer locks the player out. The scaffold
BrowserScaffold_YesNo_Mutate_…was being collapsed to plainYesNobyExtractBrowserTypeFromScaffold, losing the variant: Tab cycled only the mutation-pile cards (never reaching the Yes/No buttons), Space fell through to the duel-levelPromptButton_Primary(the pass button — wrong target, browser stayed stuck), the entry hint announced the literal locale keyBrowserHint, and the friendly name read "Choose yes or no" instead of "Mutate choice".BrowserDetectornow flags the Mutate variant onBrowserInfo, andBrowserNavigatormirrors the OptionalAction flow for it: Tab cycles cards then buttons, Space routes through the provider's logicalDoneButton(resolves to2Button_Right= Yes), Backspace throughCancelButton(= No), with a properBrowserHint_Mutatehint translated for all 12 locales. - Yes/No browser prompts now read the actual question instead of just "Choose yes or no". Ancestral Recall's "Are You Sure?" popup (and any other button-only browser like Optional-action / Mutate prompts) reads
Do you want to target your opponent with Ancestral Recall? Choose yes or no— the scaffold's subheader text is pulled via the existingExtractBrowserHeaderTexthelper and prepended to the type label. Contributed by @LordLuceus (PR #93). - Battlefield creature P/T now announces live damage as a separate piece after counters, matching how the game shows the red number next to the toughness for sighted players. A 3/3 that took 2 damage reads
3/3, 2 damage(or with counters:4/4, 1 +1/+1, 2 damage); the printed/effect-modified toughness stays intact so it's clear how the value was built. ReadsMtgCardInstance.Damageoff the model — 0 when there's no live instance, so deck builder / collection / library reveals stay unaffected. Feature originally contributed by @LordLuceus (PR #92, as a toughness adjustment); reworked to surface damage as its own line so the announcement mirrors the game's visual layout. - Opponent's hand (Shift+C) no longer lists cards that have left the hand. Cards the opponent has cast (their CDC lingers under the OpponentHand holder with
IsDisplayedFaceUp=trueeven after moving to graveyard / exile / battlefield) used to stay in the revealed-cards list, producing a "Opponent's hand, 2 cards. … 1 of 3" mismatch where the displayed count and the navigable list disagreed and a card the opponent had already cast still appeared as if it were in their hand. The OpponentHand list now applies the sameIsRealHandCardmodel-zone check the hand count already used, so list and count agree. Contributed by @LordLuceus (PR #95). - Tab navigation no longer drops cards with affordable actions. MTGA's visual HotHighlight isn't reliably refreshed in transitional states — both on castable hand cards (notably immediately after a land play, where the battlefield creature's activated-ability highlight stays but the player's still-castable hand cards lose their visual cue) and on the player's own battlefield permanents with activated abilities (e.g. a creature with
{T}: do Xwhose glow didn't refresh after an opposing spell resolved into the graveyard). The mod's scan only matched GameObjects whose names contained the literal substring"HotHighlight", so Tab would cycle whichever item happened to still have its glow. Both hand and player-side battlefield are now supplemented with a lookup into MTGA's ownActionsAvailableWorkflow: the workflow tracks aGreInteractionper available action with a pre-computedCanAffordToCastbool (set when the interaction is constructed fromAction.CanAffordToCast()), and we include any card that has a playable interaction — anyCast*variant (split halves, adventure, MDFC, prototype, Rooms, Omens), plainActivate(cycling, channel, foretell-the-card, plot-the-card, ninjutsu, on-battlefield activated abilities, planeswalker abilities), or non-paymentSpecial/SpecialTurnFaceUp(conspire, monarch, dungeon advance, morph/manifest flip) — that the workflow itself considers affordable. Mana abilities (ActivateMana), land drops (Play*), opponent permanents, and payment subactions are filtered out so Tab still only cycles meaningful actions. No mod-side affordability heuristics — the game answers the question. Castable-hand foundation contributed by @LordLuceus (PR #96); subsequently widened toActivate/Specialaction types and extended to player-side battlefield permanents.
Verification (SHA256):
AccessibleArenaInstaller.exe:0119ff8371a0b6291f9aacfb392dec4631afdd2644acb0f702a8b8e1091a5687AccessibleArena.dll:75b040f2b837b185175f8a1c8f3fa7d737f0f9bd40937de3808edd8bb7122809
Verify in PowerShell with Get-FileHash <filename> -Algorithm SHA256 or in Command Prompt with certutil -hashfile <filename> SHA256.