// Headless sim harness. Runs the real physics in Node — no browser, no // canvas, no module-cache games. Deterministic and fast. // // node test/harness.mjs // // Stubs only the things that touch the DOM (audio + gamepad rumble). import { CFG } from '../src/config.js'; // ---- stub the browser bits before anything imports them ---- globalThis.window = globalThis; globalThis.addEventListener = () => {}; // node 26 has a getter-only navigator — patch the method onto it instead if (!globalThis.navigator) { Object.defineProperty(globalThis, 'navigator', { value: {}, configurable: true }); } if (!globalThis.navigator.getGamepads) { try { globalThis.navigator.getGamepads = () => []; } catch { Object.defineProperty(globalThis, 'navigator', { value: { getGamepads: () => [] }, configurable: true }); } } const { World } = await import('../src/world.js'); const { Camera } = await import('../src/camera.js'); const { step } = await import('../src/physics.js'); const { Radicals } = await import('../src/radicals.js'); const { Input } = await import('../src/input.js'); const { ROOMS } = await import('../src/rooms.data.js'); const { seedRandom } = await import('../src/util.js'); export function makeWorld(seed = 1234) { seedRandom(seed); // reproducible runs — the storm is seeded const cam = new Camera(); const w = new World(cam); Input.usingGamepad = false; Input.grab = false; Input.moveX = Input.moveY = 0; Input.mouseWorld = { x: -9999, y: -9999 }; return w; } export function sim(w, seconds) { const n = Math.round(seconds / CFG.DT); for (let i = 0; i < n; i++) tick(w); } export function tick(w) { w.molly.resolveAim(); w.handleVerbs(); w.molly.move(CFG.DT); step(w, CFG.DT); w.update(CFG.DT); } export function aimAt(w, x, y) { Input.mouseWorld = { x, y }; w.molly.resolveAim(); } export { CFG, World, Camera, Radicals, Input, ROOMS, step, seedRandom }; // --------------------------------------------------------------- // tiny assert lib // --------------------------------------------------------------- let pass = 0, fail = 0; const fails = []; export function check(name, cond, detail = '') { if (cond) { pass++; console.log(` \x1b[32m✓\x1b[0m ${name}${detail ? ' ' + detail : ''}`); } else { fail++; fails.push(name); console.log(` \x1b[31m✗\x1b[0m ${name}${detail ? ' ' + detail : ''}`); } } export function section(t) { console.log(`\n\x1b[1m${t}\x1b[0m`); } export function report() { console.log(`\n${fail === 0 ? '\x1b[32m' : '\x1b[31m'}${pass} passed, ${fail} failed\x1b[0m`); if (fails.length) console.log('failed: ' + fails.join(', ')); return fail === 0; }