- README: rewrite Difficulty section (per-network LEVELS ∝ block time, WOW=5min, multi-level descent, corrected casino size); document game_event + depth in the Socket.IO table. - NEW docs/BALANCE_SIM.md: the block-timed edge math (exponential deadline), the headless sim over the real engine, what it proved (size self-cancels, fast monster fails on slow chains), and the per-network NETWORK_TUNING model. - NEW docs/MULTI_LEVEL.md: descent mechanic, vault-only treasure, per-level provably-fair seeding, config, file map, open polish. - NEW docs/RENDER_PACKS.md: modes-vs-packs, the RK.fx layer, iso/tiled/zoom/cat. - docs/ARCHITECTURE.md: mark the block-time-difficulty gotcha RESOLVED; add render kit + balance/pacing sections and cross-links. - docs/TODO.md: 2026-07 done/pending section.
2.8 KiB
Multi-level dungeons
A run descends through maxDepth levels instead of a single map. This is the pacing lever that
makes the block-timer provide the house edge on slow chains without a giant dungeon or a cheating
monster (see BALANCE_SIM.md for why).
Mechanic
- Each level is a normal preset-sized dungeon with its own entrance, exit, and a fair monster.
- Reaching a non-final exit takes the stairs down → a fresh level is generated, the player is placed at its entrance, a new monster spawns, and fog-of-war resets. This is not a win.
- Reaching the final level's exit escapes (win).
- Treasure lives only in the vault (the final level). Intermediate levels are a race to the stairs; the 2×/3× treasure payout requires descending all the way and getting back out.
maxDepth = 1(single level) is byte-identical to the pre-multi-level game.
Provably fair across the descent
The whole run regenerates from the one committed seed. Per-level seed
(levelSeed(masterSeed, depth) in provablyFair.js): level 1 = the master seed verbatim
(so single-level games are unchanged), deeper levels salt it (<seed>:L<depth>). Verify any level
with DungeonGenerator.regenerateFromSeed(levelSeed(seed, depth), cryptoType).
Configuration
maxDepth comes from difficultyConfig.levels, set by NETWORK_TUNING per chain
(GRIN 1 · XMR 2 · LTC 2 · WOW 4 · BTC 8, ∝ block time). Operator overrides: DUNGEON_LEVELS=<n>;
NETWORK_TUNING_DISABLED=true forces a single level.
Where it lives
| Concern | Location |
|---|---|
| Level generation + descent | src/game/game.js — _generateLevel(depth), _descend(), the exit branch in movePlayer (returns event: 'descend'), depth/maxDepth in getState |
| Per-level seed | src/game/provablyFair.js — levelSeed() |
| Level count (pacing) | src/game/difficultyConfig.js — NETWORK_TUNING, DUNGEON_LEVELS |
| Server wiring | src/network/socketHandlers.js — afterPlayerMove 'descend' branch (no game-over, no monster move on arrival) |
| Client | html/js/core/gameState.js — clears accumulated map/explored/visible tiles on a depth change so the old level doesn't ghost through the new one |
| Sim | src/sim/simBots.js — explorer bot resets fog-of-war on descent |
| Tests | test/multiLevel_ultra.test.js |
Open polish
- Client "Level N / M" indicator + a "descended" toast (the server sends
depth/maxDepthand agame_event: descend; there is no clientgame_eventhandler yet). - Per-network level counts are sim starting points — pending a full multi-level calibration run.
- Escalating hazards per level (level 1 tame → deep levels lava/poison) — the hazard-tile FX is
already wired (
RENDER_PACKS.md); it needs the generator to place hazards + gameplay stakes.