Skip to content

Compose the "Waiting for" prompt client-side - #12205

Merged
tool4ever merged 2 commits into
Card-Forge:masterfrom
MostCromulent:client-await-prompt
Oct 11, 2026
Merged

tool4ever merged 2 commits into
Card-Forge:masterfrom
MostCromulent:client-await-prompt

Conversation

@MostCromulent

@MostCromulent MostCromulent commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Moves responsibility for the "Waiting for X" prompt from the host to the client.

How it works

Today: when a remote player's input ends, the host starts a 250 ms timer for them. When it fires, the host works out who has priority, writes the text in its own language, and sends showPromptMessage, updateButtons and showWaitingTimer. The engine keeps running while it does this.

With this change: the host sends one awaitNextInput notice with no arguments. The client runs the delay, reads who has priority and which yield is active from its own state, and draws the text, buttons and elapsed timer itself.

Why

Implementation

  • ProtocolGuiGame sends the notice, and the client runs the same AbstractGuiGame code as a local game. The host sends the same notice where it used to repaint the yield prompt.
  • In the protocol, awaitNextInput replaces showWaitingTimer.
  • The client's copy of the yield state is kept accurate, because the prompt now reads it. The host sends stack-yield expiry and the End Turn yield, the client applies a SetMarker it used to drop, and active yields are resent on reconnect.

Testing completed

Played host-and-client games over a socket, with the client taking random yield actions at each priority prompt. Compared the client's prompt, buttons and yield state with the host's real state throughout each game. Two-player and three-player games showed no drift. Dropped and reconnected the client mid-game, including during a yield. Its yield state and prompt were restored each time.


🤖 Generated with Claude Code

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.

The client's copy of the yield state is now kept accurate. shouldAutoYield is read-only on the client. The host now sends stack-yield expiry and the End Turn yield being set, and the client applies a suggested upkeep marker instead of dropping it. The active yields are resent on reconnect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MostCromulent MostCromulent changed the title Compose the "Waiting for" prompt on the network client Compose the "Waiting for" prompt client-side Oct 10, 2026
@tool4ever

Copy link
Copy Markdown
Contributor

Hmn does this change behaviour with libGDX hosts - because it has some ugly bypass in InputConfirm that makes it skip directly into GUI implementation?

Like you mentioned not only showing the name based on priority but also the current prompt might be nice, so maybe the Player could still be sent - though might be tricky if we also want to include AI controllers because they're obviously not hooked up with inputs at all? 🤔

When an input ends, the game waits 250 ms and then shows "Waiting for
<player>..." with both buttons disabled. Mobile FDialog cancelled that
delay every time a dialog opened.

On a network client the cancel used to do nothing, because the host ran
the delay. The client now runs it, so any dialog cancelled it. The host
sends no second notice after a dialog, because a dialog is not an
Input. The client then kept its previous priority prompt, with OK and
End Turn enabled, until the game next asked it something. Every mobile
dialog did this: trigger confirms, choosers, the cleanup discard.

Remove the call. The delay now runs behind the dialog, as it does on
desktop, which dropped the same call in 2015. A mobile dialog draws its
own prompt bar over the match prompt, so the waiting text stays hidden
while the dialog is showing.

The removed call was also the only thing that stopped the elapsed-time
timer when a match ended without a game-finished event. Cancel that
timer in afterGameEnd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MostCromulent

MostCromulent commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Hmn does this change behaviour with libGDX hosts - because it has some ugly bypass in InputConfirm that makes it skip directly into GUI implementation?

Yes, it caused mobile remote to keep showing the old prompt rather than moving to "Waiting for". The cause was mobile FDialog cancelling the wait whenever a dialog opens. Should be addressed by c2dca83.

Like you mentioned not only showing the name based on priority but also the current prompt might be nice, so maybe the Player could still be sent - though might be tricky if we also want to include AI controllers because they're obviously not hooked up with inputs at all? 🤔

Kind of depends what shape we ultimately want for this. A couple of options:

  1. Include "isDeciding" or similar as a trackable player property that clients read when composing the prompt. However the host would have to force a state push to every client each time a dialog opens or closes, which it doesn't now.
  2. Send it as a new specific protocol message rather than piggybacking on state updates.

AI wouldn't be marked in either case, but you're not usually waiting on AI outside of when it holds priority so probably not a big deal?

One issue Claude identifies is that all remote player dialogs pass through ProtocolGuiGame and can be marked there, but host dialogs go straight from PlayerControllerHuman to the local GUI through many different entry points. These would need to be individually wrapped or funneled through a common entry point. Without that, host inputs (targeting, paying, blocking) are still marked, but host dialogs like scry are not.

Identifying the decision which is being waited on (eg "Waiting for MostCromulent to scry"), rather than just naming the player being waited on, would add a layer of complexity on top of the above to correctly flag it. imo thats probably not super high value for work involved

@tool4ever
tool4ever merged commit 8848e87 into Card-Forge:master Oct 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants