A GatewayClient-shaped facade over the offline engine, so the same game screen can drive
a local vs_ai game with no backend (the wiring into Game.svelte is B3.2).
- source.ts: LocalSource implements the game-loop subset (GameLoopSource) for a local game
id — gameState/gameHistory via replay, submitPlay/pass/exchange/resign apply the human
move then run the robot (decide(generateMoves)) synchronously, persisting both and
delivering the robot's move through a per-game event emitter (no live stream). hint is
gated to >30 min since the robot's last move; evaluate/checkWord are local. It translates
the UI's glyph space to the engine's index space with the static letters table.
- ruleset.ts: add the static per-variant letters (glyphs), pinned to the Go alphabet —
offline is now fully self-contained (no reliance on a warm server alphabet cache).
- engine.ts: submitPlay (infers the direction like the server SubmitPlay), evaluatePlay +
dictionaryHas for the move preview / word check, and record the main-word coordinate +
the words on a play (for the history MoveRecord).
- source.test.ts: create -> human pass -> synchronous robot reply via the event, the hint
gate, decoded history, and a whole game driven to completion.
Pure additive library code; no runtime behavior change (bundle unchanged).
The offline vs_ai game engine — a faithful TS port of backend/internal/engine that
drives a whole local game with no backend. Composes with the move generator (#188) and
robot strategy (#189) from Phase A; not yet wired into the UI (Phase B2/B3).
- ui/src/lib/localgame/ruleset.ts: static per-variant tile values / bag counts / blank
count, mirrored from rules.go (offline scoring is self-contained; online uses the
server alphabet). Pinned by ruleset.parity.test.ts against a Go fixture.
- bag.ts: the tile bag (fill from counts/blanks, draw-from-end, return+reshuffle) on a
deterministic in-house PRNG — a game replays from its seed, not bit-identical to a
server game (per plan).
- board.ts: the mutable board, satisfying the validator/generator read view + set().
- engine.ts: LocalGame — deal / play (reusing validate.ts) / pass / exchange / resign,
scoreless(6) & out-of-tiles end detection, end-of-game rack penalties, winner; mirrors
game.go. The end-game math is exported as pure functions, pinned against the Go engine
(engine.parity.test.ts, 9 constructed positions).
- engine.test.ts: a full-loop smoke — two robots play a whole vs_ai game to completion
via generateMoves + decide, and it is reproducible from the seed.
- backend: movegen now dumps the per-variant rulesets; a new in-package engine emitter
(endfixture_test.go, env-gated) produces the end-game golden.
Pure additive library code; no runtime behavior change (unused at runtime, bundle unchanged).