97 lines
4.0 KiB
TypeScript
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')
|
|
}
|
|
}
|
|
})
|
|
})
|