Files
wownerogue/test/networkTuning_ultra.test.js
T
jwinterm c5147de625 feat(game): multi-level dungeons — descend N levels, timer-driven edge
The fair, on-theme edge lever for slow chains (from the calibration work): a run
now descends `maxDepth` levels instead of one map, so cumulative run length makes
the block-timer bite WITHOUT a giant single dungeon or a cheating-fast monster.

Engine (game.js):
- depth/maxDepth; _generateLevel(depth) builds each level (fresh dungeon + monster
  + FOV), _descend() steps down. Reaching a non-final exit descends (event
  "descend", stays active); only the final exit escapes (event "escaped").
- Treasure lives only in the vault (final level) — intermediate levels race to the
  stairs. Single-level (maxDepth=1) is byte-identical to before.
- Provably fair per level: levelSeed(masterSeed, depth) (provablyFair.js) — level 1
  = master seed, deeper levels salted; the whole descent regenerates from the one
  committed seed. Verified: game levels == regenerateFromSeed(levelSeed(seed,d)).

Pacing (difficultyConfig.js): NETWORK_TUNING now sets LEVELS ∝ block time (GRIN 1,
XMR 2, LTC 2, WOW 4, BTC 8) — replacing the size-scale hack; size + monster stay at
the fair preset. DUNGEON_LEVELS operator override; NETWORK_TUNING_DISABLED → 1 level.

Wiring: socketHandlers afterPlayerMove gets a "descend" branch (no game-over, no
monster move on arrival). Client gameState.js clears the accumulated map/explored/
visible tiles on a depth change so the old level does not ghost through the new one.

Sim: explorer bot resets fog-of-war on descent so multi-level sims stay honest
(full re-calibration of the level counts is the pending follow-up).

Tests: multiLevel_ultra (descent, vault-only treasure, per-level provably-fair,
single-level default, DUNGEON_LEVELS) + networkTuning rewritten for the levels model.
Suite 61 suites / 432 green; end-to-end 4-level WOW run descends 3x then escapes.
2026-07-11 22:26:10 -04:00

60 lines
2.9 KiB
JavaScript

// Guards the per-network difficulty tuning: cryptoType must set LEVELS (multi-level depth ∝ block
// time), size + monster must stay at the fair preset, block-time ordering must hold, and operator
// env overrides must still win.
const {
DIFFICULTY_PRESETS, NETWORK_TUNING, applyNetworkTuning, getDifficultyConfig
} = require('../src/game/difficultyConfig');
const casino = () => ({ ...DIFFICULTY_PRESETS.casino, presetName: 'casino' });
const normal = () => ({ ...DIFFICULTY_PRESETS.normal, presetName: 'normal' });
// Isolate env mutations per test.
const SAVE = {};
const KEYS = ['NETWORK_TUNING_DISABLED', 'DUNGEON_WIDTH', 'DUNGEON_LEVELS', 'MONSTER_SPEED', 'DIFFICULTY_PRESET', 'CRYPTO_TYPE', 'PAYMENTS_ENABLED', 'GAME_MODE'];
beforeEach(() => { KEYS.forEach(k => { SAVE[k] = process.env[k]; delete process.env[k]; }); });
afterEach(() => { KEYS.forEach(k => { if (SAVE[k] === undefined) delete process.env[k]; else process.env[k] = SAVE[k]; }); });
describe('per-network difficulty tuning (multi-level)', () => {
test('tuning sets LEVELS ∝ block time, leaving size + monster at the preset', () => {
const w = applyNetworkTuning(casino(), 'WOW');
expect(w.levels).toBe(NETWORK_TUNING.WOW.levels);
// Size and monster are untouched — depth is the pacing lever, not size/monster-speed.
expect(w.dungeon.width).toBe(DIFFICULTY_PRESETS.casino.dungeon.width);
expect(w.monster.movesPerPlayerMove).toBe(DIFFICULTY_PRESETS.casino.monster.movesPerPlayerMove);
});
test('levels scale with block time: GRIN < XMR < WOW < BTC', () => {
const lv = (n) => applyNetworkTuning(casino(), n).levels;
expect(lv('GRIN')).toBeLessThan(lv('XMR'));
expect(lv('XMR')).toBeLessThanOrEqual(lv('LTC'));
expect(lv('LTC')).toBeLessThan(lv('WOW'));
expect(lv('WOW')).toBeLessThan(lv('BTC'));
expect(lv('GRIN')).toBe(1); // fast chain = single level
});
test('applies to every preset (levels is preset-agnostic pacing)', () => {
expect(applyNetworkTuning(normal(), 'WOW').levels).toBe(NETWORK_TUNING.WOW.levels);
});
test('getDifficultyConfig surfaces levels; defaults to 1 for an unknown/untuned network', () => {
process.env.DIFFICULTY_PRESET = 'casino';
expect(getDifficultyConfig('BTC').levels).toBe(NETWORK_TUNING.BTC.levels);
expect(getDifficultyConfig('DOGE').levels).toBe(1);
});
test('operator env overrides win: DUNGEON_LEVELS + DUNGEON_WIDTH beat the tuning', () => {
process.env.DIFFICULTY_PRESET = 'casino';
process.env.DUNGEON_LEVELS = '3';
process.env.DUNGEON_WIDTH = '99';
const cfg = getDifficultyConfig('BTC');
expect(cfg.levels).toBe(3); // env beats BTC's 8
expect(cfg.dungeon.width).toBe(99);
});
test('NETWORK_TUNING_DISABLED=true → no levels tuning (single level)', () => {
process.env.NETWORK_TUNING_DISABLED = 'true';
process.env.DIFFICULTY_PRESET = 'casino';
expect(getDifficultyConfig('BTC').levels).toBe(1);
});
});