Single-slot bus carried only the latest draw, so any state event off-bus
(player regen, master "Ván mới", reload, mode toggle, throttled tab)
silently lost history. Symptom the host hit: regenerating the player
board mid-game wiped all prior auto-crosses.
- master-store.svelte.js: lifted {called, remaining} out of MasterPanel
into shared reactive $state, persisted to loto_master, hydrated once
in +layout.svelte's onMount so panels mount with consistent state
- player-auto-cross.js: new applyMasterCalls helper using cursor-by-index
(vs the retired Date.now-based at timestamp), so callers can pass
lastHandledIndex: 0 to replay master's full history on demand
- PlayerBoard:
- Reads masterState.called directly; cursor advances strictly
- manualUnticks Set tracks user-initiated unticks of called numbers,
suppressing re-cross on replay; persisted to loto_manualUnticks
- "Tạo bảng mới" replays masterState.called onto fresh grid (in both)
- "Xoá đánh dấu" clears + immediately replays in both mode
- Master "Ván mới" detected by called length transitioning >0 → 0,
force-clears player crossed + manualUnticks (locked product call)
- Killed call-bus.svelte.js + auto-tick.js and their tests; helper
surface is fully covered by master-store.test.js (9) and
player-auto-cross.test.js (10), plus 6 new manualUnticks cases in
game-logic.test.js (134 tests passing, was 123 before this refactor)
Targets findings F1, F2, F4, F6, F7, F8, F10 from the 2026-04-30
both-mode consistency audit. F9 (voice ownership) and #20 (multi-tab)
remain out of scope — separate plans to follow.
Lô tô
Bàn số của trò chơi "Lô tô" — SvelteKit app.
Single page (/). The host enables "Chế độ quản trò" in settings to
reveal the master panel inline below their player card; called numbers
get spoken aloud in Vietnamese using bundled MP3 clips (no runtime TTS).
See docs/ for architecture, code standards, and deployment.
Development
npm install
npm run dev
Inside code-server (reverse proxy)
cp .env.example .env.local
# edit .env.local: set CODESERVER_HOST and CODESERVER_PORT
npm run dev:codeserver
Open https://<CODESERVER_HOST>/absproxy/<CODESERVER_PORT>/.
Use /absproxy/{port}/, not /proxy/{port}/ — the latter strips the
path prefix and breaks the SvelteKit base path.
Build
npm run build # default — root basePath, for Cloudflare Pages
npm run build:gh # /loto basePath, for tiennm99.github.io/loto manual export
Static export to build/. Deployed to Cloudflare Pages from main
(set up via the CF dashboard — see docs/deployment-guide.md).
Regenerating audio
Vietnamese voice clips live in static/audio/{voiceId}/. Generated
once with edge-tts (free, no API
key) — runtime never calls TTS. To regenerate (e.g., to add a new voice
that Microsoft ships, or to change wording):
pip install edge-tts
python3 scripts/generate-audio.py
The script auto-discovers every vi-* voice and writes a manifest the
app reads on next reload.