diablo2-web/scripts/verify-challenger-m1-2.ts

619 lines
29 KiB
TypeScript

/**
* Comprehensive Empirical Stress-Testing Harness for Milestone 1
* Ghost Interaction Decoupling & Combat Safety (Requirement R1)
*
* Playbook: solution_stress_testing (SKILL.md)
* Archetype: EMPIRICAL CHALLENGER
*/
import { KeyboardInput } from '../src/sim/input.ts'
import { HudManager } from '../src/ui/hud-manager.ts'
import { GameEngine, type GameEngineOptions, type WorldMapProvider } from '../src/game/engine.ts'
// Mock DOM Environment for Node
class MockWindow extends EventTarget {
innerWidth = 1920
innerHeight = 1080
devicePixelRatio = 1
}
class MockCanvas extends EventTarget {
clientWidth = 1920
clientHeight = 1080
width = 1920
height = 1080
style = { pointerEvents: 'none' }
getBoundingClientRect() {
return { left: 0, top: 0, width: 1920, height: 1080 }
}
getContext() {
return null
}
}
class SyntheticKeyboardEvent extends Event {
readonly code: string
readonly key: string
constructor(type: string, init: { code: string; key: string }) {
super(type, { cancelable: true })
this.code = init.code
this.key = init.key
}
}
// Global DOM shims for HudManager
const mockWin = new MockWindow()
// @ts-ignore
globalThis.window = mockWin
// @ts-ignore
globalThis.HTMLInputElement = class HTMLInputElement {}
// @ts-ignore
globalThis.HTMLSelectElement = class HTMLSelectElement {}
let totalAssertions = 0
let passedAssertions = 0
let failedAssertions = 0
function assert(condition: boolean, message: string): void {
totalAssertions++
if (condition) {
passedAssertions++
} else {
failedAssertions++
console.error(`❌ ASSERTION FAILED: ${message}`)
throw new Error(`Assertion failed: ${message}`)
}
}
async function runTestSuite(): Promise<void> {
console.log('=================================================================')
console.log('🚀 RUNNING EMPIRICAL CHALLENGER STRESS-TEST HARNESS (M1 / R1)')
console.log('=================================================================')
// -----------------------------------------------------------------------------
// SUITE 1: Ghost Interaction Decoupling: KeyT & KeyB
// -----------------------------------------------------------------------------
console.log('\n--- [Suite 1] Ghost Interaction Decoupling: KeyT & KeyB ---')
{
const win = new MockWindow()
const input = new KeyboardInput(win as unknown as Window)
input.attach()
// 1.1 Initial state
assert(input.talking === false, 'Initial input.talking must be false')
assert(input.takeWaypoint() === false, 'Initial input.takeWaypoint() must be false')
// 1.2 KeyT dispatch (single, held, repeated)
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'KeyT', key: 't' }))
assert(input.talking === false, 'KeyT down must NOT set input.talking to true')
for (let tick = 0; tick < 100; tick++) {
assert(input.talking === false, `input.talking must remain false across 100 ticks of KeyT held (tick ${tick})`)
}
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'KeyT', key: 't' })) // repeat
assert(input.talking === false, 'Repeated KeyT keydown must NOT set input.talking')
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code: 'KeyT', key: 't' }))
assert(input.talking === false, 'KeyT keyup must leave input.talking false')
// 1.3 KeyB dispatch (single, held, repeated)
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'KeyB', key: 'b' }))
assert(input.takeWaypoint() === false, 'KeyB down must NOT trigger input.takeWaypoint()')
for (let tick = 0; tick < 100; tick++) {
assert(input.takeWaypoint() === false, `input.takeWaypoint() must remain false across 100 ticks of KeyB held (tick ${tick})`)
}
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'KeyB', key: 'b' }))
assert(input.takeWaypoint() === false, 'Repeated KeyB keydown must NOT trigger input.takeWaypoint()')
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code: 'KeyB', key: 'b' }))
assert(input.takeWaypoint() === false, 'KeyB keyup must leave input.takeWaypoint() false')
// 1.4 Test every printable key on the keyboard to ensure TALK_KEYS and WAYPOINT_KEYS are 100% empty
const testCodes = [
'KeyA', 'KeyB', 'KeyC', 'KeyD', 'KeyE', 'KeyF', 'KeyG', 'KeyH', 'KeyI', 'KeyJ',
'KeyK', 'KeyL', 'KeyM', 'KeyN', 'KeyO', 'KeyP', 'KeyQ', 'KeyR', 'KeyS', 'KeyT',
'KeyU', 'KeyV', 'KeyW', 'KeyX', 'KeyY', 'KeyZ', 'Digit1', 'Digit2', 'Digit3', 'Digit4',
'Space', 'Enter', 'Tab', 'Escape', 'Backspace'
]
for (const code of testCodes) {
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code, key: code.toLowerCase() }))
assert(input.talking === false, `Key ${code} must NOT set input.talking to true`)
assert(input.takeWaypoint() === false, `Key ${code} must NOT trigger input.takeWaypoint()`)
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code, key: code.toLowerCase() }))
}
}
// -----------------------------------------------------------------------------
// SUITE 2: Combat Safety: Space vs Combat Swings & Panel Dismissal
// -----------------------------------------------------------------------------
console.log('\n--- [Suite 2] Combat Safety: Space Removal from ATTACK_KEYS & Panel Dismissal ---')
{
const win = new MockWindow()
const input = new KeyboardInput(win as unknown as Window)
input.attach()
// 2.1 Space does not set input.attacking
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Space', key: ' ' }))
assert(input.attacking === false, 'Space down must NOT set input.attacking to true')
for (let tick = 0; tick < 100; tick++) {
assert(input.attacking === false, `Space held must remain attacking=false across 100 ticks (tick ${tick})`)
}
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code: 'Space', key: ' ' }))
assert(input.attacking === false, 'Space up must remain attacking=false')
// 2.2 Legitimate attack keys DO set input.attacking (Positive Control)
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'KeyJ', key: 'j' }))
assert(input.attacking === true, 'KeyJ down MUST set input.attacking to true')
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code: 'KeyJ', key: 'j' }))
assert(input.attacking === false, 'KeyJ up must clear input.attacking')
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Enter', key: 'Enter' }))
assert(input.attacking === true, 'Enter down MUST set input.attacking to true')
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code: 'Enter', key: 'Enter' }))
assert(input.attacking === false, 'Enter up must clear input.attacking')
// 2.3 GameEngine combat verification: Space does not swing or damage monsters
const dummyTerrain: WorldMapProvider = {
widthPx: 1000,
heightPx: 1000,
overlap: () => 0,
}
const dummyOpts: GameEngineOptions = {
spawn: { x: 100, y: 100 },
stats: [
{ id: 'mon1', name: 'Demon', hp: 100, damage: 0, cooldownTicks: 100, reach: 20, aggroRadius: 100, speed: 0, xp: 5 }
],
xpTable: [0, 0, 10, 30, 60],
itemBases: [
{ id: 'sword', name: 'Sword', kind: 'weapon', invWidth: 1, invHeight: 3, maxStack: 1, value: 10, damage: 10, defense: 0, tags: ['weap'], level: 1 }
],
prefixAffixes: [],
suffixAffixes: [],
skills: [
{ id: 'attack', name: 'Attack', manaCost: 0, cooldownTicks: 1, range: 48, projectile: false, speed: 0, baseMinDamage: 10, baseMaxDamage: 15, damagePerLevel: 1, radius: 20 },
],
npcDefs: [
{ id: 'akara', name: 'Akara', questId: null, offerLines: [], progressLines: [], doneLines: [] }
],
questDefs: [],
combatOptions: {
playerSpeed: 100,
playerReach: 50,
playerCooldownTicks: 1,
playerDamage: 10,
playerManaPerAttack: 0,
respawnTicks: 100,
},
lootSeed: 12345,
talkRadius: 50,
pickupRadius: 50,
inventoryCols: 10,
inventoryRows: 4,
}
const engine = new GameEngine(dummyTerrain, dummyOpts)
// Place monster right next to player (player at 100, 100; monster at 110, 100)
assert(engine.world.monsters.length > 0, 'Engine must have spawned monsters')
engine.world.monsters[0]!.x = 110
engine.world.monsters[0]!.y = 100
const initialMonsterHp = engine.world.monsters[0]!.hp
const initialHits = engine.metrics.playerHits
// Tick engine with Space held via KeyboardInput
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Space', key: ' ' }))
for (let tick = 0; tick < 50; tick++) {
engine.tick({
movement: input.movement(),
attacking: input.attacking,
pickingUp: input.pickingUp,
talking: input.talking,
digits: input.digits,
saving: input.saving,
loading: input.loading,
})
}
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code: 'Space', key: ' ' }))
assert(engine.world.monsters[0]!.hp === initialMonsterHp, `Space key MUST NOT cause damage to monster (HP was ${initialMonsterHp}, now ${engine.world.monsters[0]!.hp})`)
assert(engine.metrics.playerHits === initialHits, `Space key MUST NOT register combat hits (hits was ${initialHits}, now ${engine.metrics.playerHits})`)
// Compare with KeyJ: ticking with KeyJ MUST damage monster
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'KeyJ', key: 'j' }))
for (let tick = 0; tick < 10; tick++) {
engine.tick({
movement: input.movement(),
attacking: input.attacking,
pickingUp: input.pickingUp,
talking: input.talking,
digits: input.digits,
saving: input.saving,
loading: input.loading,
})
}
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code: 'KeyJ', key: 'j' }))
assert(engine.world.monsters[0]!.hp < initialMonsterHp, 'KeyJ attack MUST inflict damage on adjacent monster (Positive Control)')
assert(engine.metrics.playerHits > initialHits, 'KeyJ attack MUST register combat hits in metrics (Positive Control)')
// 2.4 Panel Dismissal in HudManager: Space and Escape
const hudCanvas = new MockCanvas() as unknown as HTMLCanvasElement
const hud = new HudManager(hudCanvas, {
onToggleAutomap: () => {},
onWaypointTeleport: () => {},
})
hud['bindEvents']()
// Test Space closing Character Sheet
hud.toggleLeftPanel('char')
assert(hud.leftPanel === 'char', 'Left panel should be char')
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Space', key: ' ' }))
assert(hud.leftPanel === 'none', 'Space MUST close Character Sheet')
// Test Space closing Inventory Panel
hud.toggleRightPanel('inv')
assert(hud.rightPanel === 'inv', 'Right panel should be inv')
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Space', key: ' ' }))
assert(hud.rightPanel === 'none', 'Space MUST close Inventory Panel')
// Test Space closing Skill Tree Panel
hud.toggleRightPanel('skill')
assert(hud.rightPanel === 'skill', 'Right panel should be skill')
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Space', key: ' ' }))
assert(hud.rightPanel === 'none', 'Space MUST close Skill Tree Panel')
// Test Space closing both panels + belt + popup concurrently
hud.toggleLeftPanel('quest')
hud.toggleRightPanel('skill')
hud.belt.lockedOpen = true
hud.hotkeys.openPopup = 'right'
assert(hud.leftPanel === 'quest' && hud.rightPanel === 'skill', 'Panels should be open')
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Space', key: ' ' }))
assert(hud.leftPanel === 'none', 'Space MUST close leftPanel')
assert(hud.rightPanel === 'none', 'Space MUST close rightPanel')
assert((hud.belt.lockedOpen as boolean) === false, 'Space MUST close lockedOpen belt')
assert(hud.hotkeys.openPopup === null, 'Space MUST close hotkey popup')
// Test Escape parity with Space
hud.toggleLeftPanel('waypoint')
hud.toggleRightPanel('inv')
assert(hud.leftPanel === 'waypoint' && hud.rightPanel === 'inv', 'Panels open before Escape')
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Escape', key: 'Escape' }))
assert(hud.leftPanel === 'none', 'Escape MUST close leftPanel')
assert(hud.rightPanel === 'none', 'Escape MUST close rightPanel')
}
// -----------------------------------------------------------------------------
// SUITE 3: Belt Potion Drinking & Skill Preservation (Keys 1..4)
// -----------------------------------------------------------------------------
console.log('\n--- [Suite 3] Belt Drinking & Skill Preservation (Keys 1..4) ---')
{
const win = new MockWindow()
const input = new KeyboardInput(win as unknown as Window)
input.attach()
// 3.1 Digit keys on KeyboardInput return []
for (const code of ['Digit1', 'Digit2', 'Digit3', 'Digit4']) {
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code, key: code.slice(-1) }))
assert(input.digits.length === 0, `input.digits MUST return [] when ${code} is pressed`)
}
assert(input.digits.length === 0, 'input.digits must be [] when all 1..4 are held')
for (const code of ['Digit1', 'Digit2', 'Digit3', 'Digit4']) {
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code, key: code.slice(-1) }))
}
// 3.2 GameEngine skill selection invariance
const dummyTerrain: WorldMapProvider = { widthPx: 1000, heightPx: 1000, overlap: () => 0 }
const dummyOpts: GameEngineOptions = {
spawn: { x: 100, y: 100 },
stats: [],
xpTable: [0, 0, 10, 30, 60],
itemBases: [],
prefixAffixes: [],
suffixAffixes: [],
skills: [
{ id: 'sk0', name: 'Skill 0', manaCost: 0, cooldownTicks: 1, range: 48, projectile: false, speed: 0, baseMinDamage: 1, baseMaxDamage: 2, damagePerLevel: 1, radius: 20 },
{ id: 'sk1', name: 'Skill 1', manaCost: 0, cooldownTicks: 1, range: 48, projectile: false, speed: 0, baseMinDamage: 1, baseMaxDamage: 2, damagePerLevel: 1, radius: 20 },
{ id: 'sk2', name: 'Skill 2', manaCost: 0, cooldownTicks: 1, range: 48, projectile: false, speed: 0, baseMinDamage: 1, baseMaxDamage: 2, damagePerLevel: 1, radius: 20 },
{ id: 'sk3', name: 'Skill 3', manaCost: 0, cooldownTicks: 1, range: 48, projectile: false, speed: 0, baseMinDamage: 1, baseMaxDamage: 2, damagePerLevel: 1, radius: 20 },
],
npcDefs: [],
questDefs: [],
combatOptions: { playerSpeed: 100, playerReach: 50, playerCooldownTicks: 1, playerDamage: 10, playerManaPerAttack: 0, respawnTicks: 100 },
lootSeed: 12345,
talkRadius: 50,
pickupRadius: 50,
inventoryCols: 10,
inventoryRows: 4,
}
const engine = new GameEngine(dummyTerrain, dummyOpts)
engine.selectedSkill = 2 // Choose Skill 2
assert(engine.selectedSkill === 2, 'Initial selectedSkill should be 2')
// Tick engine with Digit1..Digit4 pressed
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Digit1', key: '1' }))
engine.tick({
movement: input.movement(),
attacking: input.attacking,
pickingUp: input.pickingUp,
talking: input.talking,
digits: input.digits,
saving: input.saving,
loading: input.loading,
})
assert(engine.selectedSkill === 2, 'engine.selectedSkill MUST remain 2 after Digit1 tick')
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Digit4', key: '4' }))
engine.tick({
movement: input.movement(),
attacking: input.attacking,
pickingUp: input.pickingUp,
talking: input.talking,
digits: input.digits,
saving: input.saving,
loading: input.loading,
})
assert(engine.selectedSkill === 2, 'engine.selectedSkill MUST remain 2 after Digit4 tick')
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code: 'Digit1', key: '1' }))
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', { code: 'Digit4', key: '4' }))
// 3.3 HudManager Potion Drinking & Skill Preservation
const hudCanvas = new MockCanvas() as unknown as HTMLCanvasElement
const hud = new HudManager(hudCanvas, {
onToggleAutomap: () => {},
onWaypointTeleport: () => {},
})
hud['bindEvents']()
// Configure distinct active skills: Left = 47 (Fire Ball), Right = 64 (Frozen Orb)
hud.hotkeys.leftSkillId = 47
hud.hotkeys.rightSkillId = 64
hud.syncPublishedState()
assert(hud.state.leftSkillId === 47, 'Initial published leftSkillId should be 47')
assert(hud.state.rightSkillId === 64, 'Initial published rightSkillId should be 64')
// Lower HP and Mana to verify drinking effects
hud.hp = 300
hud.mana = 200
hud.syncPublishedState()
const initialTotalPotions = hud.belt.countTotalPotions()
assert(initialTotalPotions === 16, 'Full belt should have 16 potions (4 cols x 4 rows)')
// --- Drink Column 0 (Key '1') ---
const hpBefore1 = hud.hp
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Digit1', key: '1' }))
assert(hud.hp > hpBefore1, `Key 1 MUST consume health potion and heal HP (was ${hpBefore1}, now ${hud.hp})`)
assert(hud.hotkeys.leftSkillId === 47, 'Key 1 MUST NOT alter hotkeys.leftSkillId (remains 47)')
assert(hud.hotkeys.rightSkillId === 64, 'Key 1 MUST NOT alter hotkeys.rightSkillId (remains 64)')
assert(hud.state.leftSkillId === 47, 'Key 1 MUST NOT alter state.leftSkillId (remains 47)')
assert(hud.state.rightSkillId === 64, 'Key 1 MUST NOT alter state.rightSkillId (remains 64)')
assert(hud.belt.countTotalPotions() === initialTotalPotions - 1, 'Belt potion count must decrement by 1')
// --- Drink Column 1 (Key '2') ---
const hpBefore2 = hud.hp
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Digit2', key: '2' }))
assert(hud.hp >= hpBefore2, `Key 2 MUST consume column 1 potion (was ${hpBefore2}, now ${hud.hp})`)
assert(hud.hotkeys.leftSkillId === 47, 'Key 2 MUST NOT alter leftSkillId')
assert(hud.hotkeys.rightSkillId === 64, 'Key 2 MUST NOT alter rightSkillId')
assert(hud.state.leftSkillId === 47, 'Key 2 MUST NOT alter state leftSkillId')
assert(hud.state.rightSkillId === 64, 'Key 2 MUST NOT alter state rightSkillId')
assert(hud.belt.countTotalPotions() === initialTotalPotions - 2, 'Belt potion count must decrement by 2')
// --- Drink Column 2 (Key '3') ---
const manaBefore3 = hud.mana
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Digit3', key: '3' }))
assert(hud.mana > manaBefore3, `Key 3 MUST consume mana potion and restore Mana (was ${manaBefore3}, now ${hud.mana})`)
assert(hud.hotkeys.leftSkillId === 47, 'Key 3 MUST NOT alter leftSkillId')
assert(hud.hotkeys.rightSkillId === 64, 'Key 3 MUST NOT alter rightSkillId')
assert(hud.state.leftSkillId === 47, 'Key 3 MUST NOT alter state leftSkillId')
assert(hud.state.rightSkillId === 64, 'Key 3 MUST NOT alter state rightSkillId')
assert(hud.belt.countTotalPotions() === initialTotalPotions - 3, 'Belt potion count must decrement by 3')
// --- Drink Column 3 (Key '4') ---
hud.hp = 500
hud.mana = 200
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: 'Digit4', key: '4' }))
assert(hud.hp === 500 + 400 && hud.mana === 200 + 400, `Key 4 MUST consume rejuv potion and heal 400 HP & Mana (hp: ${hud.hp}, mana: ${hud.mana})`)
assert(hud.hotkeys.leftSkillId === 47, 'Key 4 MUST NOT alter leftSkillId')
assert(hud.hotkeys.rightSkillId === 64, 'Key 4 MUST NOT alter rightSkillId')
assert(hud.state.leftSkillId === 47, 'Key 4 MUST NOT alter state leftSkillId')
assert(hud.state.rightSkillId === 64, 'Key 4 MUST NOT alter state rightSkillId')
assert(hud.belt.countTotalPotions() === initialTotalPotions - 4, 'Belt potion count must decrement by 4')
// 3.4 Exhaust remaining belt slots (drink all 12 remaining potions)
for (let c = 0; c < 4; c++) {
for (let r = 0; r < 3; r++) {
hud.drinkBeltSlot(c)
}
}
assert(hud.belt.countTotalPotions() === 0, 'Belt should now be completely empty (0 potions)')
// Adversarial test: pressing 1..4 on empty belt
for (const key of ['1', '2', '3', '4']) {
const consumed = hud.drinkBeltSlot(Number(key) - 1)
assert(consumed === false, `Drinking from empty slot column ${Number(key) - 1} must return false`)
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', { code: `Digit${key}`, key }))
assert(hud.hotkeys.leftSkillId === 47, `Empty belt press ${key} must NOT alter leftSkillId`)
assert(hud.hotkeys.rightSkillId === 64, `Empty belt press ${key} must NOT alter rightSkillId`)
assert(!Number.isNaN(hud.hp) && !Number.isNaN(hud.mana), `Empty belt press ${key} must NOT cause NaN`)
}
// Boundary column tests: out-of-range columns
assert(hud.drinkBeltSlot(-1) === false, 'drinkBeltSlot(-1) must return false')
assert(hud.drinkBeltSlot(4) === false, 'drinkBeltSlot(4) must return false')
assert(hud.drinkBeltSlot(100) === false, 'drinkBeltSlot(100) must return false')
assert(hud.hotkeys.leftSkillId === 47 && hud.hotkeys.rightSkillId === 64, 'Out-of-range drinkBeltSlot must not mutate skills')
}
// -----------------------------------------------------------------------------
// SUITE 4: Adversarial Fuzzing & Differential Invariant Testing (5,000 Iterations)
// -----------------------------------------------------------------------------
console.log('\n--- [Suite 4] Adversarial Fuzzing & Differential Testing (5,000 Iterations) ---')
{
const win = new MockWindow()
const input = new KeyboardInput(win as unknown as Window)
input.attach()
const hudCanvas = new MockCanvas() as unknown as HTMLCanvasElement
const hud = new HudManager(hudCanvas, {
onToggleAutomap: () => {},
onWaypointTeleport: () => {},
})
hud['bindEvents']()
hud.hotkeys.leftSkillId = 47
hud.hotkeys.rightSkillId = 64
hud.syncPublishedState()
const candidateKeys = [
{ code: 'KeyT', key: 't' },
{ code: 'KeyB', key: 'b' },
{ code: 'Space', key: ' ' },
{ code: 'Digit1', key: '1' },
{ code: 'Digit2', key: '2' },
{ code: 'Digit3', key: '3' },
{ code: 'Digit4', key: '4' },
{ code: 'KeyW', key: 'w' },
{ code: 'KeyA', key: 'a' },
{ code: 'KeyS', key: 's' },
{ code: 'KeyD', key: 'd' },
{ code: 'KeyC', key: 'c' },
{ code: 'KeyJ', key: 'j' },
{ code: 'Enter', key: 'Enter' },
{ code: 'ShiftLeft', key: 'Shift' },
{ code: 'ShiftRight', key: 'Shift' },
{ code: 'ArrowUp', key: 'ArrowUp' },
{ code: 'ArrowDown', key: 'ArrowDown' },
{ code: 'ArrowLeft', key: 'ArrowLeft' },
{ code: 'ArrowRight', key: 'ArrowRight' },
{ code: 'Escape', key: 'Escape' },
]
const currentlyHeld = new Set<string>()
// Run 5,000 randomized state transitions
const NUM_ITERATIONS = 5000
for (let i = 0; i < NUM_ITERATIONS; i++) {
const candidate = candidateKeys[Math.floor(Math.random() * candidateKeys.length)]!
const isDown = Math.random() > 0.45 // 55% keydown, 45% keyup
if (isDown) {
currentlyHeld.add(candidate.code)
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', candidate))
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', candidate))
} else {
currentlyHeld.delete(candidate.code)
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', candidate))
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keyup', candidate))
}
// --- INVARIANT ORACLE VERIFICATIONS ---
// Invariant 1: input.talking is ALWAYS false (KeyT decoupled)
assert(input.talking === false, `[Fuzz Step ${i}] input.talking MUST be false (held: ${Array.from(currentlyHeld).join(',')})`)
// Invariant 2: input.takeWaypoint() is ALWAYS false (KeyB decoupled)
assert(input.takeWaypoint() === false, `[Fuzz Step ${i}] input.takeWaypoint() MUST be false (held: ${Array.from(currentlyHeld).join(',')})`)
// Invariant 3: Space NEVER contributes to attacking
const expectedAttacking = currentlyHeld.has('KeyJ') || currentlyHeld.has('Enter')
assert(input.attacking === expectedAttacking, `[Fuzz Step ${i}] input.attacking mismatch: expected ${expectedAttacking}, got ${input.attacking}`)
// Invariant 4: input.digits is ALWAYS []
assert(input.digits.length === 0, `[Fuzz Step ${i}] input.digits MUST be empty array`)
// Invariant 5: WASD NEVER contributes to movement
const mov = input.movement()
if (!currentlyHeld.has('ArrowUp') && !currentlyHeld.has('ArrowDown') && !currentlyHeld.has('ArrowLeft') && !currentlyHeld.has('ArrowRight')) {
assert(mov.x === 0 && mov.y === 0, `[Fuzz Step ${i}] Movement must be {0,0} when no arrow keys held, even if WASD are held. Got {${mov.x}, ${mov.y}}`)
}
// Invariant 6: Shift tracking
const expectedShift = currentlyHeld.has('ShiftLeft') || currentlyHeld.has('ShiftRight')
assert(input.shiftHeld === expectedShift, `[Fuzz Step ${i}] ShiftHeld mismatch: expected ${expectedShift}, got ${input.shiftHeld}`)
// Invariant 7: Active skill IDs in HudManager remain strictly untouched
assert(hud.hotkeys.leftSkillId === 47, `[Fuzz Step ${i}] leftSkillId mutated! Got ${hud.hotkeys.leftSkillId}`)
assert(hud.hotkeys.rightSkillId === 64, `[Fuzz Step ${i}] rightSkillId mutated! Got ${hud.hotkeys.rightSkillId}`)
assert(hud.state.leftSkillId === 47, `[Fuzz Step ${i}] state.leftSkillId mutated! Got ${hud.state.leftSkillId}`)
assert(hud.state.rightSkillId === 64, `[Fuzz Step ${i}] state.rightSkillId mutated! Got ${hud.state.rightSkillId}`)
}
console.log(`✅ Passed 5,000 randomized fuzzing iterations with 7 live invariant checks per step (${NUM_ITERATIONS * 7} assertions)`)
}
// -----------------------------------------------------------------------------
// SUITE 5: Simultaneous Chord Stress (All problematic keys pressed at once)
// -----------------------------------------------------------------------------
console.log('\n--- [Suite 5] Simultaneous Chord Stress (Space + KeyT + KeyB + 1..4 + WASD) ---')
{
const win = new MockWindow()
const input = new KeyboardInput(win as unknown as Window)
input.attach()
const hudCanvas = new MockCanvas() as unknown as HTMLCanvasElement
const hud = new HudManager(hudCanvas, {
onToggleAutomap: () => {},
onWaypointTeleport: () => {},
})
hud['bindEvents']()
hud.hotkeys.leftSkillId = 47
hud.hotkeys.rightSkillId = 64
hud.syncPublishedState()
const chord = [
{ code: 'KeyA', key: 'a' },
{ code: 'KeyT', key: 't' },
{ code: 'KeyB', key: 'b' },
{ code: 'Digit1', key: '1' },
{ code: 'Digit2', key: '2' },
{ code: 'Digit3', key: '3' },
{ code: 'Digit4', key: '4' },
{ code: 'KeyW', key: 'w' },
{ code: 'KeyS', key: 's' },
{ code: 'KeyD', key: 'd' },
{ code: 'Space', key: ' ' },
]
for (const evt of chord) {
win.dispatchEvent(new SyntheticKeyboardEvent('keydown', evt))
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keydown', evt))
}
assert(input.talking === false, 'Simultaneous chord: talking MUST be false')
assert(input.takeWaypoint() === false, 'Simultaneous chord: takeWaypoint MUST be false')
assert(input.attacking === false, 'Simultaneous chord: attacking MUST be false (Space does not attack)')
assert(input.digits.length === 0, 'Simultaneous chord: digits MUST be []')
assert(input.movement().x === 0 && input.movement().y === 0, 'Simultaneous chord: movement MUST be {0, 0}')
assert(input.active === false, 'Simultaneous chord: active MUST be false')
assert(hud.leftPanel === 'none', 'Simultaneous chord: Space closes leftPanel')
assert(hud.rightPanel === 'none', 'Simultaneous chord: Space closes rightPanel')
assert(hud.hotkeys.leftSkillId === 47, 'Simultaneous chord: leftSkillId is 47')
assert(hud.hotkeys.rightSkillId === 64, 'Simultaneous chord: rightSkillId is 64')
for (const evt of chord) {
win.dispatchEvent(new SyntheticKeyboardEvent('keyup', evt))
mockWin.dispatchEvent(new SyntheticKeyboardEvent('keyup', evt))
}
}
console.log('\n=================================================================')
console.log(`🎉 EMPIRICAL STRESS TEST RESULTS:`)
console.log(`Total Assertions Executed: ${totalAssertions}`)
console.log(`Assertions Passed: ${passedAssertions}`)
console.log(`Assertions Failed: ${failedAssertions}`)
console.log('Verdict: EMPIRICAL PROOF OF CORRECTNESS CONFIRMED')
console.log('=================================================================')
}
runTestSuite().catch((err) => {
console.error('Fatal error in stress test harness:', err)
process.exit(1)
})