diablo2-web/scripts/stress-test-m3-adversarial.ts

148 lines
4.3 KiB
TypeScript

import { resolveUnsetSize, UnresolvedLevelSizeError } from '../src/game/wilderness.ts'
import type { WildernessPiece, WildernessRequest } from '../src/game/wilderness.ts'
import type { Ds1, Ds1Cell } from '../src/formats/ds1.ts'
function makeMockDs1(width: number, height: number): Ds1 {
const cells: Ds1Cell[][] = []
for (let y = 0; y < height; y += 1) {
const row: Ds1Cell[] = []
for (let x = 0; x < width; x += 1) {
row.push({
walls: [],
floors: [{ prop1: 2, sequence: 0, style: 1, unknown1: 0, unknown2: 0, hidden: false }],
shadows: [],
substitutions: [],
})
}
cells.push(row)
}
return {
version: 7,
width,
height,
act: 5,
substitutionType: 0,
wallLayers: 1,
floorLayers: 1,
cells,
objects: [],
npcPathOffset: null,
}
}
console.log('=== STARTING ADVERSARIAL STRESS TEST FOR resolveUnsetSize ===')
let failures = 0
function assert(condition: boolean, msg: string) {
if (!condition) {
console.error(`FAIL: ${msg}`)
failures++
} else {
console.log(`PASS: ${msg}`)
}
}
// 1. Anti-silent failure: Empty pieces throws UnresolvedLevelSizeError
try {
resolveUnsetSize({
levelId: 111,
levelName: 'Act 5 - Barricade 1',
levelTypeName: 'Act 5 - Barricade',
sizeX: -1,
sizeY: -1,
subType: 10,
subTheme: 0,
seed: 12345,
pieces: [],
substitutions: [],
})
assert(false, 'Expected empty pieces to throw UnresolvedLevelSizeError')
} catch (err) {
assert(err instanceof UnresolvedLevelSizeError, 'Empty pieces throws instance of UnresolvedLevelSizeError')
assert((err as Error).message.includes('Level 111 (Act 5 - Barricade 1)'), 'Error message contains level info')
}
// 2. Anti-silent failure: Non-barricade pieces throw UnresolvedLevelSizeError
try {
resolveUnsetSize({
levelId: 111,
levelName: 'Act 5 - Barricade 1',
levelTypeName: 'Act 5 - Barricade',
sizeX: -1,
sizeY: -1,
subType: 10,
subTheme: 0,
seed: 12345,
pieces: [
{ name: 'Act 5 - Siege 1', border: false, levels: [makeMockDs1(10, 10)] },
],
substitutions: [],
})
assert(false, 'Expected non-barricade pieces to throw UnresolvedLevelSizeError')
} catch (err) {
assert(err instanceof UnresolvedLevelSizeError, 'Non-barricade pieces throw instance of UnresolvedLevelSizeError')
}
// 3. Fuzzing pieces dimensions: 10,000 random dimensions test
let allBoundsHold = true
let allAspectRatiosHold = true
for (let i = 0; i < 10000; i++) {
const randW1 = Math.floor(Math.random() * 2000) - 500 // -500 to 1500
const randH1 = Math.floor(Math.random() * 2000) - 500
const randW2 = Math.floor(Math.random() * 2000) - 500
const randH2 = Math.floor(Math.random() * 2000) - 500
const res = resolveUnsetSize({
levelId: 111,
levelName: 'Act 5 - Barricade 1',
levelTypeName: 'Act 5 - Barricade',
sizeX: -1,
sizeY: -1,
subType: 10,
subTheme: 0,
seed: i,
pieces: [
{ name: 'Act 5 - Barricade Entrance', border: false, levels: [makeMockDs1(randW1, randH1)] },
{ name: 'Act 5 - Barricade Exit', border: false, levels: [makeMockDs1(randW2, randH2)] },
],
substitutions: [],
})
if (res.sizeX < 48 || res.sizeX > 64 || res.sizeY < 160 || res.sizeY > 192) {
allBoundsHold = false
console.error(`Bounds failure at iteration ${i}: sizeX=${res.sizeX}, sizeY=${res.sizeY}`)
break
}
if (res.sizeY <= res.sizeX || (res.sizeY / res.sizeX) < 2.5) {
allAspectRatiosHold = false
console.error(`Aspect ratio failure at iteration ${i}: sizeX=${res.sizeX}, sizeY=${res.sizeY}`)
break
}
}
assert(allBoundsHold, '10,000 random input tests: sizeX in [48..64] and sizeY in [160..192] strictly hold')
assert(allAspectRatiosHold, '10,000 random input tests: sizeY > sizeX and aspect ratio >= 2.5 strictly hold')
// 4. Overrides
const overrideRes = resolveUnsetSize({
levelId: 111,
levelName: 'Act 5 - Barricade 1',
levelTypeName: 'Act 5 - Barricade',
sizeX: -1,
sizeY: -1,
sizeOverride: { x: 72, y: 144 },
subType: 10,
subTheme: 0,
seed: 999,
pieces: [],
substitutions: [],
})
assert(overrideRes.sizeX === 72 && overrideRes.sizeY === 144, 'sizeOverride is respected exactly')
if (failures > 0) {
console.error(`\nFAILED: ${failures} assertions failed!`)
process.exit(1)
} else {
console.log('\nSUCCESS: All adversarial checks passed!')
}