// 用真实引擎(Player/Level)测量跳跃水平距离,核对求解器的物理近似是否可信 const handlers = {}; const addL = (t, fn) => { (handlers[t] = handlers[t] || []).push(fn); }; globalThis.addEventListener = addL; globalThis.window = { addEventListener: addL }; const key = (t, c) => (handlers[t] || []).forEach(fn => fn({ code: c, preventDefault() { }, repeat: false })); const { Level } = await import('../js/level.js'); const { Player } = await import('../js/player.js'); const { Input } = await import('../js/input.js'); // 一块 40 格长的平地,右端断开 const flat = { id: 'test', name: 'T', type: 'jump', width: 44, grid: (() => { const rows = []; rows.push('#'.repeat(44)); for (let i = 0; i < 16; i++) rows.push('#' + '.'.repeat(42) + '#'); rows.push('#' + '#'.repeat(42) + '#'); // 整条实心平台 rows.push('#'.repeat(44)); return rows; })(), }; function measure(djFrame) { const L = new Level(flat); const P = new Player(L); P.x = 8 * 32; P.y = 16 * 32 - P.h; // 站在 row17 平台上(平台行 17) P.vx = 0; P.vy = 0; P.grounded = true; P.jumps = 0; const x0 = P.x; key('keydown', 'ArrowRight'); key('keydown', 'ShiftLeft'); let f = 0, landed = -1, released = false; for (let i = 0; i < 200; i++) { f++; // 二段跳需要先松开再按下(跨帧),否则不算一次新的按下 if (djFrame && f === djFrame) { key('keyup', 'ShiftLeft'); released = true; } if (released && f === djFrame + 1) { key('keydown', 'ShiftLeft'); released = false; } L.update(1, P, null); P.update(1); Input.endFrame(); if (i > 2 && P.grounded) { landed = P.x; break; } if (P.dead) break; } key('keyup', 'ArrowRight'); key('keyup', 'ShiftLeft'); Input.endFrame(); return { dist: landed >= 0 ? landed - x0 : null, f }; } console.log('真实引擎跳跃水平距离(起跳点 x 到落点 x,单位 px / 格)'); for (const dj of [0, 6, 12, 18, 24, 30]) { const r = measure(dj); console.log(` 二段跳时机=${String(dj).padStart(2)} 帧 -> ${r.dist === null ? '未落地' : r.dist.toFixed(1) + 'px = ' + (r.dist / 32).toFixed(2) + ' 格 (' + r.f + ' 帧)'}`); }