feat(hint): unify the vs_ai idle hint online + offline (server-enforced, monotonic) #209

Merged
developer merged 2 commits from feature/online-vsai-hint into development 2026-07-06 23:04:38 +00:00
Owner

Fixes the broken online vs_ai hint and delivers the unified model you asked for.

The breakage: #207 made the vs_ai hint button always-enabled + wallet-free (assuming all vs_ai = the offline idle-gate), but the backend still served online vs_ai from the allowance/wallet — so over-clicking hit ErrNoHintsLeft -> a generic error toast. Now online vs_ai uses the same idle-gate as offline.

Design (your calls):

  • vs_ai hints: unlimited, wallet-free, idle-gated (~30 min since the robot last move; first move exempt). Counted toward no hint statistic.
  • Gate server-enforced online: the backend refuses an early hint (hint_locked) from its own clock (tamper-proof); the client pre-gates + handles the backstop.
  • The source reports hint_unlock_left_seconds (not an absolute time — your call, to dodge client clock skew); the client counts down on a MONOTONIC clock (performance.now()) anchored when it lands (on load from the view; to the full window when the robot moves). Immune to a live client clock change; a relaunch re-reads a fresh value.

Shape: backend Hint/GameState + hintUnlockLeftSeconds helper (unit-tested); FlatBuffers hint_unlock_left_seconds (additive) through pkg/wire + gateway (round-trip test); client gate reworked from #207 absolute hintUnlockAtMs to the seconds-left + monotonic anchor (offline record keeps the absolute for persistence).

Verified: go build/vet + game/server/transcode tests (+ new); ui check 0 / unit 490 / e2e 198 (one pre-existing webkit offline flake, CI retries) / app entry 114.2/115 KB.

**Fixes the broken online vs_ai hint** and delivers the unified model you asked for. **The breakage:** #207 made the vs_ai hint button always-enabled + wallet-free (assuming *all* vs_ai = the offline idle-gate), but the backend still served online vs_ai from the allowance/wallet — so over-clicking hit `ErrNoHintsLeft` -> a generic error toast. Now online vs_ai uses the **same idle-gate** as offline. **Design (your calls):** - vs_ai hints: **unlimited, wallet-free, idle-gated** (~30 min since the robot last move; first move exempt). **Counted toward no hint statistic.** - Gate **server-enforced** online: the backend refuses an early hint (`hint_locked`) from **its own clock** (tamper-proof); the client pre-gates + handles the backstop. - The source reports **`hint_unlock_left_seconds`** (not an absolute time — your call, to dodge client clock skew); the **client counts down on a MONOTONIC clock** (`performance.now()`) anchored when it lands (on load from the view; to the full window when the robot moves). Immune to a live client clock change; a relaunch re-reads a fresh value. **Shape:** backend `Hint`/`GameState` + `hintUnlockLeftSeconds` helper (unit-tested); FlatBuffers `hint_unlock_left_seconds` (additive) through pkg/wire + gateway (round-trip test); client gate reworked from #207 absolute `hintUnlockAtMs` to the seconds-left + monotonic anchor (offline record keeps the absolute for persistence). Verified: go build/vet + game/server/transcode tests (+ new); ui check 0 / unit 490 / e2e 198 (one pre-existing webkit offline flake, CI retries) / app entry 114.2/115 KB.
developer added 1 commit 2026-07-06 22:43:00 +00:00
feat(hint): unify the vs_ai idle hint online + offline (server-enforced, monotonic)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m28s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m43s
7fc1301b31
Online vs_ai hints were broken: #207 made the vs_ai hint button always-enabled and
wallet-free (assuming all vs_ai = the offline idle-gate), but the backend still served
online vs_ai from the allowance/wallet, so over-clicking hit ErrNoHintsLeft -> a generic
error toast. Now online vs_ai uses the SAME idle-gate model as offline.

Backend: Hint() for a vs_ai game skips the allowance/wallet and increments no hints_used
(owner: vs_ai counts toward no hint statistic), and is idle-gated from the SERVER clock --
it returns ErrHintLocked (code hint_locked) until the robot's last move + 30 min, else
serves the top move. GameState/StateView expose hint_unlock_left_seconds (server-computed
seconds remaining; 0 for a human game / first move / not-your-turn). Pure helper
hintUnlockLeftSeconds unit-tested.

Wire: StateView gains hint_unlock_left_seconds (FlatBuffers, additive); pkg/wire + gateway
transcode carry it (round-trip test).

Client: the gate counts down from a MONOTONIC clock (performance.now()) anchored to the
source's seconds-left when it lands (on load from the view; to the full window when the
robot moves), so a client clock change cannot skew it and a relaunch re-reads a fresh
value. The vs_ai hint button (online + offline) shows the lock + toast; doHint handles the
hint_locked backstop by re-syncing. Replaces #207's absolute hintUnlockAtMs on the
wire/model/delta (the offline record keeps the absolute for persistence; the view exposes
seconds-left).

Docs FUNCTIONAL(+_ru)/ARCHITECTURE updated. Verified: go build/vet + game/server/transcode
tests (+ new hintUnlockLeftSeconds); ui check 0 / unit 490 / e2e 198 (one pre-existing
webkit offline flake) / app entry 114.2/115.
developer added 1 commit 2026-07-06 22:59:07 +00:00
fix(hint): arm the vs_ai idle-gate from the warm cache so the lock shows without a delay
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 17s
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 1m41s
53311cbc95
Entering a vs_ai game from the lobby armed the idle-hint countdown only in load() (after
the gameState round-trip), so the lock popped in after a visible delay. Arm it on the
instant warm-cache render in onMount too — the preloadGames-warmed StateView carries
hint_unlock_left_seconds; load() then refreshes the snapshot. A first move (0 seconds
left) stays open.
owner approved these changes 2026-07-06 23:04:18 +00:00
developer merged commit ef8a32bb82 into development 2026-07-06 23:04:38 +00:00
developer deleted branch feature/online-vsai-hint 2026-07-06 23:04:38 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#209