/** * Challenger & Empirical Stress Testing Suite for Milestone M1 (Requirement R1). * * Comprehensive empirical verification against `src/sim/input.ts` and `src/ui/hud-manager.ts`: * 1. Unit & Boundary Assertions on KeyboardInput (WASD, Arrows, Diagonals, Opposites, Shift, Digits, Space, Talk, Waypoint) * 2. Differential Fuzzing: 1,000 randomized keyboard states against an independent oracle * 3. In-Memory HUD Manager State Transitions (Space/Escape dismissal, KeyS/KeyT Skill Tree, KeyW Weapon Swap, 1..4 Potions, Input Element Suppression) * 4. Headless Chrome CDP In-Engine Live Gameplay Verification (`acts.html?act=1`): * - KeyW, KeyA, KeyS, KeyD zero player movement verification * - Real keyboard hotkey panel toggling & dismissal * - Weapon swap verification * - Accessibility arrow key navigation * - Visual screenshot capture */ import { spawn } from 'node:child_process' import { mkdirSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import { createServer } from 'vite' import { KeyboardInput, directionOf, DIRECTION, type Movement } from '../src/sim/input.ts' import { HudManager } from '../src/ui/hud-manager.ts' const HANDOFF_DIR = '/usr/local/google/home/taodao/d2w-wg1-main/.agents/teamwork_preview_challenger_m1_1' const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) interface TestResult { name: string passed: boolean error?: string } const results: TestResult[] = [] function assert(condition: boolean, message: string): void { if (!condition) { throw new Error(`Assertion failed: ${message}`) } } function assertClose(a: number, b: number, epsilon = 1e-4, msg = ''): void { if (Math.abs(a - b) > epsilon) { throw new Error(`Assertion failed: expected ${a} to be close to ${b} (diff=${Math.abs(a - b)} > ${epsilon}). ${msg}`) } } // ============================================================================ // PART 1: IN-MEMORY KEYBOARDINPUT UNIT & ADVERSARIAL STRESS // ============================================================================ function runKeyboardInputTests(): void { console.log('\n--- PART 1: In-Memory KeyboardInput Empirical Verification ---') const listeners = new Map void>() const mockWindow = { addEventListener: (type: string, fn: any) => listeners.set(type, fn), removeEventListener: (type: string, fn: any) => listeners.delete(type), } const input = new KeyboardInput(mockWindow as unknown as Window) input.attach() const press = (code: string) => { let prevented = false const evt = { code, preventDefault: () => { prevented = true }, } listeners.get('keydown')?.(evt) return prevented } const release = (code: string) => { listeners.get('keyup')?.({ code }) } const blur = () => { listeners.get('blur')?.({}) } // 1.1 WASD keys produce {x: 0, y: 0} and active === false console.log(' 1.1 Testing WASD keys individual and combined...') for (const code of ['KeyW', 'KeyA', 'KeyS', 'KeyD']) { press(code) const m = input.movement() assert(m.x === 0 && m.y === 0, `${code} produced non-zero movement: ${JSON.stringify(m)}`) assert(input.active === false, `${code} set active to true`) assert(directionOf(m) === null, `${code} produced direction: ${directionOf(m)}`) release(code) } // Combinations of WASD press('KeyW') press('KeyA') assert(input.movement().x === 0 && input.movement().y === 0, 'WA produced movement') assert(input.active === false, 'WA produced active: true') press('KeyS') press('KeyD') assert(input.movement().x === 0 && input.movement().y === 0, 'WASD produced movement') assert(input.active === false, 'WASD produced active: true') release('KeyW') release('KeyA') release('KeyS') release('KeyD') // 1.2 Arrow keys produce correct unit vectors and active === true console.log(' 1.2 Testing Arrow keys unit vectors and active === true...') // Up press('ArrowUp') assert(input.active === true, 'ArrowUp active is false') assertClose(input.movement().x, 0, 1e-4, 'ArrowUp x') assertClose(input.movement().y, -1, 1e-4, 'ArrowUp y') assert(directionOf(input.movement()) === DIRECTION.North, 'ArrowUp direction not North') release('ArrowUp') assert(input.active === false, 'ArrowUp release active still true') // Down press('ArrowDown') assert(input.active === true, 'ArrowDown active is false') assertClose(input.movement().x, 0, 1e-4, 'ArrowDown x') assertClose(input.movement().y, 1, 1e-4, 'ArrowDown y') assert(directionOf(input.movement()) === DIRECTION.South, 'ArrowDown direction not South') release('ArrowDown') // Left press('ArrowLeft') assert(input.active === true, 'ArrowLeft active is false') assertClose(input.movement().x, -1, 1e-4, 'ArrowLeft x') assertClose(input.movement().y, 0, 1e-4, 'ArrowLeft y') assert(directionOf(input.movement()) === DIRECTION.West, 'ArrowLeft direction not West') release('ArrowLeft') // Right press('ArrowRight') assert(input.active === true, 'ArrowRight active is false') assertClose(input.movement().x, 1, 1e-4, 'ArrowRight x') assertClose(input.movement().y, 0, 1e-4, 'ArrowRight y') assert(directionOf(input.movement()) === DIRECTION.East, 'ArrowRight direction not East') release('ArrowRight') // 1.3 Diagonal normalisation (hypot === 1.0, not √2) console.log(' 1.3 Testing Diagonal arrows normalisation...') press('ArrowUp') press('ArrowRight') const diagNE = input.movement() assert(input.active === true, 'Diag active is false') assertClose(Math.hypot(diagNE.x, diagNE.y), 1.0, 1e-4, 'DiagNE magnitude not 1.0') assertClose(diagNE.x, Math.SQRT1_2, 1e-4, 'DiagNE x') assertClose(diagNE.y, -Math.SQRT1_2, 1e-4, 'DiagNE y') assert(directionOf(diagNE) === DIRECTION.NorthEast, 'DiagNE direction') release('ArrowUp') release('ArrowRight') press('ArrowDown') press('ArrowLeft') const diagSW = input.movement() assertClose(Math.hypot(diagSW.x, diagSW.y), 1.0, 1e-4, 'DiagSW magnitude') assertClose(diagSW.x, -Math.SQRT1_2, 1e-4, 'DiagSW x') assertClose(diagSW.y, Math.SQRT1_2, 1e-4, 'DiagSW y') assert(directionOf(diagSW) === DIRECTION.SouthWest, 'DiagSW direction') release('ArrowDown') release('ArrowLeft') // 1.4 Opposing arrows cancellation console.log(' 1.4 Testing Opposing arrows cancellation...') press('ArrowUp') press('ArrowDown') assert(input.active === true, 'Opposing up/down active should be true') assert(input.movement().x === 0 && input.movement().y === 0, 'Opposing up/down non-zero vector') assert(directionOf(input.movement()) === null, 'Opposing up/down direction not null') release('ArrowUp') release('ArrowDown') // 1.5 ShiftLeft / ShiftRight tracking console.log(' 1.5 Testing ShiftLeft / ShiftRight state...') assert(input.shiftHeld === false, 'Shift initially held') press('ShiftLeft') assert(input.shiftHeld === true, 'ShiftLeft down did not set shiftHeld') release('ShiftLeft') assert(input.shiftHeld === false, 'ShiftLeft up did not clear shiftHeld') press('ShiftRight') assert(input.shiftHeld === true, 'ShiftRight down did not set shiftHeld') release('ShiftRight') assert(input.shiftHeld === false, 'ShiftRight up did not clear shiftHeld') press('ShiftLeft') press('ShiftRight') assert(input.shiftHeld === true, 'Both shifts down') release('ShiftLeft') assert(input.shiftHeld === true, 'One shift released, other still down') release('ShiftRight') assert(input.shiftHeld === false, 'Both shifts released') // 1.6 Digit1..4 do not appear in digits console.log(' 1.6 Testing Digit1..4 exclusion from digits...') for (const digit of ['Digit1', 'Digit2', 'Digit3', 'Digit4', 'Digit5']) { press(digit) assert(input.digits.length === 0, `${digit} appeared in digits: ${JSON.stringify(input.digits)}`) release(digit) } // 1.7 Space does not set attacking === true console.log(' 1.7 Testing Space key attacking suppression...') press('Space') assert(input.attacking === false, 'Space key set attacking to true!') release('Space') assert(input.attacking === false, 'After Space release') // Verify KeyJ and Enter do set attacking === true press('KeyJ') assert(input.attacking === true, 'KeyJ failed to set attacking: true') release('KeyJ') assert(input.attacking === false, 'KeyJ release failed to clear attacking') press('Enter') assert(input.attacking === true, 'Enter failed to set attacking: true') release('Enter') assert(input.attacking === false, 'Enter release failed to clear attacking') // 1.8 Ghost interaction decoupling (KeyT and KeyB) console.log(' 1.8 Testing Ghost interaction keys decoupling (KeyT, KeyB)...') press('KeyT') assert(input.talking === false, 'KeyT set talking: true') release('KeyT') press('KeyB') assert(input.takeWaypoint() === false, 'KeyB triggered takeWaypoint()') release('KeyB') // 1.9 Blur resets all held state console.log(' 1.9 Testing blur event resets all held state...') press('ArrowUp') press('ShiftLeft') press('KeyJ') assert(input.active === true, 'Pre-blur active') assert(input.shiftHeld === true, 'Pre-blur shiftHeld') assert(input.attacking === true, 'Pre-blur attacking') blur() assert(input.active === false, 'Post-blur active not false') assert(input.shiftHeld === false, 'Post-blur shiftHeld not false') assert(input.attacking === false, 'Post-blur attacking not false') assert(input.movement().x === 0 && input.movement().y === 0, 'Post-blur movement not zero') input.detach() console.log(' āœ… All In-Memory KeyboardInput tests passed!') } // ============================================================================ // PART 2: DIFFERENTIAL FUZZING (ORACLE VS IMPLEMENTATION) // ============================================================================ function runDifferentialFuzzing(iterations = 1000): void { console.log(`\n--- PART 2: Differential Fuzzing (${iterations} iterations) ---`) const ARROWS: Record = { ArrowUp: [0, -1], ArrowDown: [0, 1], ArrowLeft: [-1, 0], ArrowRight: [1, 0], } const ALL_TEST_KEYS = [ 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyA', 'KeyS', 'KeyD', 'Digit1', 'Digit2', 'Digit3', 'Digit4', 'Space', 'ShiftLeft', 'ShiftRight', 'KeyJ', 'Enter', 'KeyT', 'KeyB', 'KeyE', 'Tab', 'KeyQ', 'KeyC', 'KeyV', 'KeyI', 'KeyR', 'KeyG', 'KeyP', 'KeyK', 'KeyL', ] const listeners = new Map void>() const mockWindow = { addEventListener: (type: string, fn: any) => listeners.set(type, fn), removeEventListener: (type: string, fn: any) => listeners.delete(type), } const input = new KeyboardInput(mockWindow as unknown as Window) input.attach() // Simple, obviously correct Oracle implementation function oracle(heldKeys: Set) { let ox = 0 let oy = 0 let active = false for (const k of heldKeys) { const v = ARROWS[k] if (v) { ox += v[0] oy += v[1] active = true } } const mag = Math.hypot(ox, oy) const movement: Movement = mag === 0 ? { x: 0, y: 0 } : { x: ox / mag, y: oy / mag } const shiftHeld = heldKeys.has('ShiftLeft') || heldKeys.has('ShiftRight') const attacking = heldKeys.has('KeyJ') || heldKeys.has('Enter') const digits: number[] = [] // Invariant: must be empty const talking = false // Invariant: must be false return { movement, active, shiftHeld, attacking, digits, talking } } // Seeded pseudo-random generator let seed = 1337 const rand = () => { seed = (seed * 1664525 + 1013904223) % 4294967296 return seed / 4294967296 } const currentlyHeld = new Set() for (let iter = 0; iter < iterations; iter++) { // Pick 1-5 keys to toggle const numToggles = Math.floor(rand() * 5) + 1 for (let t = 0; t < numToggles; t++) { const key = ALL_TEST_KEYS[Math.floor(rand() * ALL_TEST_KEYS.length)]! if (currentlyHeld.has(key)) { currentlyHeld.delete(key) listeners.get('keyup')?.({ code: key }) } else { currentlyHeld.add(key) listeners.get('keydown')?.({ code: key, preventDefault: () => {} }) } } const exp = oracle(currentlyHeld) const actMov = input.movement() assertClose(actMov.x, exp.movement.x, 1e-4, `Iter ${iter} Movement X mismatch on keys: ${Array.from(currentlyHeld).join(',')}`) assertClose(actMov.y, exp.movement.y, 1e-4, `Iter ${iter} Movement Y mismatch on keys: ${Array.from(currentlyHeld).join(',')}`) assert(input.active === exp.active, `Iter ${iter} Active mismatch: actual ${input.active} vs oracle ${exp.active}`) assert(input.shiftHeld === exp.shiftHeld, `Iter ${iter} ShiftHeld mismatch`) assert(input.attacking === exp.attacking, `Iter ${iter} Attacking mismatch`) assert(input.digits.length === 0, `Iter ${iter} Digits must be empty`) assert(input.talking === false, `Iter ${iter} Talking must be false`) } // Clear all listeners.get('blur')?.({}) input.detach() console.log(` āœ… Differential fuzzing passed all ${iterations} iterations with 0 discrepancies!`) } // ============================================================================ // PART 3: IN-MEMORY HUD MANAGER KEY HANDLING & STATE TRANSITIONS // ============================================================================ function runHudManagerKeyTests(): void { console.log('\n--- PART 3: In-Memory HudManager Key Handling & State Transitions ---') const windowListeners = new Map void>() const mockWindow = { addEventListener: (type: string, fn: any) => windowListeners.set(type, fn), removeEventListener: (type: string, fn: any) => windowListeners.delete(type), devicePixelRatio: 1.0, innerWidth: 1920, innerHeight: 1080, } // Mock global DOM constructors if running in pure Node if (typeof (globalThis as any).window === 'undefined') { ;(globalThis as any).window = mockWindow } if (typeof (globalThis as any).HTMLInputElement === 'undefined') { ;(globalThis as any).HTMLInputElement = class {} } if (typeof (globalThis as any).HTMLSelectElement === 'undefined') { ;(globalThis as any).HTMLSelectElement = class {} } const mockCanvas = { width: 1920, height: 1080, clientWidth: 1920, clientHeight: 1080, style: { pointerEvents: 'none' }, getBoundingClientRect: () => ({ left: 0, top: 0, width: 1920, height: 1080 }), getContext: () => null, addEventListener: () => {}, } as unknown as HTMLCanvasElement const hud = new HudManager(mockCanvas, { onToggleAutomap: () => {}, onWaypointTeleport: () => {}, }) // Wire events via private bindEvents() ;(hud as any).bindEvents() const fireKey = (key: string, code: string, target?: any) => { let prevented = false const evt = { key, code, target: target ?? {}, preventDefault: () => { prevented = true }, } windowListeners.get('keydown')?.(evt) return prevented } // 3.1 KeyS and KeyT toggle Skill Tree console.log(' 3.1 Testing KeyS and KeyT toggle Skill Tree...') assert(hud.rightPanel === 'none', 'Initial right panel') fireKey('s', 'KeyS') assert(hud.rightPanel === 'skill', 'KeyS did not open skill tree') assert(hud.skillTree.visible === true, 'Skill tree not visible') fireKey('s', 'KeyS') assert(hud.rightPanel === 'none', 'KeyS did not close skill tree') assert(hud.skillTree.visible === false, 'Skill tree still visible') fireKey('t', 'KeyT') assert(hud.rightPanel === 'skill', 'KeyT did not open skill tree') fireKey('t', 'KeyT') assert(hud.rightPanel === 'none', 'KeyT did not close skill tree') // Alternate: KeyS opens, KeyT closes fireKey('s', 'KeyS') assert(hud.rightPanel === 'skill', 'KeyS open') fireKey('t', 'KeyT') assert(hud.rightPanel === 'none', 'KeyT close') // 3.2 KeyW toggles Weapon Swap console.log(' 3.2 Testing KeyW toggles Weapon Swap...') assert(hud.inventory.weaponSwapSet === 0, 'Initial weaponSwapSet is not 0') fireKey('w', 'KeyW') assert(hud.inventory.weaponSwapSet === 1, 'KeyW did not switch to weapon set II (1)') fireKey('w', 'KeyW') assert(hud.inventory.weaponSwapSet === 0, 'KeyW did not switch back to weapon set I (0)') // 3.3 Space and Escape call closeAllPanels() console.log(' 3.3 Testing Space and Escape panel closing...') // Test Character Sheet dismissal via Space fireKey('a', 'KeyA') assert(hud.leftPanel === 'char', 'KeyA open char sheet') const preventedSpace = fireKey(' ', 'Space') assert(hud.leftPanel === 'none', 'Space failed to close char sheet') assert(preventedSpace === true, 'Space should preventDefault when closing panel') // Test Skill Tree dismissal via Escape fireKey('s', 'KeyS') assert(hud.rightPanel === 'skill', 'KeyS open skill tree') const preventedEscape = fireKey('Escape', 'Escape') assert(hud.rightPanel === 'none', 'Escape failed to close skill tree') assert(preventedEscape === true, 'Escape should preventDefault when closing panel') // Test Multi-Panel dismissal via Space fireKey('c', 'KeyC') fireKey('i', 'KeyI') assert(hud.leftPanel === 'char', 'Char open') assert(hud.rightPanel === 'inv', 'Inv open') fireKey(' ', 'Space') assert(hud.leftPanel === 'none', 'Space failed to close left panel') assert(hud.rightPanel === 'none', 'Space failed to close right panel') // Test Space when no panels open returns false const preventedNoOp = fireKey(' ', 'Space') assert(preventedNoOp === false, 'Space when no panels open should not preventDefault') // 3.4 Number keys 1..4 drink belt potions console.log(' 3.4 Testing Number keys 1..4 drink belt potions without altering skills...') hud.hp = 500 hud.mana = 400 const initialLeftSkill = hud.hotkeys.leftSkillId const initialRightSkill = hud.hotkeys.rightSkillId const initialPotions = hud.belt.countTotalPotions() fireKey('1', 'Digit1') assert(hud.belt.countTotalPotions() === initialPotions - 1, 'Potion 1 not consumed') assert(hud.hp > 500, 'HP did not increase after drinking potion 1') assert(hud.hotkeys.leftSkillId === initialLeftSkill, 'leftSkillId changed!') assert(hud.hotkeys.rightSkillId === initialRightSkill, 'rightSkillId changed!') fireKey('2', 'Digit2') assert(hud.belt.countTotalPotions() === initialPotions - 2, 'Potion 2 not consumed') // 3.5 Target element filtering (input / select) console.log(' 3.5 Testing Input / Select element key event suppression...') const inputEl = new (globalThis as any).HTMLInputElement() fireKey('s', 'KeyS', inputEl) assert(hud.rightPanel === 'none', 'KeyS inside input element toggled skill tree!') fireKey('w', 'KeyW', inputEl) assert(hud.inventory.weaponSwapSet === 0, 'KeyW inside input element toggled weapon swap!') console.log(' āœ… All In-Memory HudManager tests passed!') } // ============================================================================ // PART 4: HEADLESS CHROME CDP IN-ENGINE LIVE GAMEPLAY VERIFICATION // ============================================================================ async function runChromeCdpVerification(): Promise { console.log('\n--- PART 4: Headless Chrome CDP Live Gameplay Invariant Verification ---') mkdirSync(HANDOFF_DIR, { recursive: true }) const server = await createServer({ root: process.cwd(), server: { port: 5199, host: '127.0.0.1', strictPort: false }, }) await server.listen() const port = (server.httpServer?.address() as any)?.port ?? 5199 const baseUrl = `http://127.0.0.1:${port}` console.log(` Vite server running at: ${baseUrl}`) const debugPort = 9255 const chromeProc = spawn('/usr/bin/google-chrome', [ '--headless=new', `--remote-debugging-port=${debugPort}`, '--no-sandbox', '--disable-dev-shm-usage', '--enable-webgl', '--ignore-gpu-blocklist', '--use-gl=angle', '--use-angle=swiftshader', '--window-size=1280,840', 'about:blank', ]) try { let wsUrl: string | null = null for (let i = 0; i < 60; i++) { await sleep(200) try { const res = await fetch(`http://127.0.0.1:${debugPort}/json/list`) const pages = (await res.json()) as any[] const p = pages.find(x => x.type === 'page') if (p?.webSocketDebuggerUrl) { wsUrl = p.webSocketDebuggerUrl break } } catch {} } if (!wsUrl) throw new Error('Could not connect to Chrome CDP') console.log(' Connected to Chrome CDP WebSocket.') const ws = new WebSocket(wsUrl) await new Promise(res => { ws.onopen = () => res() }) let idCounter = 1 const send = (method: string, params: Record = {}): Promise => new Promise((resolve, reject) => { const id = idCounter++ const handler = (event: MessageEvent) => { const msg = JSON.parse(String(event.data)) if (msg.id === id) { ws.removeEventListener('message', handler) if (msg.error) reject(new Error(`CDP ${method} failed: ${JSON.stringify(msg.error)}`)) else resolve(msg.result as T) } } ws.addEventListener('message', handler) ws.send(JSON.stringify({ id, method, params })) }) await send('Page.enable') await send('Runtime.enable') const evalJs = async (expr: string): Promise => { const res = await send('Runtime.evaluate', { expression: expr, awaitPromise: true, returnByValue: true }) if (res.exceptionDetails) throw new Error(`JS error: ${JSON.stringify(res.exceptionDetails)}`) return res.result.value } const saveScreenshot = async (name: string): Promise => { const shot = await send('Page.captureScreenshot', { format: 'png' }) const p = join(HANDOFF_DIR, name) writeFileSync(p, Buffer.from(shot.data, 'base64')) console.log(` šŸ“ø Saved screenshot: ${name}`) return p } const pressKeyCdp = async (key: string, code: string, holdMs = 50) => { await send('Input.dispatchKeyEvent', { type: 'keyDown', key, code, windowsVirtualKeyCode: key.charCodeAt(0) }) if (holdMs > 0) await sleep(holdMs) await send('Input.dispatchKeyEvent', { type: 'keyUp', key, code, windowsVirtualKeyCode: key.charCodeAt(0) }) } await send('Page.navigate', { url: `${baseUrl}/acts.html?act=1` }) // Wait for engine and HUD readiness console.log(' Waiting for __d2webAct and __d2webHud...') for (let i = 0; i < 100; i++) { await sleep(250) const ready = await evalJs(`Boolean(window.__d2webAct?.ready && window.__d2webHud?.ready)`) if (ready) break } await sleep(500) const ready = await evalJs(`Boolean(window.__d2webAct?.ready && window.__d2webHud?.ready)`) assert(ready === true, 'Timed out waiting for scene readiness') console.log(' āœ… Engine & HUD ready.') // 4.1 Test WASD produces ZERO player movement console.log(' 4.1 Testing WASD keys produce 0 player displacement in live engine...') const posInitial = await evalJs<{ x: number; y: number }>(`({ x: window.__d2webAct.x, y: window.__d2webAct.y })`) // Press W, A, S, D repeatedly for (let i = 0; i < 5; i++) { await pressKeyCdp('w', 'KeyW', 50) await pressKeyCdp('a', 'KeyA', 50) await pressKeyCdp('s', 'KeyS', 50) await pressKeyCdp('d', 'KeyD', 50) await sleep(50) } await sleep(300) const posAfterWasd = await evalJs<{ x: number; y: number }>(`({ x: window.__d2webAct.x, y: window.__d2webAct.y })`) console.log(` Player coordinates before WASD: (${posInitial.x}, ${posInitial.y}), after: (${posAfterWasd.x}, ${posAfterWasd.y})`) assert(posInitial.x === posAfterWasd.x && posInitial.y === posAfterWasd.y, `Player moved during WASD! Initial: ${JSON.stringify(posInitial)}, After: ${JSON.stringify(posAfterWasd)}`) console.log(' āœ… VERIFIED: WASD keys produce exactly 0 player displacement.') // Close any panels opened during WASD test (e.g. A opened char, S opened skill) await pressKeyCdp(' ', 'Space', 50) await sleep(100) // 4.2 Test KeyA opens Character Sheet and Space closes it console.log(' 4.2 Testing KeyA opens Character Sheet and Space closes it...') await pressKeyCdp('a', 'KeyA') await sleep(150) const leftPanelChar = await evalJs(`window.__d2webHud.leftPanel`) assert(leftPanelChar === 'char', `KeyA failed to open Character Sheet (got: ${leftPanelChar})`) await saveScreenshot('challenger_m1_char_sheet_opened.png') await pressKeyCdp(' ', 'Space') await sleep(150) const leftPanelClosed = await evalJs(`window.__d2webHud.leftPanel`) assert(leftPanelClosed === 'none', `Space failed to close Character Sheet (got: ${leftPanelClosed})`) // 4.3 Test KeyS and KeyT toggle Skill Tree and Escape closes it console.log(' 4.3 Testing KeyS / KeyT toggles Skill Tree and Escape closes it...') await pressKeyCdp('s', 'KeyS') await sleep(150) const rightPanelSkillS = await evalJs(`window.__d2webHud.rightPanel`) assert(rightPanelSkillS === 'skill', `KeyS failed to open Skill Tree (got: ${rightPanelSkillS})`) await saveScreenshot('challenger_m1_skill_tree_opened_by_s.png') await pressKeyCdp('s', 'KeyS') await sleep(150) const rightPanelClosedS = await evalJs(`window.__d2webHud.rightPanel`) assert(rightPanelClosedS === 'none', `KeyS failed to close Skill Tree (got: ${rightPanelClosedS})`) await pressKeyCdp('t', 'KeyT') await sleep(150) const rightPanelSkillT = await evalJs(`window.__d2webHud.rightPanel`) assert(rightPanelSkillT === 'skill', `KeyT failed to open Skill Tree (got: ${rightPanelSkillT})`) await pressKeyCdp('Escape', 'Escape') await sleep(150) const rightPanelClosedEsc = await evalJs(`window.__d2webHud.rightPanel`) assert(rightPanelClosedEsc === 'none', `Escape failed to close Skill Tree (got: ${rightPanelClosedEsc})`) // 4.4 Test KeyW toggles Weapon Swap console.log(' 4.4 Testing KeyW toggles Weapon Swap...') const swap0 = await evalJs(`window.__d2webHud.weaponSwapSet`) await pressKeyCdp('w', 'KeyW') await sleep(150) const swap1 = await evalJs(`window.__d2webHud.weaponSwapSet`) assert(swap1 === (swap0 === 0 ? 1 : 0), `KeyW failed to toggle weapon swap set (from ${swap0} to ${swap1})`) await saveScreenshot('challenger_m1_weapon_swap_set_toggled.png') await pressKeyCdp('w', 'KeyW') await sleep(150) const swapBack = await evalJs(`window.__d2webHud.weaponSwapSet`) assert(swapBack === swap0, `KeyW failed to toggle weapon swap set back (from ${swap1} to ${swapBack})`) // 4.5 Test Arrow keys move the player smoothly console.log(' 4.5 Testing Arrow keys accessibility player movement...') const posBeforeArrow = await evalJs<{ x: number; y: number }>(`({ x: window.__d2webAct.x, y: window.__d2webAct.y })`) // Hold ArrowRight for 350ms await send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'ArrowRight', code: 'ArrowRight', windowsVirtualKeyCode: 39 }) await sleep(350) await send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'ArrowRight', code: 'ArrowRight', windowsVirtualKeyCode: 39 }) await sleep(250) const posAfterArrow = await evalJs<{ x: number; y: number }>(`({ x: window.__d2webAct.x, y: window.__d2webAct.y })`) console.log(` Player coordinates before Arrow: (${posBeforeArrow.x}, ${posBeforeArrow.y}), after ArrowRight: (${posAfterArrow.x}, ${posAfterArrow.y})`) assert(posAfterArrow.x > posBeforeArrow.x, `ArrowRight failed to move player east! (dx = ${posAfterArrow.x - posBeforeArrow.x})`) console.log(' āœ… VERIFIED: Arrow keys move player successfully.') await saveScreenshot('challenger_m1_arrow_movement.png') // 4.6 Test Digit 1..4 drinks belt potions without altering active skills console.log(' 4.6 Testing Digit 1..4 drinks belt potions in live browser...') const beforePotions = await evalJs(`window.__d2webHud.beltPotionsCount`) const beforeLeftSkill = await evalJs(`window.__d2webHud.leftSkillId`) const beforeRightSkill = await evalJs(`window.__d2webHud.rightSkillId`) await pressKeyCdp('1', 'Digit1') await sleep(150) const afterPotions = await evalJs(`window.__d2webHud.beltPotionsCount`) const afterLeftSkill = await evalJs(`window.__d2webHud.leftSkillId`) const afterRightSkill = await evalJs(`window.__d2webHud.rightSkillId`) assert(afterPotions === beforePotions - 1, `Digit1 failed to consume potion (before: ${beforePotions}, after: ${afterPotions})`) assert(afterLeftSkill === beforeLeftSkill, `Digit1 changed leftSkillId`) assert(afterRightSkill === beforeRightSkill, `Digit1 changed rightSkillId`) console.log(' āœ… VERIFIED: Digit1 drinks belt potion without modifying skill selection.') ws.close() } finally { chromeProc.kill() await server.close() } } // ============================================================================ // MAIN RUNNER // ============================================================================ async function main(): Promise { console.log('======================================================================') console.log('šŸ›”ļø CHALLENGER EMPIRICAL VERIFICATION SUITE — MILESTONE 1 (R1)') console.log('======================================================================') try { runKeyboardInputTests() runDifferentialFuzzing(1000) runHudManagerKeyTests() await runChromeCdpVerification() console.log('\n======================================================================') console.log('šŸŽ‰ ALL EMPIRICAL CHALLENGER TESTS PASSED (100% SUCCESS, VERDICT: APPROVE)') console.log('======================================================================') process.exit(0) } catch (err: any) { console.error('\nāŒ CHALLENGER TEST FAILED WITH AN ERROR:') console.error(err) process.exit(1) } } main()