// 无头冒烟测试:构造全部关卡 + 模拟输入跑物理 const handlers = {}; const addL = (t, fn) => { (handlers[t] = handlers[t] || []).push(fn); }; globalThis.addEventListener = addL; globalThis.window = { addEventListener: addL }; const key = (type, code) => (handlers[type] || []).forEach(fn => fn({ code, preventDefault() { }, repeat: false })); const down = c => key('keydown', c); const up = c => key('keyup', c); const { LEVELS } = await import('../js/levels/index.js'); const { Level } = await import('../js/level.js'); const { Player } = await import('../js/player.js'); const { Input } = await import('../js/input.js'); let fail = 0; const err = m => { console.log(' ✗ ' + m); fail++; }; console.log('=== 关卡构造 ==='); for (const d of LEVELS) { try { const L = new Level(d); const P = new Player(L); // 静止 60 帧 for (let i = 0; i < 60; i++) { L.update(1, P, null); P.update(1); Input.endFrame(); } const grounded = P.grounded, alive = P.alive; console.log(` ${d.id.padEnd(3)} ${String(d.name).padEnd(16)} 实体=${String(L.entities.length).padStart(3)} boss=${L.bosses.length} 平台=${L.movers.length} 站住=${grounded ? 'Y' : 'N'} 存活=${alive ? 'Y' : 'N'}`); if (!grounded && d.type !== 'boss') err(`${d.id} 出生点悬空(60帧后未着地)`); if (!alive) err(`${d.id} 出生即死`); // 模拟:向右跑 + 反复跳跃 400 帧 down('ArrowRight'); for (let i = 0; i < 400; i++) { if (i % 40 === 0) down('ShiftLeft'); if (i % 40 === 8) up('ShiftLeft'); if (i % 40 === 12) { down('ShiftLeft'); } if (i % 40 === 16) up('ShiftLeft'); L.update(1, P, null); for (const b of L.bosses) b.update(1, P); P.update(1); Input.endFrame(); } up('ArrowRight'); up('ShiftLeft'); const moved = P.x; if (!Number.isFinite(P.x) || !Number.isFinite(P.y)) err(`${d.id} 坐标 NaN`); if (Math.abs(P.vx) > 20 || Math.abs(P.vy) > 40) err(`${d.id} 速度异常 vx=${P.vx} vy=${P.vy}`); console.log(` → 400帧后 x=${moved.toFixed(0)} y=${P.y.toFixed(0)} 死亡=${G_DEATHS(P)}`); } catch (e) { err(`${d.id} 运行异常: ${e.message}\n${e.stack?.split('\n')[1] || ''}`); } } function G_DEATHS() { return ''; } console.log('\n=== 死亡与重生 ==='); { const L = new Level(LEVELS.find(l => l.id === 'j1')); const P = new Player(L); let deaths = 0; P.onDeath = () => deaths++; for (let i = 0; i < 30; i++) { L.update(1, P, null); P.update(1); Input.endFrame(); } P.kill(); for (let i = 0; i < 80; i++) { L.update(1, P, null); P.update(1); Input.endFrame(); } console.log(` 死亡计数=${deaths} 重生后 alive=${P.alive} 位置=(${P.x.toFixed(0)},${P.y.toFixed(0)})`); if (!P.alive) err('死亡后未复活'); if (deaths !== 1) err('死亡回调未触发'); } console.log('\n=== 射击与 BOSS ==='); { const L = new Level(LEVELS.find(l => l.id === 'b1')); const P = new Player(L); const boss = L.bosses[0]; const hp0 = boss.hp; P.hasGun = true; for (let i = 0; i < 600; i++) { if (i % 12 === 0) P.shootCool = 0, P.shoot(); L.update(1, P, null); for (const b of L.bosses) b.update(1, P); P.update(1); Input.endFrame(); if (boss.hp <= 0) break; } console.log(` BOSS hp ${hp0} → ${boss.hp} 弹幕数=${L.ebullets.length} 阶段=${boss.phase}`); if (boss.hp >= hp0) err('子弹没有对 BOSS 造成伤害'); } console.log('\n=== 坑向事件 ==='); { const L = new Level(LEVELS.find(l => l.id === 'r4')); const P = new Player(L); let died = 0; P.onDeath = () => died++; // 走到传送门 (r4: col 44, row 17) P.x = 44 * 32; P.y = 17 * 32; for (let i = 0; i < 4; i++) { L.update(1, P, null); P.update(1); Input.endFrame(); } console.log(` 传送门:玩家被送到 x=${P.x.toFixed(0)} (起点 x=${L.spawn.x})`); if (Math.abs(P.x - L.spawn.x) > 4) err('传送门未送回起点'); } console.log(fail ? `\n❌ ${fail} 项失败` : '\n✅ 全部通过'); process.exit(fail ? 1 : 0);