feat(hint): idle-gated wallet-free vs_ai hint on a monotonic clock (B4) #207

Merged
developer merged 3 commits from feature/offline-hint-gate into development 2026-07-06 21:46:19 +00:00
Owner

Fast-follow (e): the local vs_ai hint presents its 30-min idle gate instead of erroring.

Behaviour: vs_ai hints are unlimited + wallet-free, idle-gated (unlock ~30 min into a stuck turn; the human first move is exempt). Gated -> an enabled button with a 🔒 badge; a tap shows «Будет доступно через N мин.» / «Available in N min.»; the lock lifts live.

Clock handling (revised per owner to Option A): the wait now persists across relaunches — the unlock is a wall-clock instant (hintUnlockAtMs, stamped off the robot reply, stored on the record, carried on the move delta). Read through a sanitiser (cap at now + window) so a device clock set back cannot freeze the gate; a clock set forward just opens the hint early (harmless in a solo game). We accept that rare clock-change case for a wait that survives leaving/reopening the app. (An earlier revision used a session-scoped monotonic clock that reset on reload; the owner chose persistence.)

Online vs_ai will gate the same way from the server clock — a separate follow-up.

Verified: check 0 / unit 488 / e2e 198 (both engines, incl. lock-survives-reload) / app entry 114.1/115 KB.

Fast-follow (e): the local vs_ai hint presents its 30-min idle gate instead of erroring. **Behaviour:** vs_ai hints are unlimited + wallet-free, idle-gated (unlock ~30 min into a stuck turn; the human first move is exempt). Gated -> an enabled button with a 🔒 badge; a tap shows «Будет доступно через N мин.» / «Available in N min.»; the lock lifts live. **Clock handling (revised per owner to Option A):** the wait now **persists across relaunches** — the unlock is a wall-clock instant (`hintUnlockAtMs`, stamped off the robot reply, stored on the record, carried on the move delta). Read through a **sanitiser** (cap at `now + window`) so a device clock set **back** cannot freeze the gate; a clock set **forward** just opens the hint early (harmless in a solo game). We accept that rare clock-change case for a wait that survives leaving/reopening the app. (An earlier revision used a session-scoped monotonic clock that reset on reload; the owner chose persistence.) Online vs_ai will gate the same way from the **server** clock — a separate follow-up. Verified: check 0 / unit 488 / e2e 198 (both engines, incl. lock-survives-reload) / app entry 114.1/115 KB.
developer added 1 commit 2026-07-06 20:03:46 +00:00
feat(hint): idle-gated wallet-free vs_ai hint on a monotonic clock (B4)
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m11s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
42a6308261
A vs_ai hint is now unlimited and wallet-free, but idle-gated as an anti-frustration
aid: it unlocks only after the player has been stuck ~30 min on a turn (timed from the
robot's last move; the human's first move, before the robot has played, is exempt).
While gated the hint button carries a small lock badge and a tap shows the remaining
minutes ('Available in N min.'); the lock lifts live at the mark.

The gate is enforced CLIENT-SIDE against a MONOTONIC clock (performance.now()), never a
wall-clock timestamp: a device clock the player controls (or an auto-sync) must not be
able to open or freeze it. The wait is therefore session-scoped -- a reload restarts it
(there is no tamper-proof way to carry idle time across a relaunch without a wall clock).
An online vs_ai game will gate the same way but from the server's clock (a follow-up).

- lib/hints.ts: HINT_GATE_MS + pure hintGateRemainingMs (monotonic) + hintLockMinutes;
  removed the wall-clock hintRemainingMs. Unit-tested red->green.
- Game.svelte: the monotonic gate (hintGateStart/monoNow, armed on the turn change), a
  vs_ai hint button (plain: no confirm, no count; lock badge + gated tap -> toast), a
  live 10s tick.
- localgame: removed the wall-clock hint gate and the now-dead robotLastMoveAtUnix field
  from source.ts/serialize.ts (the client is authoritative); hint() just serves the top
  move.
- i18n game.hintLockedIn.
- offline.spec.ts: assert the first move is un-gated and the lock arms after the robot moves.
- Docs FUNCTIONAL(+_ru) + ARCHITECTURE; bundle budget 114->115 (game-screen feature).
developer added 1 commit 2026-07-06 20:34:52 +00:00
feat(hint): persist the vs_ai idle-hint wait (wall-clock + read sanitiser)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m28s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m44s
ff486c80f8
Per the owner's call, the idle-hint gate now PERSISTS across leaving and reopening the
app, instead of the session-scoped monotonic clock: the unlock is a wall-clock instant
(hintUnlockAtMs) stamped from the robot's reply, stored on the local record, carried on
the game view + the opponent_moved move delta, and read through a sanitiser that caps it
at now + the window. So:
- the wait survives a relaunch (a stuck turn is not forgotten);
- a device clock set BACK cannot freeze the gate (the cap bounds the remaining to the
  window and self-heals on the next read);
- a clock set FORWARD just opens the hint early -- accepted as harmless for a solo game.

- lib/hints.ts hintGateRemainingMs now takes the unlock instant + clamps to the window.
- localgame: re-add hintUnlockAtMs to the record/meta; stamp it off the robot's reply;
  sanitise on read (a shared hintUnlock helper feeds stateView + the opponent_moved event).
- gamedelta + PushEvent: the move delta carries hintUnlockAtMs so the view stays fresh.
- Game.svelte: hintUnlockAt derives from the view; the tick + lock + toast unchanged.
- offline.spec.ts: also assert the lock survives a reload (the wait persisted).
- Docs FUNCTIONAL(+_ru)/ARCHITECTURE updated (persist + sanitiser, not monotonic-resets).
owner approved these changes 2026-07-06 21:18:21 +00:00
Dismissed
developer added 1 commit 2026-07-06 21:39:02 +00:00
Merge branch 'development' into feature/offline-hint-gate
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m28s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
537a265409
# Conflicts:
#	ui/src/lib/localgame/source.ts
developer dismissed owner's review 2026-07-06 21:39:02 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

owner approved these changes 2026-07-06 21:45:57 +00:00
developer merged commit ee7ff06403 into development 2026-07-06 21:46:19 +00:00
developer deleted branch feature/offline-hint-gate 2026-07-06 21:46:19 +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#207