fix(hint): stop the hint count going stale across games
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m20s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m20s
The in-game hint badge re-fetched on entry and, for a game where it was the player's turn, showed a too-high count that "reset" (e.g. back to 11) — the wallet hint spent in another game was not reflected. Root cause: the server sends one hints_remaining = per-game allowance + global wallet, and the client cached that combined number per game. The wallet is global, so spending a wallet hint in one game left every other game's cached count stale (a my-turn game holds the stalest value: the opponent-moved delta preserves the old number, whereas a game you just moved in re-cached a fresh one). The backend allowance-then-wallet spend order was already correct. Fix: split the two. StateView/HintResult gain a trailing wallet_balance field (the global wallet alone); the client derives the per-game allowance as hints_remaining - wallet_balance (stable, cacheable) and reads the wallet live from the profile, refreshing it from every state/hint response. The badge is allowance + live wallet, so a wallet hint anywhere updates every game at once. - wire: scrabble.fbs StateView/HintResult + pkg/wire.BuildStateView (the single encoder for both the gateway transcode and the backend's event StateView), gateway encode + resp structs, regen. - backend: game StateView/HintResult + service (GameState/Hint) + eventwire + notify PlayerState/encode + server DTOs. - ui: lib/hints.ts (pure hintsLeft), Game.svelte (badge + syncWallet on load/hint, carry wallet_balance through applyMoveResult), codec/model, mock. - docs: ARCHITECTURE §Hint. Tests: hints.ts unit (incl. the staleness case), TestHintPolicy extended (wallet_balance + allowance-first), gateway state/hint round-trips.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
// Hint-count derivation, kept out of the .svelte component so it is unit-testable.
|
||||
//
|
||||
// The badge shows the per-game hint allowance remaining plus the player's global hint
|
||||
// wallet. The server's hints_remaining folds the two together, but the wallet is global —
|
||||
// shared across every game — so caching the combined number per game makes it go stale the
|
||||
// moment a wallet hint is spent in another game. We therefore split it: the per-game
|
||||
// allowance is hints_remaining - wallet_balance (both from the same fetch, so it is stable
|
||||
// and cacheable), and the wallet is read live from the global profile, never the per-game
|
||||
// snapshot.
|
||||
|
||||
import type { StateView } from './model';
|
||||
|
||||
/**
|
||||
* hintsLeft is the hint count for the badge: the per-game allowance remaining (the view's
|
||||
* hints_remaining minus the wallet snapshot baked into that same view) plus the live global
|
||||
* wallet balance. Passing the live wallet (not view.walletBalance) is what keeps the count
|
||||
* correct when a wallet hint was spent in another game since this view was fetched.
|
||||
*/
|
||||
export function hintsLeft(
|
||||
view: Pick<StateView, 'hintsRemaining' | 'walletBalance'> | null,
|
||||
walletBalance: number,
|
||||
): number {
|
||||
if (!view) return 0;
|
||||
const allowance = Math.max(0, view.hintsRemaining - view.walletBalance);
|
||||
return allowance + Math.max(0, walletBalance);
|
||||
}
|
||||
Reference in New Issue
Block a user