108 lines
3.9 KiB
TypeScript
108 lines
3.9 KiB
TypeScript
/**
|
|
* Act III outdoor DRLG (#499): bit-exact parity of the TypeScript port (src/game/drlg) with the native
|
|
* D2MOO oracle (tools/d2moo-oracle) across all Act III outdoor levels (76-83).
|
|
*/
|
|
import { existsSync } from 'node:fs'
|
|
import { join, resolve } from 'node:path'
|
|
import { beforeAll, describe, expect, it } from 'vitest'
|
|
import { DrlgTraceWriter, dumpAct1 } from '../src/game/drlg/drlg-dump.ts'
|
|
import { drlgLevelInputFromDump } from '../src/game/drlg/drlg-map.ts'
|
|
import { createDrlgEnv } from '../src/game/drlg/drlg-source.ts'
|
|
import { loadDrlgTables } from '../src/game/drlg/drlg-tables.ts'
|
|
import { MpqArchive } from '../src/mpq/archive.ts'
|
|
import { fileSource } from '../src/mpq/file-source.ts'
|
|
import { MountedArchives } from '../src/mpq/mount.ts'
|
|
import { loadDrlgMpqData, type DrlgMpqData } from '../scripts/lib/drlg-mpq-source.ts'
|
|
import {
|
|
firstDiff,
|
|
firstTraceDivergence,
|
|
formatDiff,
|
|
fsDrlgSource,
|
|
ORACLE_BIN,
|
|
ORACLE_DATA,
|
|
runOracle,
|
|
} from '../scripts/lib/drlg-oracle.ts'
|
|
|
|
const ACT3_OUTDOOR_LEVELS = [76, 77, 78, 79, 80, 81, 82, 83]
|
|
const SEEDS = [0x12345678, 0x5eed0300, 0x5eed0301, 0x5eed0302]
|
|
const ARCHIVE_DIR = resolve(import.meta.dirname, '../samples/d2')
|
|
const MOUNTS = ['d2char.mpq', 'd2data.mpq', 'd2exp.mpq', 'Patch_D2.mpq'] as const
|
|
|
|
function hexSeed(seed: number): string {
|
|
return seed.toString(16).padStart(8, '0')
|
|
}
|
|
|
|
describe('Act III outdoor DRLG 1:1 parity against native D2MOO oracle', () => {
|
|
let data: DrlgMpqData
|
|
|
|
beforeAll(async () => {
|
|
const missing = MOUNTS.filter(name => !existsSync(join(ARCHIVE_DIR, name)))
|
|
if (missing.length > 0) {
|
|
throw new Error(`the DRLG parity suite needs the 1.13c archives in ${ARCHIVE_DIR}; missing ${missing.join(', ')}`)
|
|
}
|
|
const archives = new MountedArchives()
|
|
for (const name of MOUNTS) archives.add(name, await MpqArchive.open(await fileSource(join(ARCHIVE_DIR, name))))
|
|
data = await loadDrlgMpqData(member => archives.read(member), 3)
|
|
}, 120_000)
|
|
|
|
for (const seed of SEEDS) {
|
|
const hex = hexSeed(seed)
|
|
it.skipIf(!existsSync(ORACLE_BIN))(
|
|
`seed 0x${hex}: MPQ-fed port reproduces every native oracle stage and RNG trace`,
|
|
() => {
|
|
const oracleRun = runOracle({
|
|
seed,
|
|
levels: ACT3_OUTDOOR_LEVELS,
|
|
difficulty: 0,
|
|
isolated: false,
|
|
dataDir: ORACLE_DATA,
|
|
trace: true,
|
|
})
|
|
delete oracleRun.doc.oracle
|
|
|
|
const trace = new DrlgTraceWriter()
|
|
const actual = dumpAct1(createDrlgEnv(data.source, data.tables), seed, ACT3_OUTDOOR_LEVELS, {
|
|
difficulty: 0,
|
|
isolated: false,
|
|
trace,
|
|
})
|
|
const diff = firstDiff(oracleRun.doc, actual, '$')
|
|
if (diff) throw new Error(`Mismatch on seed 0x${hex}: ${formatDiff(diff)}`)
|
|
|
|
const traceDiff = firstTraceDivergence(oracleRun.trace ?? [], trace.lines)
|
|
expect(traceDiff).toBeNull()
|
|
|
|
for (const levelId of ACT3_OUTDOOR_LEVELS) {
|
|
const input = drlgLevelInputFromDump(actual, levelId, data.tables)
|
|
expect(input.tiles.length).toBeGreaterThan(0)
|
|
}
|
|
},
|
|
60_000,
|
|
)
|
|
}
|
|
|
|
it.skipIf(!existsSync(ORACLE_BIN))('matches live native oracle in --isolated mode on seed 0x12345678', () => {
|
|
const seed = 0x12345678
|
|
const oracleRun = runOracle({
|
|
seed,
|
|
levels: ACT3_OUTDOOR_LEVELS,
|
|
difficulty: 0,
|
|
isolated: true,
|
|
dataDir: ORACLE_DATA,
|
|
trace: true,
|
|
})
|
|
delete oracleRun.doc.oracle
|
|
const source = fsDrlgSource(ORACLE_DATA)
|
|
const trace = new DrlgTraceWriter()
|
|
const actual = dumpAct1(createDrlgEnv(source, loadDrlgTables(source)), seed, ACT3_OUTDOOR_LEVELS, {
|
|
difficulty: 0,
|
|
isolated: true,
|
|
trace,
|
|
})
|
|
const diff = firstDiff(oracleRun.doc, actual, '$')
|
|
if (diff) throw new Error(`Isolated dump mismatch: ${formatDiff(diff)}`)
|
|
const traceDiff = firstTraceDivergence(oracleRun.trace ?? [], trace.lines)
|
|
expect(traceDiff).toBeNull()
|
|
}, 60_000)
|
|
})
|