Skip to content

Route libgdx-port checks through the target GUI#10615

Merged
tool4ever merged 1 commit into
Card-Forge:masterfrom
MostCromulent:NetworkPlay/libgdx-handshake
May 6, 2026
Merged

Route libgdx-port checks through the target GUI#10615
tool4ever merged 1 commit into
Card-Forge:masterfrom
MostCromulent:NetworkPlay/libgdx-handshake

Conversation

@MostCromulent
Copy link
Copy Markdown
Contributor

@MostCromulent MostCromulent commented May 6, 2026

Bug report

Resolves #10613 — On the mobile client against a desktop host, search-your-library effects (Demonic Tutor, Solemn Simulacrum's basic-land trigger, fetch lands) present prompt but no cards are visible.

The general issue

Several UI decision points read GuiBase.getInterface().isLibgdxPort() to determine whether to route to desktop or mobile rendering paths.

This is a process-global static answering "is this JVM libgdx?" — when the right question is "is the player whose UI will render this libgdx?". The two answers agree in single-player and same-platform netplay, but diverge in cross-platform netplay: a desktop host serving a mobile client returns false and steers the choice down a desktop-renderer code path the mobile client cannot honor.

How it manifests in the tutor case

PlayerControllerHuman.chooseSingleEntityForEffect (line 585)
                        │
                        ▼
        ┌──────────────────────────────────────┐
        │ useSelectCardsInput(optionList, sa)  │
        │                                      │
        │  Reads GuiBase.getInterface()        │
        │       .isLibgdxPort()                │
        │  ── PROCESS-LOCAL on the HOST ──     │
        │                                      │
        │  Library zone qualifies only when    │
        │  !isLibgdxPort()  (PCH.java:459)     │
        └──────────────┬───────────────────────┘
                       │
        ┌──────────────┴───────────────┐
        │                              │
   returns TRUE                  returns FALSE
        │                              │
        ▼                              ▼
   PATH A                          PATH B
   InputSelectEntitiesFromList     getGui().chooseSingleEntityForEffect
        │                              │
        │ setSelectables ──┐           │ → GameEntityPicker / SGuiChoose
        │ tempShowZones ───┤           │   (a self-contained dialog —
        │ updateZones      │           │    does NOT use tempShowZones)
        │ hideZones ───────┘           │
        ▼                              ▼
   Renders cards INSIDE the         Renders cards INSIDE the picker
   matching floating zone           dialog. Works regardless of
   on the player's screen.          whether floating zones exist.
   ── REQUIRES tempShowZones
      to be implemented ──

Desktop host → check returns false → PATH A → host serializes tempShowZones over the wire to the mobile client. Mobile's MatchScreen.tempShowZones is an unimplemented stub (// pfps needs to actually do something), so cards never render. The same anti-pattern wrong-routes confirm dialogs, scry reorder, end-step discard, AI-skip reveals, and stack notifications.

The fix

Add boolean isLibgdxPort() to IGuiGame. Local GUIs return their process flavor; RemoteClientGuiGame returns the value the remote sent at lobby handshake (LoginEvent gains a libgdx field, populated by GameClientHandler and stored on RemoteClient at login). Every UI-flavor decision that calls getGui() afterwards is converted to the per-GUI check:

File Sites What it gates
PlayerControllerHuman.java 459 tutor / library-search picker (the reported bug)
PlayerControllerHuman.java 977 scry — also drops the !isNetPlay(getGui()) workaround for the same defect class
PlayerControllerHuman.java 814, 1444, 1871 trigger / replacement / payment confirm dialogs
PlayerControllerHuman.java 1564 end-step hand-size discard picker
PlayerControllerHuman.java 1712 spell-played value notification
PlayerControllerHuman.java 2221 AI-skipped-cards reveal
HumanCostDecision.java 1507 cost-action confirm
InputConfirm.java 58, 67, 80 generic confirm dialogs
InputPayMana.java 79 mobile multi-tap behaviour
FControlGameEventHandler.java 263, 285, 391, 449 stack notifications + zone-update gating

Why it's safe

Semantically a no-op except where it fixes a bug:

  • Single-player and same-platform netplay: returns the same value as before.
  • FControlGameEventHandler is only ever bound to a local GUI (HostedMatch.java:237 routes remotes to GameEventForwarder) — pure consistency cleanup.
  • Cross-platform netplay: now routes per the receiving renderer.

LoginEvent gains one boolean field; the existing version-mismatch warning catches any wire-format incompatibility between divergent builds.

Testing

Original bug can be easily recreated on master by casting Solemn Simulacrum as mobile client with desktop host.

Fix verified on NetworkPlay/libgdx-handshake: Solemn Simulacrum tutor (desktop host + mobile client, the original repro) now shows the library cards in the picker instead of an empty selection.

Note: although the bug report describes a recent change in behaviour, none of the relevant code paths (PlayerControllerHuman.java:459, the MatchScreen.tempShowZones stub, useSelectCardsInput, InputSelectEntitiesFromList) appear to have been modified by any recent commit in a way that would introduce this issue. This appears to be a long-standing latent bug.


🤖 Generated with Claude Code

PlayerControllerHuman and several input/event handlers branch UI flavor
on GuiBase.getInterface().isLibgdxPort() — a process-global check that
answers "is this JVM libgdx?" rather than "is the player whose UI will
render this libgdx?". On any cross-platform netplay configuration the
host steers dialogs, pickers, and notifications down the wrong code
path for the receiving client.

The visible symptom is Card-Forge/forge<!-- -->Card-Forge#10613: a desktop host
serving a mobile client routes tutors through
InputSelectEntitiesFromList → tempShowZones, but mobile's tempShowZones
is an unimplemented stub, so the library cards never render.

Add isLibgdxPort() to IGuiGame. Local GUIs return their process flavor;
RemoteClientGuiGame returns the value learned at lobby handshake
(LoginEvent gains a libgdx field, stored on RemoteClient at login).
Convert every UI-flavor decision that calls getGui() afterwards, and
drop arrangeForScry's !isNetPlay workaround for the same defect class.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MostCromulent MostCromulent requested a review from tool4ever May 6, 2026 09:04
@MostCromulent MostCromulent added Netplay BUG Something isn't working labels May 6, 2026
Copy link
Copy Markdown
Contributor

@tool4ever tool4ever left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice find + breakdown!

@tool4ever tool4ever merged commit b5e98d3 into Card-Forge:master May 6, 2026
2 checks passed
@MostCromulent MostCromulent deleted the NetworkPlay/libgdx-handshake branch May 6, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BUG Something isn't working Netplay

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tutors not showing cards on mobile

2 participants