diablo2-web/scripts/stress-world-placement.ts

270 lines
10 KiB
TypeScript

/**
* Standalone Adversarial Stress Test Harness for resolveWorldOffsets
*
* Tests 100 distinct seeds across all 5 Acts against:
* 1. Seamless Adjacency Boundary Alignment (Zero Tears/Gaps)
* 2. Non-Overlapping Interior Areas (Seamless and All Levels)
* 3. Act 5 Level 111 Alignment to X = 702
* 4. Determinism
*/
import { SEAMLESS_ADJACENCY } from '../src/game/world-graph.ts'
import { resolveWorldOffsets, type WorldOffset } from '../src/game/world-placement.ts'
export function generateTestSeeds(): number[] {
const seedSet = new Set<number>([
0x301cd095, 0x416d61c5, 0xdeadbeef, 42, 123, 9999,
0, 1, 0x7fffffff, 0xffffffff, 241, 572, 919,
])
let state = 0x8546291a
while (seedSet.size < 100) {
state = (state * 1664525 + 1013904223) >>> 0
seedSet.add(state)
}
return Array.from(seedSet)
}
export const TEST_SEEDS = generateTestSeeds()
export function doRectanglesOverlap(a: WorldOffset, b: WorldOffset): boolean {
return (
a.offsetX < b.offsetX + b.sizeX &&
a.offsetX + a.sizeX > b.offsetX &&
a.offsetY < b.offsetY + b.sizeY &&
a.offsetY + a.sizeY > b.offsetY
)
}
export interface BoundaryCheckResult {
readonly touching: boolean
readonly type: 'vertical-boundary' | 'horizontal-boundary' | 'none'
readonly sharedLength: number
readonly gapX: number
readonly gapY: number
}
export function checkBoundarySharing(a: WorldOffset, b: WorldOffset): BoundaryCheckResult {
const touchLR = a.offsetX + a.sizeX === b.offsetX || b.offsetX + b.sizeX === a.offsetX
const overlapY = Math.max(
0,
Math.min(a.offsetY + a.sizeY, b.offsetY + b.sizeY) - Math.max(a.offsetY, b.offsetY),
)
if (touchLR && overlapY > 0) {
return {
touching: true,
type: 'vertical-boundary',
sharedLength: overlapY,
gapX: 0,
gapY: 0,
}
}
const touchTB = a.offsetY + a.sizeY === b.offsetY || b.offsetY + b.sizeY === a.offsetY
const overlapX = Math.max(
0,
Math.min(a.offsetX + a.sizeX, b.offsetX + b.sizeX) - Math.max(a.offsetX, b.offsetX),
)
if (touchTB && overlapX > 0) {
return {
touching: true,
type: 'horizontal-boundary',
sharedLength: overlapX,
gapX: 0,
gapY: 0,
}
}
const gapX = a.offsetX + a.sizeX <= b.offsetX
? b.offsetX - (a.offsetX + a.sizeX)
: b.offsetX + b.sizeX <= a.offsetX
? a.offsetX - (b.offsetX + b.sizeX)
: 0
const gapY = a.offsetY + a.sizeY <= b.offsetY
? b.offsetY - (a.offsetY + a.sizeY)
: b.offsetY + b.sizeY <= a.offsetY
? a.offsetY - (b.offsetY + b.sizeY)
: 0
return {
touching: false,
type: 'none',
sharedLength: 0,
gapX,
gapY,
}
}
interface HarnessResults {
totalSeeds: number
p1SeamlessBoundary: { total: number; passed: number; failed: number; failuresByPair: Map<string, number> }
p2SeamlessInterior: { total: number; passed: number; failed: number }
p2AllLevelsInterior: { total: number; passed: number; failed: number; overlapCases: { seed: number; act: number; a: number; b: number }[] }
p3Act5Alignment: { total: number; passed: number; failed: number }
p4Determinism: { total: number; passed: number; failed: number }
}
export function runStressHarness(): HarnessResults {
const results: HarnessResults = {
totalSeeds: TEST_SEEDS.length,
p1SeamlessBoundary: { total: 0, passed: 0, failed: 0, failuresByPair: new Map() },
p2SeamlessInterior: { total: 0, passed: 0, failed: 0 },
p2AllLevelsInterior: { total: 0, passed: 0, failed: 0, overlapCases: [] },
p3Act5Alignment: { total: 0, passed: 0, failed: 0 },
p4Determinism: { total: 0, passed: 0, failed: 0 },
}
for (const seed of TEST_SEEDS) {
for (let act = 1; act <= 5; act++) {
const offsets = resolveWorldOffsets(act, seed)
// 1. Seamless Boundary Sharing
const actSeamless = SEAMLESS_ADJACENCY.filter(([a, b]) => offsets.has(a) && offsets.has(b))
for (const [aId, bId] of actSeamless) {
results.p1SeamlessBoundary.total++
const a = offsets.get(aId)!
const b = offsets.get(bId)!
const res = checkBoundarySharing(a, b)
if (res.touching) {
results.p1SeamlessBoundary.passed++
} else {
results.p1SeamlessBoundary.failed++
const key = `Act ${act}: [${aId}, ${bId}]`
results.p1SeamlessBoundary.failuresByPair.set(
key,
(results.p1SeamlessBoundary.failuresByPair.get(key) || 0) + 1,
)
}
}
// 2. Seamless Interior Non-Overlap
const seamlessIds = Array.from(new Set(actSeamless.flat()))
for (let i = 0; i < seamlessIds.length; i++) {
for (let j = i + 1; j < seamlessIds.length; j++) {
results.p2SeamlessInterior.total++
const a = offsets.get(seamlessIds[i])!;
const b = offsets.get(seamlessIds[j])!;
if (!doRectanglesOverlap(a, b)) {
results.p2SeamlessInterior.passed++
} else {
results.p2SeamlessInterior.failed++
}
}
}
// 2b. All Levels Interior Non-Overlap (including isolated levels)
const allPlaced = Array.from(offsets.values())
for (let i = 0; i < allPlaced.length; i++) {
for (let j = i + 1; j < allPlaced.length; j++) {
results.p2AllLevelsInterior.total++
const a = allPlaced[i]!
const b = allPlaced[j]!
if (!doRectanglesOverlap(a, b)) {
results.p2AllLevelsInterior.passed++
} else {
results.p2AllLevelsInterior.failed++
results.p2AllLevelsInterior.overlapCases.push({
seed,
act,
a: a.levelId,
b: b.levelId,
})
}
}
}
// 3. Act 5 Alignment
if (act === 5) {
results.p3Act5Alignment.total++
const l110 = offsets.get(110)
const l111 = offsets.get(111)
const l112 = offsets.get(112)
if (
l110 &&
l111 &&
l112 &&
l110.offsetX === 760 &&
l110.offsetY === 1000 &&
l111.sizeX === 58 &&
l111.offsetX === 702 &&
l111.offsetX + l111.sizeX === l110.offsetX &&
l112.sizeX === 58 &&
l112.offsetX === 702 &&
l112.offsetY + l112.sizeY === l111.offsetY
) {
results.p3Act5Alignment.passed++
} else {
results.p3Act5Alignment.failed++
}
}
// 4. Determinism
results.p4Determinism.total++
const replay = resolveWorldOffsets(act, seed)
const origEntries = JSON.stringify(Array.from(offsets.entries()))
const replayEntries = JSON.stringify(Array.from(replay.entries()))
if (origEntries === replayEntries) {
results.p4Determinism.passed++
} else {
results.p4Determinism.failed++
}
}
}
return results
}
const res = runStressHarness()
console.log('================================================================================')
console.log('ADVERSARIAL STRESS TEST REPORT: resolveWorldOffsets (100 Seeds x 5 Acts)')
console.log('================================================================================')
console.log(`Total Seeds Tested: ${res.totalSeeds}`)
console.log(`Property 1 (Seamless Boundary Sharing):`)
console.log(` - Total Checks: ${res.p1SeamlessBoundary.total}`)
console.log(` - Passed: ${res.p1SeamlessBoundary.passed} (${((res.p1SeamlessBoundary.passed / res.p1SeamlessBoundary.total) * 100).toFixed(2)}%)`)
console.log(` - Failed: ${res.p1SeamlessBoundary.failed} (${((res.p1SeamlessBoundary.failed / res.p1SeamlessBoundary.total) * 100).toFixed(2)}%)`)
console.log(` - Failure Breakdown:`)
for (const [pair, count] of res.p1SeamlessBoundary.failuresByPair.entries()) {
console.log(` * ${pair}: ${count} failures (${((count / res.totalSeeds) * 100).toFixed(1)}% failure rate)`)
}
console.log(`Property 2a (Seamless Levels Interior Non-Overlap):`)
console.log(` - Total Checks: ${res.p2SeamlessInterior.total}`)
console.log(` - Passed: ${res.p2SeamlessInterior.passed} (100%)`)
console.log(` - Failed: ${res.p2SeamlessInterior.failed}`)
console.log(`Property 2b (All Placed Levels Interior Non-Overlap):`)
console.log(` - Total Checks: ${res.p2AllLevelsInterior.total}`)
console.log(` - Passed: ${res.p2AllLevelsInterior.passed} (${((res.p2AllLevelsInterior.passed / res.p2AllLevelsInterior.total) * 100).toFixed(4)}%)`)
console.log(` - Failed: ${res.p2AllLevelsInterior.failed}`)
if (res.p2AllLevelsInterior.overlapCases.length > 0) {
console.log(` - Overlap Cases:`)
for (const c of res.p2AllLevelsInterior.overlapCases) {
console.log(` * Seed ${c.seed} (Act ${c.act}): Level ${c.a} collides with Level ${c.b}`)
}
}
console.log(`Property 3 (Act 5 Level 111 Alignment X = 702):`)
console.log(` - Total Checks: ${res.p3Act5Alignment.total}`)
console.log(` - Passed: ${res.p3Act5Alignment.passed} (100%)`)
console.log(` - Failed: ${res.p3Act5Alignment.failed}`)
console.log(`Property 4 (Determinism Replay):`)
console.log(` - Total Checks: ${res.p4Determinism.total}`)
console.log(` - Passed: ${res.p4Determinism.passed} (100%)`)
console.log(` - Failed: ${res.p4Determinism.failed}`)
console.log('--------------------------------------------------------------------------------')
const totalAssertions =
res.p1SeamlessBoundary.total +
res.p2SeamlessInterior.total +
res.p2AllLevelsInterior.total +
res.p3Act5Alignment.total +
res.p4Determinism.total
const totalFailures =
res.p1SeamlessBoundary.failed +
res.p2SeamlessInterior.failed +
res.p2AllLevelsInterior.failed +
res.p3Act5Alignment.failed +
res.p4Determinism.failed
console.log(`SUMMARY (Required Acceptance Metrics):`)
console.log(` - Total Assertions: ${totalAssertions}`)
console.log(` - Passed: ${totalAssertions - totalFailures}`)
console.log(` - Failed: ${totalFailures}`)
console.log(` - VERDICT: ${totalFailures === 0 ? 'APPROVE' : 'REJECT'}`)
console.log('================================================================================')