Files
wownerogue/test/dungeonGeneratorVersion.test.js
jwinterm fad2a695bd Wownerogue
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.
2026-07-27 08:23:01 -04:00

116 lines
4.8 KiB
JavaScript

const crypto = require('crypto');
const DungeonGenerator = require('../src/game/dungeon');
const Game = require('../src/game/game');
const { attachDungeonVerification } = require('../src/game/fairnessVerifier');
const LEGACY_OVERLAP_SEED = '5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9';
const LEGACY_OVERLAP_FINGERPRINT = '9fd69a09c5f7a233b03538142994dd932afb4f2d6cfa19baab7041d5303edb50';
function objectiveKeys(dungeon) {
return [dungeon.entrance, dungeon.exit, dungeon.treasure].map(point => point.join(','));
}
describe('versioned playable dungeon objectives', () => {
const saved = {};
beforeAll(() => {
for (const key of ['DIFFICULTY_PRESET', 'DUNGEON_LEVELS', 'GAME_MODE', 'PAYMENTS_ENABLED']) {
saved[key] = process.env[key];
}
process.env.DIFFICULTY_PRESET = 'easy';
process.env.DUNGEON_LEVELS = '1';
process.env.GAME_MODE = 'FREE';
process.env.PAYMENTS_ENABLED = 'false';
});
afterAll(() => {
for (const [key, value] of Object.entries(saved)) {
if (value === undefined) delete process.env[key];
else process.env[key] = value;
}
});
test('v2 deterministically retries a known v1 entrance/exit overlap', () => {
const v1 = DungeonGenerator.regenerateFromSeed(LEGACY_OVERLAP_SEED, 'XMR', {
width: 30,
height: 15,
generatorVersion: 'dungeon-generator-v1'
});
const v2 = DungeonGenerator.regenerateFromSeed(LEGACY_OVERLAP_SEED, 'XMR', {
width: 30,
height: 15,
generatorVersion: 'dungeon-generator-v2'
});
expect(v1.entrance).toEqual(v1.exit);
expect([v1.entrance, v1.exit, v1.treasure]).toEqual([[17, 8], [17, 8], [16, 8]]);
expect(DungeonGenerator.layoutFingerprint(v1)).toBe(LEGACY_OVERLAP_FINGERPRINT);
expect(new Set(objectiveKeys(v1)).size).toBeLessThan(3);
expect(new Set(objectiveKeys(v2)).size).toBe(3);
expect(DungeonGenerator.hasDistinctReachableObjectives(v2, {
primaryFloor: "'1",
secondaryFloor: "'2"
})).toBe(true);
const repeated = DungeonGenerator.regenerateFromSeed(LEGACY_OVERLAP_SEED, 'XMR', {
width: 30,
height: 15,
generatorVersion: 'dungeon-generator-v2'
});
expect(DungeonGenerator.layoutFingerprint(repeated)).toBe(DungeonGenerator.layoutFingerprint(v2));
});
test('easy-profile v2 samples always have distinct reachable objectives', () => {
for (let i = 0; i < 100; i += 1) {
const seed = crypto.createHash('sha256').update(`objective-sample-${i}`).digest('hex');
const dungeon = DungeonGenerator.regenerateFromSeed(seed, 'XMR', {
width: 30,
height: 15,
generatorVersion: DungeonGenerator.GENERATOR_VERSION
});
expect(new Set(objectiveKeys(dungeon)).size).toBe(3);
expect(DungeonGenerator.hasDistinctReachableObjectives(dungeon, {
primaryFloor: "'1",
secondaryFloor: "'2"
})).toBe(true);
}
});
test('historical v1 fingerprints remain verifiable after v2 becomes current', () => {
const legacy = DungeonGenerator.regenerateFromSeed(LEGACY_OVERLAP_SEED, 'XMR', {
width: 30,
height: 15,
generatorVersion: 'dungeon-generator-v1'
});
expect(DungeonGenerator.layoutFingerprint(legacy)).toBe(LEGACY_OVERLAP_FINGERPRINT);
const result = attachDungeonVerification({ valid: true }, {
effectiveSeed: LEGACY_OVERLAP_SEED,
proofContext: {
cryptoType: 'XMR',
maxDepth: 1,
generatorVersion: 'dungeon-generator-v1',
generationOptions: { width: 30, height: 15 }
},
expectedFingerprint: LEGACY_OVERLAP_FINGERPRINT,
generatorVersion: 'dungeon-generator-v1'
});
expect(result.valid).toBe(true);
expect(result.generatorVersion).toBe('dungeon-generator-v1');
expect(result.layoutMatches).toBe(true);
});
test('public game options cannot select a historical generator', () => {
const game = Game.createStandardGame('public-socket', { id: 'public-socket' }, {
cryptoType: 'XMR',
width: 30,
height: 15,
generatorVersion: 'dungeon-generator-v1'
});
expect(game.generatorVersion).toBe(DungeonGenerator.GENERATOR_VERSION);
expect(game.gameConfig).not.toHaveProperty('generatorVersion');
expect(game.gameProof.generatorVersion).toBe(DungeonGenerator.GENERATOR_VERSION);
expect(game.gameProof.context.generatorVersion).toBe(DungeonGenerator.GENERATOR_VERSION);
});
});