God of Sparrow 太歲 — Developer Wiki · single source of truth for contributors (human or LLM)
HK Mahjong roguelite · single-file HTML5 game · targeting Steam 0.1 (PC) → 0.2 (macOS + Mobile)
Design & Test Pages
🀄 The Gamesparrow.html — the entire game (deployed as index.html)
📖 Player Wikiroguelite-style reference: rules, 144 charms, items, 39 bosses. Auto-generated from game data.
⚙ How the Game WorksMechanics reference written from the live code: tiles, blind flow, scoring/fan, run structure, dice, economy, walls & stakes, controls
🎨 Art, Sound & Music ReviewEvery shipped image + music bed at review size, captioned from game data, orphan detection. Regenerate: tools/build-art-review.py
🎚️ Audio Test BenchEvery SFX + all music rooms with scrub timelines & shared volume settings
📈 Economy SimulatorBalance/design simulator for gold, targets and scaling
⚖️ Design ComparisonSystem-by-system design comparison (items · relics · bosses · consumables) + a sibling-project side-by-side. Local/dev only — not deployed to the public site.
📋 ChangelogEvery change, newest first — REQUIRED reading before touching code
Architecture
| File | What it is |
sparrow.html | The whole game — CSS + HTML + JS in one file (~14k lines). Deployed to web as index.html. Key data blocks: JOKER_POOL (144 charms), CONSUMABLE_POOL (66), VOUCHER_POOL (31), BOSS_POOL (39) + BOSS_LORE, WALL_TYPES/STAKE_TYPES (8+8), SIGIL_TYPES, EDITION_TYPES, and the game object G. |
audio.js | Web Audio engine (window.GameAudio): procedural SFX (incl. mahjong clack()), scoring ensemble, music-room manager that swaps in audio/music/m_<room>.ogg (7 rooms) over a procedural fallback pad. Per-bus volumes persisted in localStorage. Audio hooks are monkey-patched onto G at the bottom of sparrow.html. |
serve-mahjong.js | Dev static server on 0.0.0.0:5502 (Tailscale-reachable). No-cache headers, audio MIME types, HTTP Range support. MUST serve .ogg as audio/ogg. |
images/ | 39 boss portraits (boss_<id>.webp, 560×840), landing art, transparent title PNGs. Top level only is deployed. |
audio/music/ | 8 MusicGen beds (m_title/blind/boss/shop/pack/victory/defeat/credits.ogg) — seamless loops. |
tools/ | Dev tooling — see Tools below. |
electron/ | Steam wrapper scaffold (Electron, mirrors the Melder project approach). |
STEAM_PLAN.md | The release plan — roadmap, role-by-role analyses, art manifest, LLM handoff instructions. |
Golden Rules (do these EVERY session)
- Backup before editing: copy
sparrow.html → revisions/MMddyyyyHHmm.html.
- Every save: update
changelog.txt (top, newest first) and the in-game tutorial if gameplay changed.
- Deploy with
sync-once.ps1 (or tools/deploy.bat) — pushes game→index.html, audio, wiki, images/ + audio/music/ diffs to W:\sparrow and SFTP.
- NEVER sync
imagegen/ to the web, copy API keys out of ~/.claude/openai-config.json, or echo keys anywhere.
- Use the Tailscale IP
100.122.243.71:5502 (not localhost) in anything shown to the user.
- After regenerating data (charms/bosses/etc.), run
tools/build-wiki.bat so the player wiki stays in sync.
Tools (tools/)
| Tool | Purpose |
start-server.bat | Run the dev server (auto-restarts if it crashes) |
deploy.bat | One-click deploy (wraps sync-once.ps1: backup + W: + SFTP) |
backup.bat | Timestamped revision backup of sparrow.html |
smoke-test.bat / smoke-test.js | 46 CI-style checks: JS syntax, asset presence (39 portraits, 8 music beds, titles), data integrity, server MIME config, security leak scan, regression guards. Run before every deploy (deploy.bat gates on it). |
qa-data.js | 20 deep data-spec checks (rarity distribution, unique ids, fx/apply presence, voucher require-chains). See QA_MATRIX.md. |
check-syntax.js | Parse-checks every inline <script> in sparrow.html without executing — catches broken edits instantly. |
make_steam_art.py | Builds all 8 Steam capsule sizes + electron/icon.png/.ico from a master image. |
build-wiki.bat / build-wiki.js | Regenerate wiki-data.json from sparrow.html (validates portraits + counts) |
gen-music.bat / gen_music.py | Regenerate music beds (MusicGen-large, seamless loops) + transcode to OGG. Args = track names for single-track re-rolls. |
electron/build-app.bat | Package the Steam build (Electron → .exe; .app on macOS) |
In-game QA hooks
- 🤖 Auto + 🧪 Stress buttons on the landing screen — bot plays/stress-tests the game.
- Cheat menu (top-right on landing): boss viewer, tile builder, sigil/edition application, seed control.
- Seeded runs: 5-digit seed input on Run Setup → deterministic RNG (
RNG object) for repro cases.
- Console:
window.G, window._validate, window._findAutoOptions are exposed for debugging.
Asset pipelines (local, zero-cost)
IMAGES imagegen server (port 5511, gpt-image; NEVER deploy this folder) → PNG → images/
MUSIC tools/gen_music.py → MusicGen-large (audiocraft venv, RTX 5090) → seamless-loop WAV → ffmpeg → audio/music/*.ogg
SFX procedural in audio.js (no files needed) — audition via audio-test.html
Rebuild From Scratch — full tech-stack spec
If this codebase were lost, or you are a new developer/LLM with no access to prior sessions,
this section + wiki-data.json (all game data) + images/ + audio/ is enough to rebuild the game.
1. Stack (deliberately boring)
Runtime Vanilla HTML + CSS + ES2020 JS. ONE file: sparrow.html (~14k lines). No framework, no build step, no npm deps.
Audio Web Audio API (audio.js): procedural SFX synth + <audio>-element music rooms w/ WebAudio gain crossfade.
Persistence localStorage only: save blob (gos_save), unlocks, volumes (gos_vol/gos_musicvol/gos_sfxvol/gos_muted).
Rendering Absolutely-positioned DOM divs on a 1280×720 virtual canvas, scaled by scaleGameRoot(); NO <canvas>.
Desktop Electron wrapper (electron/) — loads the same files unmodified. Target: Steam Deck-class HW.
Server Any static file server. MUST serve .ogg as audio/ogg + support Range requests (see serve-mahjong.js).
2. Source-of-truth data (all exported to wiki-data.json)
JOKER_POOL[144] {id, icon, name(EN 中文), rarity C|U|R|L, cost, col, desc, fx(ctx)…, bane?} charms
CONSUMABLE_POOL[66] {id, icon, name, type gem|scroll|rune|spirit, cost, col, desc, apply(G)}
VOUCHER_POOL[31] {id, icon, name, cost, tier, requires?, desc, effect flags}
WALL_TYPES[8] decks: {id, icon, name, col, desc, reward, mod flags} STAKE_TYPES[8] additive difficulty
EDITION_TYPES / SIGIL_TYPES BOSS_POOL[39] {id, icon, name, minAnte, descFn, effect flags} + portraits images/boss_<id>.webp
MJ_SUITS/MJ_HONOURS/MJ_FLOWERS tile definitions ANTE_BASE[8] score targets BLIND_NAMES
3. Core algorithms (the 20% that is the game)
RNG Mulberry32 seeded (RNG object) — EVERYTHING random flows through it so runs are seed-reproducible.
Deal Wall = 4×(suits+honours)+flowers, minus wall-type removals; Fisher-Yates shuffle w/ RNG.
Validate validate(meldTiles): Pung=3 identical · Kong=4 · Chow=3+ consecutive same suit · wilds adopt meld identity.
GUARD: [].every() returns TRUE — every all-X check must test length>0 first (bit us: 1.2B exploit).
Scoring eat = Σ(meld baseChips + tile enhancements) × (mult from fan 番 table + charms).
Fan table: HK classics (碰碰糊+3, 混一色+3, 清一色+7, 字一色/清老頭 limit=20…); min-fan gate rises w/ ante.
Blinds 8 antes × (Small ×1.0 · Big ×1.5 · Boss ×2.0 target from ANTE_BASE, stake-scaled). Boss applies its effect flags.
Economy reward = base + $1/unused draw + interest floor(gold/5) cap $5 (voucher-raisable); shop = 2 charms+3 items+1 voucher, reroll cost escalates.
Charms fx(ctx) hooks fire at named phases (onScore, onDraw, perMeld, blindStart/End…). Neighbor charms read the
charm ROW ORDER — order is a mechanic (drag-to-reorder).
Save JSON blob of full run state incl. RNG cursor; save each blind end; NEEDS saveVersion guard (top 0.1 risk).
4. Rebuild order (tested path, ~7 milestones)
1 tiles+deal+hand UI → 2 meld validate+eat scoring w/ fan table → 3 blind loop+antes+gameover
4 shop+economy+consumables → 5 charm fx engine (phase hooks) → 6 bosses+stakes+editions/sigils
7 audio+saves+landing/setup UI. At each step: add a smoke-test assertion; keep bilingual EN/繁中 strings.
5. Asset regeneration (no hand art needed)
Item icons tools/gen-icons.py (PIL) → images/ic_<id>.webp — 265 tile-style cards from wiki-data.json. Deterministic.
Boss art imagegen server (gpt-image) — prompts per boss in BOSSES.md style: HK Teddyboy manhua portrait 560×840.
Titles white brush lettering on black → alpha-key + tint + outline (scratchpad scripts in changelog Jul 4).
Music tools/gen-music.bat [room] → MusicGen-large prompts per room (genre table in gen_music.py). 8 rooms.
Steam art tools/make_steam_art.py from a master poster → 8 capsule sizes + electron icon.
For LLM contributors: read OPUS_HANDOFF.md then STEAM_PLAN.md first — roadmap,
done-vs-missing, exact conventions, and every known trap. Do not restructure sparrow.html into modules before 0.1;
single-file is a deliberate choice until the Electron wrapper stabilises.