// ============ inputcheck.mjs — simulate keyboard & mouse against real handlers ============ let fails = 0; const ok = (cond, msg) => { console.log((cond ? '✔ ' : '✘ ') + msg); if (!cond) fails++; }; const makeCtx = () => new Proxy(function () {}, { get(t, p) { if (!(p in t)) t[p] = (...a) => makeCtx(); return typeof t[p] === 'function' ? t[p] : t[p]; }, set() { return true; }, apply() { return makeCtx(); }, }); const mkEl = (id = '') => { const el = { id, style: {}, dataset: {}, children: [], _listeners: {}, addEventListener(ev, fn) { (el._listeners[ev] ??= []).push(fn); }, dispatch(ev, arg = {}) { for (const f of el._listeners[ev] || []) f({ preventDefault() {}, stopPropagation() {}, target: el, ...arg }); }, classList: { _s: new Set(), add(c) { this._s.add(c); }, remove(c) { this._s.delete(c); }, toggle() {}, contains: c => false }, appendChild() {}, remove() {}, setAttribute() {}, querySelectorAll: () => [], getBoundingClientRect: () => ({ left: 0, top: 0, width: 180, height: 180 }), getContext: () => makeCtx(), width: 300, height: 150, textContent: '', }; Object.defineProperty(el, 'innerHTML', { get() { return ''; }, set(v) { if (!v) el.children = []; } }); return el; }; globalThis.window = globalThis; globalThis.innerWidth = 1280; globalThis.innerHeight = 800; globalThis.__rafQueue = []; globalThis.requestAnimationFrame = fn => { __rafQueue.push(fn); return __rafQueue.length; }; globalThis.cancelAnimationFrame = () => {}; globalThis.__winListeners = {}; globalThis.addEventListener = (ev, fn) => { (globalThis.__winListeners[ev] ??= []).push(fn); }; globalThis.removeEventListener = () => {}; globalThis.localStorage = { _m: {}, getItem(k) { return this._m[k] ?? null; }, setItem(k, v) { this._m[k] = String(v); }, removeItem(k) { delete this._m[k]; } }; const els = {}; globalThis.document = { getElementById(id) { return els[id] ?? (els[id] = mkEl(id)); }, createElement: t => mkEl(t), createTextNode: t => ({ textContent: String(t) }), querySelectorAll: () => [], querySelector: () => null, addEventListener() {}, body: { appendChild() {} }, }; // start a game FIRST (so getState() exists like a real session) const mainMod = await import('../js/main.js'); const cam = mainMod.cam; const stateM = await import('../js/game/state.js'); const heroesM = await import('../js/game/heroes.js'); const cfg = await import('../js/core/config.js'); const st = stateM.newGame('sandbox'); heroesM.cacheScenario(st, cfg.SCENARIOS.find(s => s.id === 'sandbox')); document.getElementById('main-menu').classList.remove ? null : null; const winDispatch = (ev, arg = {}) => { for (const f of globalThis.__winListeners[ev] || []) f({ preventDefault() {}, target: { tagName: 'CANVAS' }, ...arg }); }; // ---- KEYBOARD PAN DIRECTIONS (screen-relative) ---- function press(key, frames = 20) { winDispatch('keydown', { key }); for (let i = 0; i < frames; i++) { const q = [...__rafQueue]; __rafQueue.length = 0; for (const f of q) f(performance.now()); } winDispatch('keyup', { key }); } const center = () => { cam.x = 30; cam.y = 40; }; let p; center(); p = { ...cam }; press('w'); ok(cam.x < p.x && cam.y < p.y, `W pans screen-up (Δ${(cam.x - p.x).toFixed(1)},${(cam.y - p.y).toFixed(1)})`); center(); p = { ...cam }; press('s'); ok(cam.x > p.x && cam.y > p.y, `S pans screen-down (+${(cam.x - p.x).toFixed(1)},+${(cam.y - p.y).toFixed(1)})`); center(); p = { ...cam }; press('a'); ok(cam.x < p.x && cam.y > p.y, `A pans screen-left (${(cam.x - p.x).toFixed(1)},+${(cam.y - p.y).toFixed(1)})`); center(); p = { ...cam }; press('d'); ok(cam.x > p.x && cam.y < p.y, `D pans screen-right (+${(cam.x - p.x).toFixed(1)},${(cam.y - p.y).toFixed(1)})`); // ---- MOUSE DRAG PAN TEST ---- const canvas = document.getElementById('game'); const camBeforeDrag = { x: cam.x, y: cam.y }; canvas.dispatch('pointerdown', { button: 0, clientX: 600, clientY: 400 }); for (let s = 1; s <= 10; s++) canvas.dispatch('pointermove', { button: 0, clientX: 600 + s * 12, clientY: 400 - s * 6, shiftKey: false }); canvas.dispatch('pointerup', { button: 0, clientX: 720, clientY: 340 }); const dragMoved = Math.abs(cam.x - camBeforeDrag.x) + Math.abs(cam.y - camBeforeDrag.y); ok(dragMoved > 1, `mouse drag pans camera (delta ${dragMoved.toFixed(1)} tiles)`); // ---- WHEEL ZOOM TEST ---- const zBefore = cam.zoom; canvas.dispatch('wheel', { deltaY: -120, clientX: 640, clientY: 400 }); ok(cam.zoom > zBefore, `wheel zooms (${zBefore.toFixed(2)} → ${cam.zoom.toFixed(2)})`); canvas.dispatch('wheel', { deltaY: 120, clientX: 640, clientY: 400 }); // ---- MINIMAP CLICK TELEPORT ---- const mm = document.getElementById('minimap'); const mx = cam.x, my = cam.y; mm.dispatch('pointerdown', { clientX: 90, clientY: 90, target: mm }); ok(Math.abs(cam.x - mx) + Math.abs(cam.y - my) > 3, `minimap click teleports camera (${cam.x.toFixed(0)},${cam.y.toFixed(0)})`); console.log(fails ? `\n${fails} INPUT FAILURES` : '\nALL CAMERA INPUTS OK'); process.exit(fails ? 1 : 0);