diablo2-web/scripts/verify-challenger-m3-stress.ts

622 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Milestone 3 Challenger Empirical Stress & Parity Verification Suite
*
* Exhaustive Empirical Verification:
* 1. Drop Placement Quadrant Scatter Parity:
* - 10,000 multi-item drops using findSafeDropPosition across 4 diverse map collision matrices:
* * Narrow Corridors (Tower / Arcane)
* * Chaos Sanctuary Star / Cross (lava borders & diagonal halls)
* * Catacombs (pillars, rooms, doors)
* * Maggot Lair (1-cell winding tunnels & dead ends)
* - Invariants: Zero wall penetration (COLLIDE_WALL), zero void penetration (COLLIDE_BLANK,
* COLLIDE_MASK_INVALID), valid floor grounding, anti-stacking burst dispersal, and quadrant symmetry.
* 2. Gold Pile Monte Carlo Distribution:
* - 100,000 gold drops across Normal, Nightmare, Hell difficulties.
* - Invariants: 100% conformance to 3-tier graphic cutoffs (flpgld_0: 1-49, flpgld_1: 50-499,
* flpgld_2: 500+), zero occurrences of artificial 5,000 clamp, presence of piles > 5,000.
* 3. Monster Seed PRNG Determinism with Queued Drops:
* - Verification of monster pendingDrop isolation from player movement, skill cast RNG,
* and runtime equipment/MF changes.
* - Out-of-order death animation completion and lockstep multi-session determinism.
*
* Usage: npx tsx scripts/verify-challenger-m3-stress.ts
*/
import { getEmbeddedDropTables } from '../src/game/embedded-drop-tables.ts'
import { executeDropPipeline } from '../src/game/drop-pipeline.ts'
import {
findSafeDropPosition,
getIsometricRingCandidates,
} from '../src/game/ground-items.ts'
import {
getGoldFlippyRect,
getInventoryGoldLimit,
resolveGroundItemSpriteRect,
} from '../src/ui/inventory.ts'
import { BAKED_UI_MANIFEST } from '../src/ui/baked-ui-meta.ts'
import {
COLLIDE_NONE,
COLLIDE_WALL,
COLLIDE_BLANK,
COLLIDE_MASK_INVALID,
COLLIDE_DOOR,
type CollisionGrid,
} from '../src/game/d2map.ts'
import { D2Rng } from '../src/game/d2-rng.ts'
import { Rng } from '../src/game/rng.ts'
import { GameEngine, type WorldMapProvider } from '../src/game/engine.ts'
import type { Monster } from '../src/game/combat.ts'
import { goldItem } from '../src/game/items.ts'
const dropTables = getEmbeddedDropTables()
const problems: string[] = []
let checks = 0
function assert(condition: boolean, message: string): void {
checks += 1
if (!condition) {
problems.push(message)
console.error(`❌ FAILED: ${message}`)
}
}
console.log('======================================================================')
console.log('⚔️ CHALLENGER M3-2: EMPIRICAL STRESS & MONTE CARLO HARNESS')
console.log('======================================================================\n')
// ============================================================================
// PART 1: DROP PLACEMENT QUADRANT SCATTER PARITY (10,000 MULTI-ITEM DROPS)
// ============================================================================
console.log('--- PART 1: QUADRANT SCATTER STRESS (10,000 Multi-Item Drops) ---')
// 1.1 Helper to create a CollisionGrid
function makeGrid(width: number, height: number, filler: number = COLLIDE_NONE): {
cellsX: number
cellsY: number
gridWidth: number
collisionMasks: Uint16Array
blocked: Uint8Array
} {
const subWidth = width * 5
const subHeight = height * 5
const collisionMasks = new Uint16Array(subWidth * subHeight)
const blocked = new Uint8Array(subWidth * subHeight)
if (filler !== COLLIDE_NONE) {
collisionMasks.fill(filler)
blocked.fill(1)
}
return {
cellsX: width,
cellsY: height,
gridWidth: subWidth,
collisionMasks,
blocked,
}
}
function setCellMask(grid: any, cx: number, cy: number, mask: number): void {
if (cx < 0 || cy < 0 || cx >= grid.cellsX || cy >= grid.cellsY) return
const startX = cx * 5
const startY = cy * 5
const isBlock = (mask & (COLLIDE_WALL | COLLIDE_BLANK | COLLIDE_MASK_INVALID)) !== 0
for (let dy = 0; dy < 5; dy++) {
for (let dx = 0; dx < 5; dx++) {
const idx = (startY + dy) * grid.gridWidth + (startX + dx)
grid.collisionMasks[idx] = mask
grid.blocked[idx] = isBlock ? 1 : 0
}
}
}
function getCellCenterMask(grid: any, cx: number, cy: number): number {
if (cx < 0 || cy < 0 || cx >= grid.cellsX || cy >= grid.cellsY) return COLLIDE_MASK_INVALID
const subX = cx * 5 + 2
const subY = cy * 5 + 2
const idx = subY * grid.gridWidth + subX
return grid.collisionMasks[idx] ?? COLLIDE_NONE
}
// Map 1: Narrow Corridors (Tower / Arcane) - 50x50 cells
const corridorGrid = makeGrid(50, 50, COLLIDE_WALL)
// Carve horizontal corridor at y = 10, 20, 30 (width = 2 cells)
for (let y of [10, 11, 20, 21, 30, 31]) {
for (let x = 5; x < 45; x++) setCellMask(corridorGrid, x, y, COLLIDE_NONE)
}
// Carve vertical connecting corridors at x = 10, 25, 40 (width = 2 cells)
for (let x of [10, 11, 25, 26, 40, 41]) {
for (let y = 5; y < 45; y++) setCellMask(corridorGrid, x, y, COLLIDE_NONE)
}
// Map 2: Chaos Sanctuary Star / Cross - 60x60 cells
const sanctuaryGrid = makeGrid(60, 60, COLLIDE_BLANK) // Lava void background
// Carve central hub (25..35, 25..35)
for (let y = 25; y <= 35; y++) {
for (let x = 25; x <= 35; x++) setCellMask(sanctuaryGrid, x, y, COLLIDE_NONE)
}
// 4 cardinal wings
for (let i = 10; i < 25; i++) {
for (let w = -2; w <= 2; w++) {
setCellMask(sanctuaryGrid, 30 + w, i, COLLIDE_NONE) // North
setCellMask(sanctuaryGrid, 30 + w, 60 - i, COLLIDE_NONE) // South
setCellMask(sanctuaryGrid, i, 30 + w, COLLIDE_NONE) // West
setCellMask(sanctuaryGrid, 60 - i, 30 + w, COLLIDE_NONE) // East
}
}
// Diagonal corridors with wall boundaries
for (let d = 5; d < 18; d++) {
setCellMask(sanctuaryGrid, 25 - d, 25 - d, COLLIDE_NONE)
setCellMask(sanctuaryGrid, 35 + d, 25 - d, COLLIDE_NONE)
setCellMask(sanctuaryGrid, 25 - d, 35 + d, COLLIDE_NONE)
setCellMask(sanctuaryGrid, 35 + d, 35 + d, COLLIDE_NONE)
}
// Map 3: Catacombs (Pillar room & doors) - 50x50 cells
const catacombsGrid = makeGrid(50, 50, COLLIDE_NONE)
// Perimeter wall
for (let i = 0; i < 50; i++) {
setCellMask(catacombsGrid, i, 0, COLLIDE_WALL)
setCellMask(catacombsGrid, i, 49, COLLIDE_WALL)
setCellMask(catacombsGrid, 0, i, COLLIDE_WALL)
setCellMask(catacombsGrid, 49, i, COLLIDE_WALL)
}
// Dividing walls with doors
for (let i = 1; i < 49; i++) {
setCellMask(catacombsGrid, 25, i, i === 15 || i === 35 ? COLLIDE_DOOR : COLLIDE_WALL)
}
// Dense pillars inside rooms
for (let py of [10, 20, 30, 40]) {
for (let px of [10, 18, 32, 42]) {
setCellMask(catacombsGrid, px, py, COLLIDE_WALL)
setCellMask(catacombsGrid, px + 1, py, COLLIDE_WALL)
setCellMask(catacombsGrid, px, py + 1, COLLIDE_WALL)
setCellMask(catacombsGrid, px + 1, py + 1, COLLIDE_WALL)
}
}
// Map 4: Maggot Lair (Tight winding 1-cell tunnels) - 50x50 cells
const maggotGrid = makeGrid(50, 50, COLLIDE_WALL)
let curX = 5, curY = 5
setCellMask(maggotGrid, curX, curY, COLLIDE_NONE)
const pathRng = new D2Rng(0xCAFEF00D)
for (let step = 0; step < 800; step++) {
const dir = pathRng.rand(4)
if (dir === 0 && curX < 45) curX++
else if (dir === 1 && curX > 5) curX--
else if (dir === 2 && curY < 45) curY++
else if (dir === 3 && curY > 5) curY--
setCellMask(maggotGrid, curX, curY, COLLIDE_NONE)
}
const testMaps = [
{ name: 'Narrow Corridors', grid: corridorGrid },
{ name: 'Chaos Sanctuary Star', grid: sanctuaryGrid },
{ name: 'Catacombs with Pillars & Doors', grid: catacombsGrid },
{ name: 'Maggot Lair 1-Cell Tunnels', grid: maggotGrid },
]
let totalMultiItemDrops = 0
let totalPlacedItems = 0
let wallPenetrations = 0
let voidPenetrations = 0
let outOfBoundsDrops = 0
let unexpectedOverlapCount = 0
// Run 2,500 multi-item drops per map (total 10,000 multi-item drops)
for (const map of testMaps) {
const grid = map.grid
const walkableCells: { x: number; y: number }[] = []
for (let cy = 0; cy < grid.cellsY; cy++) {
for (let cx = 0; cx < grid.cellsX; cx++) {
const mask = getCellCenterMask(grid, cx, cy)
if ((mask & (COLLIDE_WALL | COLLIDE_BLANK | COLLIDE_MASK_INVALID)) === 0) {
walkableCells.push({ x: cx, y: cy })
}
}
}
assert(walkableCells.length > 50, `${map.name} must have sufficient walkable cells`)
const dropRng = new D2Rng(0x5EED1000 + totalMultiItemDrops)
for (let dropIdx = 0; dropIdx < 2500; dropIdx++) {
totalMultiItemDrops++
// Pick an origin from walkable cells
const origin = walkableCells[dropRng.rand(walkableCells.length)]!
const itemCount = 4 + dropRng.rand(5) // 4 to 8 items per burst
const occupied: { cellX: number; cellY: number }[] = []
for (let itemIdx = 0; itemIdx < itemCount; itemIdx++) {
totalPlacedItems++
const pos = findSafeDropPosition(grid, origin.x, origin.y, 4, occupied)
// 1. Out of bounds check
if (pos.cellX < 0 || pos.cellY < 0 || pos.cellX >= grid.cellsX || pos.cellY >= grid.cellsY) {
outOfBoundsDrops++
}
// 2. Wall & Void check
const placedMask = getCellCenterMask(grid, pos.cellX, pos.cellY)
if ((placedMask & COLLIDE_WALL) !== 0) {
wallPenetrations++
}
if ((placedMask & (COLLIDE_BLANK | COLLIDE_MASK_INVALID)) !== 0) {
voidPenetrations++
}
occupied.push(pos)
}
// Check anti-stacking: if available free cells within maxRadius >= itemCount,
// all placed items must be in distinct cells!
let freeNeighbors = 0
for (let dy = -4; dy <= 4; dy++) {
for (let dx = -4; dx <= 4; dx++) {
const cx = origin.x + dx
const cy = origin.y + dy
if (cx >= 0 && cy >= 0 && cx < grid.cellsX && cy < grid.cellsY) {
const m = getCellCenterMask(grid, cx, cy)
if ((m & (COLLIDE_WALL | COLLIDE_BLANK | COLLIDE_MASK_INVALID)) === 0) {
freeNeighbors++
}
}
}
}
const uniqueOccupied = new Set(occupied.map(p => `${p.cellX},${p.cellY}`))
if (freeNeighbors >= itemCount && uniqueOccupied.size !== itemCount) {
unexpectedOverlapCount++
}
}
}
console.log(`- Total Multi-Item Drop Events: ${totalMultiItemDrops}`)
console.log(`- Total Items Placed: ${totalPlacedItems}`)
console.log(`- Wall Penetrations: ${wallPenetrations}`)
console.log(`- Void Penetrations: ${voidPenetrations}`)
console.log(`- Out of Bounds Drops: ${outOfBoundsDrops}`)
console.log(`- Unexpected Overlaps (when free cells available): ${unexpectedOverlapCount}`)
assert(wallPenetrations === 0, 'Must have exactly ZERO wall penetrations')
assert(voidPenetrations === 0, 'Must have exactly ZERO void penetrations')
assert(outOfBoundsDrops === 0, 'Must have exactly ZERO out-of-bounds drops')
assert(unexpectedOverlapCount === 0, 'Must have ZERO unexpected overlaps when candidate cells are free')
// 1.2 Quadrant Ring Symmetry Invariant Check
console.log('\nTesting Concentric Quadrant Ring Symmetry & Ordering Invariant:')
for (let r = 1; r <= 5; r++) {
const ring = getIsometricRingCandidates(r)
const l1Values = ring.map(c => c.l1)
// Check that L1 values are monotonically non-decreasing
for (let i = 1; i < l1Values.length; i++) {
assert(l1Values[i]! >= l1Values[i - 1]!, `Ring ${r} candidates must be sorted by Manhattan distance L1`)
}
// Check quadrant balance: count of +x, -x, +y, -y
const posX = ring.filter(c => c.dx > 0).length
const negX = ring.filter(c => c.dx < 0).length
const posY = ring.filter(c => c.dy > 0).length
const negY = ring.filter(c => c.dy < 0).length
assert(posX === negX, `Ring ${r} must have balanced X axis symmetry (+X:${posX}, -X:${negX})`)
assert(posY === negY, `Ring ${r} must have balanced Y axis symmetry (+Y:${posY}, -Y:${negY})`)
}
console.log('✓ Concentric quadrant ring symmetry invariant holds strictly.')
// ============================================================================
// PART 2: GOLD PILE MONTE CARLO DISTRIBUTION (100,000 GOLD DROPS)
// ============================================================================
console.log('\n--- PART 2: GOLD PILE MONTE CARLO (100,000 Drops Across Difficulties) ---')
const flippyRects = BAKED_UI_MANIFEST.flippyRects ?? {}
assert(Boolean(flippyRects['flpgld_0']), 'flpgld_0 flippy rect must exist')
assert(Boolean(flippyRects['flpgld_1']), 'flpgld_1 flippy rect must exist')
assert(Boolean(flippyRects['flpgld_2']), 'flpgld_2 flippy rect must exist')
let tier0Count = 0 // 1..49
let tier1Count = 0 // 50..499
let tier2Count = 0 // 500+
let pilesOver5000 = 0
let maxGoldAmount = 0
let minGoldAmount = Infinity
let invalidTierResolutions = 0
let clamped5000Count = 0
// Test scenarios:
// Normal: Fallen (lvl 1..5), Zombie (lvl 3..8), Blood Raven (lvl 10), Andariel (lvl 12)
// Nightmare: Flayer (lvl 50), Council Member (lvl 55), Mephisto (lvl 59)
// Hell: Council Member (lvl 85), Chaos Sanctuary (lvl 85), Diablo (lvl 94), Baal (lvl 99)
// With GF = 0%, 50%, 150%, 300%
const goldScenarios = [
{ diff: 'normal', tc: 'Act 1 Junk', lvl: 3, gf: 0, weight: 15 },
{ diff: 'normal', tc: 'Act 1 Junk', lvl: 8, gf: 25, weight: 15 },
{ diff: 'normal', tc: 'Act 2 Junk', lvl: 16, gf: 50, weight: 10 },
{ diff: 'nightmare', tc: 'Act 3 (N) Junk', lvl: 52, gf: 0, weight: 15 },
{ diff: 'nightmare', tc: 'Act 3 (N) Junk', lvl: 55, gf: 100, weight: 10 },
{ diff: 'nightmare', tc: 'Act 4 (N) Junk', lvl: 60, gf: 150, weight: 10 },
{ diff: 'hell', tc: 'Act 3 (H) Junk', lvl: 83, gf: 100, weight: 10 },
{ diff: 'hell', tc: 'Act 4 (H) Junk', lvl: 85, gf: 200, weight: 10 },
{ diff: 'hell', tc: 'Act 5 (H) Junk', lvl: 88, gf: 350, weight: 5 },
]
const totalTargetGoldDrops = 100000
const goldRng = new D2Rng(0xB16601D)
for (let dropIdx = 0; dropIdx < totalTargetGoldDrops; dropIdx++) {
// Select scenario
const scen = goldScenarios[dropIdx % goldScenarios.length]!
const effectiveIlvl = scen.lvl
const minGold = effectiveIlvl
const maxGoldAdd = 5 * effectiveIlvl
const baseGold = Math.max(1, goldRng.rand(maxGoldAdd) + minGold)
const multiplier = 1 + goldRng.rand(3) // 1x to 3x multiplier
let goldAmount = Math.max(1, baseGold * multiplier)
if (scen.gf > 0) {
goldAmount = Math.max(1, Math.floor((goldAmount * (100 + scen.gf)) / 100))
}
// Create gold item
const item = goldItem(goldAmount)
const stack = item.stack ?? 0
if (stack < minGoldAmount) minGoldAmount = stack
if (stack > maxGoldAmount) maxGoldAmount = stack
if (stack > 5000) pilesOver5000++
if (stack === 5000) clamped5000Count++
// Check 3-tier classification
const rect = getGoldFlippyRect(stack, flippyRects)
const spriteRect = resolveGroundItemSpriteRect(
{ isGold: true, amount: stack, code: 'gld' },
flippyRects,
)
if (stack < 50) {
tier0Count++
if (rect !== flippyRects['flpgld_0'] || spriteRect !== flippyRects['flpgld_0']) {
invalidTierResolutions++
}
} else if (stack < 500) {
tier1Count++
if (rect !== flippyRects['flpgld_1'] || spriteRect !== flippyRects['flpgld_1']) {
invalidTierResolutions++
}
} else {
tier2Count++
if (rect !== flippyRects['flpgld_2'] || spriteRect !== flippyRects['flpgld_2']) {
invalidTierResolutions++
}
}
}
console.log(`- Total Gold Drops Sampled: ${totalTargetGoldDrops}`)
console.log(`- Min Gold Pile Amount: ${minGoldAmount}`)
console.log(`- Max Gold Pile Amount: ${maxGoldAmount}`)
console.log(`- Piles in Tier 0 (1–49 gold / flpgld_0): ${tier0Count} (${((tier0Count / totalTargetGoldDrops) * 100).toFixed(2)}%)`)
console.log(`- Piles in Tier 1 (50–499 gold / flpgld_1): ${tier1Count} (${((tier1Count / totalTargetGoldDrops) * 100).toFixed(2)}%)`)
console.log(`- Piles in Tier 2 (500+ gold / flpgld_2): ${tier2Count} (${((tier2Count / totalTargetGoldDrops) * 100).toFixed(2)}%)`)
console.log(`- Piles > 5,000 Gold: ${pilesOver5000} (${((pilesOver5000 / totalTargetGoldDrops) * 100).toFixed(2)}%)`)
console.log(`- Invalid Tier Resolutions: ${invalidTierResolutions}`)
assert(tier0Count > 0, 'Tier 0 (flpgld_0) must be populated')
assert(tier1Count > 0, 'Tier 1 (flpgld_1) must be populated')
assert(tier2Count > 0, 'Tier 2 (flpgld_2) must be populated')
assert(pilesOver5000 > 0, 'Must have gold piles > 5,000 (proving absence of 5,000 clamp)')
assert(maxGoldAmount > 5000, `Max gold pile (${maxGoldAmount}) must exceed 5,000`)
assert(invalidTierResolutions === 0, 'All 100,000 gold piles must resolve to exact authentic 3-tier flippy rects')
// Character inventory gold capacity checks
for (let lvl = 1; lvl <= 99; lvl++) {
const cap = getInventoryGoldLimit(lvl)
assert(cap === lvl * 10000, `Level ${lvl} gold limit must be exactly ${lvl * 10000}`)
}
console.log('✓ Gold inventory capacity limit strictly conforms to level * 10,000 across all 99 levels.')
// ============================================================================
// PART 3: MONSTER SEED PRNG DETERMINISM WITH QUEUED DROPS
// ============================================================================
console.log('\n--- PART 3: MONSTER SEED PRNG DETERMINISM (Queued vs Immediate) ---')
function createTestEngine(seed = 42, equippedItems: any[] = []): GameEngine {
const engine = new GameEngine(
{ widthPx: 2000, heightPx: 2000, overlap: () => 0 },
{
spawn: { x: 500, y: 500 },
stats: [],
xpTable: [0, 500, 1500],
skills: [
{
id: 1,
name: 'Fire Bolt',
manaCost: 2,
cooldownTicks: 5,
radius: 20,
range: 300,
speed: 10,
damage: 10,
} as any,
],
npcDefs: [],
questDefs: [],
combatOptions: {
playerSpeed: 4,
playerReach: 50,
playerCooldownTicks: 10,
playerDamage: 100,
playerManaPerAttack: 1,
respawnTicks: 100,
},
talkRadius: 50,
pickupRadius: 30,
inventoryCols: 10,
inventoryRows: 4,
lootSeed: seed,
dropTables,
equippedItems,
},
)
engine.setDropTables(dropTables)
return engine
}
function makeMonster(index: number, id: string, x: number, y: number, lvl = 15): Monster {
return {
index,
stats: {
id,
name: id,
hp: 50,
damage: 1,
cooldownTicks: 10,
reach: 20,
aggroRadius: 100,
speed: 10,
xp: 10,
level: lvl,
rank: 'normal',
},
x,
y,
hp: 50,
cooldown: 0,
state: 'idle',
facing: 0,
hitFlash: 0,
corpseTicks: 0,
dropRolled: false,
}
}
// Scenario 3.1: Isolated PRNG Stream Invariance
// Verify that intermediate gameplay ticks (casting skills, moving, swapping gear)
// do NOT alter the pending drop output rolled upon death animation completion.
console.log('Testing Scenario 3.1: Immediate Drop vs Delayed Queued Drop Determinism')
const TEST_SEEDS = [101, 777, 1337, 0xCAFE, 0x12345678]
for (const testSeed of TEST_SEEDS) {
// Run 1: Immediate drop (no death gating)
const engineImmediate = createTestEngine(testSeed, [
{ name: 'MF Ring', stats: [{ text: '30% Better Chance of Getting Magic Items' }] },
])
const monster1 = makeMonster(0, 'fallen1', 520, 500, 15)
engineImmediate.world.monsters.push(monster1)
engineImmediate.damageMonster(0, 999)
engineImmediate.tick({ movement: { x: 0, y: 0 }, attacking: false, pickingUp: false, talking: false, digits: [], saving: false, loading: false })
const immediateDrops = engineImmediate.groundItems.all.map(g => ({
name: g.name,
code: g.item.code,
quality: g.quality,
amount: g.amount,
}))
// Run 2: Gated drop (deathGated = true, deathTicks = 20)
// During the 20 ticks, the player moves, casts spells (advancing castRng), and unequips MF ring!
const engineGated = createTestEngine(testSeed, [
{ name: 'MF Ring', stats: [{ text: '30% Better Chance of Getting Magic Items' }] },
])
const monster2 = makeMonster(0, 'fallen1', 520, 500, 15)
monster2.deathGated = true
monster2.deathTicks = 20
engineGated.world.monsters.push(monster2)
engineGated.damageMonster(0, 999)
// Lethal tick: drop is queued into pendingDrop
engineGated.tick({ movement: { x: 0, y: 0 }, attacking: false, pickingUp: false, talking: false, digits: [], saving: false, loading: false })
assert(engineGated.groundItems.count === 0, 'No drops should appear while death animation is ticking')
// Unequip MF ring during death animation! (Player un-equips gear before monster hits ground)
;(engineGated.opts as any).equippedItems = []
// Simulate 19 ticks with movement and spell casting
for (let t = 0; t < 19; t++) {
engineGated.tick({
movement: { x: t % 2 === 0 ? 1 : -1, y: 0 },
attacking: t % 4 === 0, // Cast spell, consuming castRng
pickingUp: false,
talking: false,
digits: [1],
saving: false,
loading: false,
})
}
// Final tick: death animation completes, drop triggers!
engineGated.tick({ movement: { x: 0, y: 0 }, attacking: false, pickingUp: false, talking: false, digits: [], saving: false, loading: false })
assert(monster2.dropRolled === true, 'Drop must be rolled upon death animation completion')
const gatedDrops = engineGated.groundItems.all.map(g => ({
name: g.name,
code: g.item.code,
quality: g.quality,
amount: g.amount,
}))
assert(
immediateDrops.length === gatedDrops.length,
`Seed ${testSeed}: Item count mismatch (immediate: ${immediateDrops.length}, gated: ${gatedDrops.length})`,
)
for (let i = 0; i < immediateDrops.length; i++) {
const imm = immediateDrops[i]!
const gat = gatedDrops[i]!
assert(imm.name === gat.name, `Seed ${testSeed}: Item ${i} name mismatch ("${imm.name}" vs "${gat.name}")`)
assert(imm.code === gat.code, `Seed ${testSeed}: Item ${i} code mismatch ("${imm.code}" vs "${gat.code}")`)
assert(imm.quality === gat.quality, `Seed ${testSeed}: Item ${i} quality mismatch ("${imm.quality}" vs "${gat.quality}")`)
assert(imm.amount === gat.amount, `Seed ${testSeed}: Item ${i} amount mismatch (${imm.amount} vs ${gat.amount})`)
}
}
console.log('✓ Queued drop determinism strictly matches immediate drop bit-for-bit, immune to mid-animation input/casting/swaps.')
// Scenario 3.2: Multi-Monster Lockstep Queue Integrity
console.log('\nTesting Scenario 3.2: Multi-Monster Parallel Death Queue Race Conditions')
const engineA = createTestEngine(0x998877)
const engineB = createTestEngine(0x998877)
// 3 monsters with different death durations dying in staggered ticks
for (let i = 0; i < 3; i++) {
const mA = makeMonster(i, i === 0 ? 'zombie1' : i === 1 ? 'skeleton1' : 'fallen1', 520 + i * 20, 500, 10 + i * 5)
const mB = makeMonster(i, i === 0 ? 'zombie1' : i === 1 ? 'skeleton1' : 'fallen1', 520 + i * 20, 500, 10 + i * 5)
mA.deathGated = true
mB.deathGated = true
mA.deathTicks = (3 - i) * 5 // Monster 0: 15 ticks, Monster 1: 10 ticks, Monster 2: 5 ticks (finishes first!)
mB.deathTicks = (3 - i) * 5
engineA.world.monsters.push(mA)
engineB.world.monsters.push(mB)
}
// Kill all 3 on tick 0
engineA.damageMonster(0, 999)
engineA.damageMonster(1, 999)
engineA.damageMonster(2, 999)
engineB.damageMonster(0, 999)
engineB.damageMonster(1, 999)
engineB.damageMonster(2, 999)
// Tick both engines for 25 frames
for (let t = 0; t < 25; t++) {
engineA.tick({ movement: { x: 0, y: 0 }, attacking: false, pickingUp: false, talking: false, digits: [], saving: false, loading: false })
engineB.tick({ movement: { x: 0, y: 0 }, attacking: false, pickingUp: false, talking: false, digits: [], saving: false, loading: false })
}
const dropsA = engineA.groundItems.all.map(g => `${g.name}:${g.quality}:${g.cellX},${g.cellY}`)
const dropsB = engineB.groundItems.all.map(g => `${g.name}:${g.quality}:${g.cellX},${g.cellY}`)
assert(dropsA.length > 0, 'Drops must have spawned in Engine A')
assert(dropsA.length === dropsB.length, 'Engine A and B must have identical drop counts')
assert(JSON.stringify(dropsA) === JSON.stringify(dropsB), 'Engine A and B must maintain 100% lockstep parity in queued drops')
console.log('✓ Multi-monster staggered death queues maintain 100% lockstep parity across independent engines.')
console.log('\n======================================================================')
console.log(`TOTAL CHECKS EXECUTED: ${checks}`)
console.log(`PROBLEMS ENCOUNTERED: ${problems.length}`)
if (problems.length > 0) {
console.log('FAILED CONDITIONS:')
for (const p of problems) console.log(` - ${p}`)
process.exit(1)
} else {
console.log('🎉 ALL EMPIRICAL CHALLENGER STRESS TESTS PASSED WITH 0 PROBLEMS!')
console.log('======================================================================')
}