diablo2-web/tests/drlg-act1-oracle.test.ts

97 lines
4.0 KiB
TypeScript

import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { gunzipSync } from 'node:zlib'
import { describe, expect, it } from 'vitest'
import { dumpLevelToDs1 } from '../src/game/drlg/adapter.ts'
import { DrlgTraceWriter, dumpAct1, type DrlgDump } from '../src/game/drlg/drlg-dump.ts'
import { createDrlgEnv } from '../src/game/drlg/drlg-source.ts'
import { loadDrlgTables } from '../src/game/drlg/drlg-tables.ts'
import {
firstDiff,
firstTraceDivergence,
formatDiff,
fsDrlgSource,
ORACLE_BIN,
ORACLE_DATA,
runOracle,
} from '../scripts/lib/drlg-oracle.ts'
const ACT1_OUTDOOR_LEVELS = [2, 3, 4, 5, 6, 7, 17, 39]
const FIXTURE_DIR = resolve(import.meta.dirname, 'fixtures/d2moo-oracle')
const FIXTURE_SEEDS = [0x12345678, 0x5eed0100, 0x5eed0101, 0x5eed0102]
describe('Act I outdoor DRLG 1:1 parity against native D2MOO oracle', () => {
const source = fsDrlgSource(ORACLE_DATA)
const tables = loadDrlgTables(source)
for (const seed of FIXTURE_SEEDS) {
const hex = seed.toString(16).padStart(8, '0')
it(`matches committed oracle fixture for seed 0x${hex} across all stages including room activation and adapted Ds1 canvas`, () => {
const fixturePath = resolve(FIXTURE_DIR, `act1-seed-${hex}.json.gz`)
const expected = JSON.parse(gunzipSync(readFileSync(fixturePath)).toString('utf8')) as DrlgDump
delete expected.oracle
const actual = dumpAct1(createDrlgEnv(source, tables), seed, ACT1_OUTDOOR_LEVELS, {
difficulty: 0,
isolated: false,
})
const diff = firstDiff(expected, actual, '$')
if (diff) {
throw new Error(`Mismatch on seed 0x${hex}: ${formatDiff(diff)}`)
}
expect(diff).toBeNull()
for (let i = 0; i < ACT1_OUTDOOR_LEVELS.length; i += 1) {
const expectedCanvas = dumpLevelToDs1(expected.act, expected.levels[i]!, tables, { seed })
const actualCanvas = dumpLevelToDs1(actual.act, actual.levels[i]!, tables, { seed })
const canvasDiff = firstDiff(expectedCanvas, actualCanvas, `$.canvas[L${ACT1_OUTDOOR_LEVELS[i]}]`)
if (canvasDiff) {
throw new Error(`Canvas mismatch on seed 0x${hex}: ${formatDiff(canvasDiff)}`)
}
expect(actualCanvas.level.width).toBeGreaterThan(0)
expect(actualCanvas.level.height).toBeGreaterThan(0)
}
})
}
it.skipIf(!existsSync(ORACLE_BIN))('matches live native oracle + RNG trace on seed 0x12345678', () => {
const seed = 0x12345678
const oracleRun = runOracle({
seed,
levels: ACT1_OUTDOOR_LEVELS,
difficulty: 0,
isolated: false,
dataDir: ORACLE_DATA,
trace: true,
})
delete oracleRun.doc.oracle
const trace = new DrlgTraceWriter()
const actual = dumpAct1(createDrlgEnv(source, tables), seed, ACT1_OUTDOOR_LEVELS, {
difficulty: 0,
isolated: false,
trace,
})
const diff = firstDiff(oracleRun.doc, actual, '$')
if (diff) {
throw new Error(`Dump mismatch: ${formatDiff(diff)}`)
}
const traceDiff = firstTraceDivergence(oracleRun.trace ?? [], trace.lines)
expect(traceDiff).toBeNull()
})
it('enforces anti-cheat guards on src/game/drlg/* (no fixture reads, no seed literal branches, no levelName conditionals)', () => {
const drlgDir = resolve(import.meta.dirname, '../src/game/drlg')
const files = readdirSync(drlgDir).filter(f => f.endsWith('.ts'))
expect(files.length).toBeGreaterThan(10)
for (const file of files) {
const content = readFileSync(join(drlgDir, file), 'utf8')
expect(content, `${file} must not reference tests/fixtures`).not.toContain('tests/fixtures')
expect(content, `${file} must not reference act1-seed-`).not.toContain('act1-seed-')
expect(content, `${file} must not hardcode fixture seed 0x12345678`).not.toMatch(/0x12345678/i)
expect(content, `${file} must not hardcode fixture seed 0x5eed`).not.toMatch(/0x5eed/i)
if (file !== 'adapter.ts') {
expect(content, `${file} must not branch on levelName`).not.toContain('levelName')
}
}
})
})