mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-21 08:08:32 +02:00
feat: preserve solo runs across production restarts
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user