fix(game): show granted/bought hints in-game — finish the D31 wire removal
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m11s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m58s

The in-game hint badge ignored the payments hint wallet: loading a game clobbered
app.profile.hintBalance with the deprecated StateView.wallet_balance (zeroed in the
D31 domain removal but left in the protocol as a dead 0, then synced over the real
balance at game load).

Finish the removal honestly. StateView carries the per-game allowance alone
(hints_remaining); the purchasable wallet lives solely on the profile and the client
adds it (lib/hints). Removed StateView.wallet_balance across every layer — backend
StateView/DTO, notify.PlayerState (live events), gateway StateResp + wire.StateView,
the client model/codec/mock/localgame — and dropped the now-unused wallet arg from
hintsRemaining. The FBS field is tombstoned `(deprecated)` (not deleted) so the vtable
slots after it stay stable across a rolling deploy; no accessor is generated. HintResult
keeps wallet_balance (the real post-spend payments balance the client adopts into the
profile). The StateView type no longer has walletBalance, so the clobber cannot return
without a compile error.

Tests: hintsRemaining (2-arg); hints.hintsLeft (allowance + live wallet, no strip); the
game state/hint integration (allowance-only HintsRemaining); gateway transcode; FBS regen.
This commit is contained in:
Ilia Denisov
2026-07-10 06:26:49 +02:00
parent d2d6955cbf
commit 82648a4398
25 changed files with 76 additions and 130 deletions
+5 -5
View File
@@ -251,7 +251,8 @@
view = st;
// Anchor the vs_ai idle-hint countdown to the freshly fetched seconds-left (0 = open / non-vs_ai).
armHintGate(st.game.vsAi ? (st.hintUnlockLeftSeconds ?? 0) : 0);
syncWallet(st.walletBalance);
// The game state no longer carries the hint wallet (it lives on the profile); do not touch
// app.profile.hintBalance here — that would clobber the authoritative payments balance.
// Seed the unread flag from the authoritative state (the live stream only raises it).
seedChatUnread(id, st.game.unreadChat, st.game.unreadMessages);
moves = hist.moves;
@@ -833,10 +834,9 @@
seat: r.move.player,
rack: r.rack,
bagLen: r.bagLen,
// A move is not a hint, so the per-game allowance and the wallet are unchanged: carry both
// forward (their difference is the stable allowance; the badge adds the live wallet).
// A move is not a hint, so the per-game allowance is unchanged: carry it forward. The badge
// adds the live wallet from the profile.
hintsRemaining: view?.hintsRemaining ?? 0,
walletBalance: view?.walletBalance ?? 0,
};
// The move result is an authoritative per-viewer view: a nudge the actor just answered by
// moving is already cleared server-side, so reconcile the unread flag from it.
@@ -1047,7 +1047,7 @@
recenter++;
}
if (isCoarse() && !landscape) zoomed = true;
view = { ...view, hintsRemaining: h.hintsRemaining, walletBalance: h.walletBalance };
view = { ...view, hintsRemaining: h.hintsRemaining };
syncWallet(h.walletBalance);
// The hint is the engine's own top-ranked, fully scored legal move: reuse it as the
// preview instead of a redundant evaluate (same engine call, same placement). Cancel any