mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-24 01:28:33 +02:00
A browser-based roguelike synchronized with crypto block timing. A run is a race against the next block: reach the exit before the block lands, or the house wins. The same codebase runs the solo dungeon, the social Tavern, and operator-selected PvP and race rulesets, with separate free and paid leaderboards. Self-hostable under the MIT License. See README.md to run it, docs/ARCHITECTURE.md for the design, and docs/DEPLOY.md to operate an instance.
89 lines
5.1 KiB
Markdown
89 lines
5.1 KiB
Markdown
# Multi-level dungeons
|
|
|
|
A run descends through `maxDepth` levels instead of playing a single map. Depth is the pacing lever
|
|
that lets the block timer supply the house edge on slow chains without an oversized dungeon or a
|
|
monster that moves faster than the player. See [`BALANCE_SIM.md`](BALANCE_SIM.md) for the balance
|
|
model behind that choice.
|
|
|
|
## Mechanic
|
|
|
|
- Each level is a normal preset-sized dungeon with its own entrance, exit, and a freshly placed
|
|
monster.
|
|
- Reaching a **non-final** exit takes the **stairs down**: a new level is generated, the player is
|
|
placed at its entrance, a new monster spawns, and fog of war resets. Descending is not a win.
|
|
- Reaching the **final** level's exit **escapes** the dungeon and wins the run.
|
|
- **Treasure exists only on the final level.** Intermediate levels are a pure race to the stairs, so
|
|
the treasure payout multiplier requires descending all the way and getting back out.
|
|
- `maxDepth = 1` is the single-level case: `levelSeed` returns the master seed unchanged at depth 1,
|
|
so a one-level run's layout is exactly what the master seed alone produces.
|
|
- The monster does not take its turn on the arrival move of a descent. `afterPlayerMove` returns
|
|
early on a `descend` result, so the player is never caught by a monster they have not yet seen.
|
|
|
|
## Provable fairness across the descent
|
|
|
|
Every level in a run regenerates from the one committed seed. `levelSeed(masterSeed, depth)` in
|
|
`provablyFair.js` returns the master seed verbatim for level 1 and salts it as `<seed>:L<depth>` for
|
|
deeper levels. Any level can be reproduced with:
|
|
|
|
```js
|
|
DungeonGenerator.regenerateFromSeed(levelSeed(seed, depth), cryptoType, generationOptions);
|
|
```
|
|
|
|
The `Game` constructor commits a layout fingerprint for **every** level of the run up front, before
|
|
play, in `gameProof.layoutFingerprints` (one entry per depth, each carrying `depth`,
|
|
`fingerprintVersion`, `generatorVersion`, and the SHA-256 `fingerprint`). `gameProof.layoutFingerprint`
|
|
remains as a level-1 alias for older verification clients. Because the manifest is complete at
|
|
creation time, a run that ends early still exposes a full, immutable record of the advertised
|
|
descent.
|
|
|
|
Each level is re-fingerprinted as it is actually generated during play, and a mismatch against the
|
|
committed manifest throws. Generator drift inside a live process becomes a hard failure rather than
|
|
an unverifiable served layout.
|
|
|
|
`src/game/fairnessVerifier.js` replays the whole manifest: it rejects a manifest whose depths are
|
|
incomplete or out of order, regenerates each level from its per-depth seed, nulls the treasure on
|
|
every non-final level to match play, and reports per-level fingerprint matches alongside entrance,
|
|
exit, treasure, and dimensions.
|
|
|
|
## Configuration
|
|
|
|
`maxDepth` comes from `difficultyConfig.levels`, set per chain by `NETWORK_TUNING` in
|
|
`src/game/difficultyConfig.js`. Depth scales with block time so that a slower chain gives a longer
|
|
run before the block lands:
|
|
|
|
| Chain | Approx. block time | Levels |
|
|
|---|---|---|
|
|
| GRIN | ~1 min | 1 |
|
|
| XMR | ~2 min | 1 |
|
|
| LTC | ~2.5 min | 2 |
|
|
| WOW | ~5 min | 2 |
|
|
| BTC | ~10 min | 3 |
|
|
|
|
Resolution order for the level count, lowest precedence first: preset (no `levels` key, so the
|
|
effective floor is 1) → `NETWORK_TUNING` for the chain → `customOverrides.levels` → the
|
|
`DUNGEON_LEVELS` environment variable, which always wins and is clamped to a minimum of 1.
|
|
|
|
`NETWORK_TUNING_DISABLED=true` skips per-network tuning entirely, which leaves the run at a single
|
|
level unless `DUNGEON_LEVELS` or an explicit override supplies a count. An unknown `cryptoType` is
|
|
also left untuned and therefore single-level.
|
|
|
|
Dungeon size and monster behaviour are not varied by depth: `applyNetworkTuning` folds only the
|
|
level count onto the resolved difficulty preset, and every level uses the same preset dimensions and
|
|
the same fair monster settings.
|
|
|
|
## Where it lives
|
|
|
|
| Concern | Location |
|
|
|---|---|
|
|
| Level generation and descent | `src/game/game.js`: `_generateLevel(depth)`, `_descend()`, the exit branch in `movePlayer` returning `event: 'descend'`, and `depth`/`maxDepth` in `getState` |
|
|
| All-level fingerprint manifest | `src/game/game.js`: `_buildLayoutFingerprintManifest()`, `getProofContext()` |
|
|
| Per-level seed | `src/game/provablyFair.js`: `levelSeed()` |
|
|
| Level count | `src/game/difficultyConfig.js`: `NETWORK_TUNING`, `applyNetworkTuning()`, `DUNGEON_LEVELS` |
|
|
| Verification | `src/game/fairnessVerifier.js` |
|
|
| Server wiring | `src/network/socketHandlers.js`: the `'descend'` branch in `afterPlayerMove` (no game over, no monster move on arrival) |
|
|
| Event emission | `src/game/movementManager.js`: emits `game_event` with `event`, `depth`, `maxDepth` after the `game_update` |
|
|
| Client state | `html/js/core/gameState.js`: clears accumulated map, explored, and visible tiles on a `depth` change so the previous level does not ghost through the new one |
|
|
| Client feedback | `html/js/network/socketHandlers.js`: the `game_event` handler renders the descend message and on-screen banner with `Level N / M` |
|
|
| Sim | `src/sim/simBots.js`: the explorer bot clears its known-tile set and remembered objectives on a depth change |
|
|
| Tests | `test/multiLevel_ultra.test.js`, `test/networkTuning_ultra.test.js` |
|