Files
kidgo/tools/ice-test.mjs
T

104 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 冰面惯性回归测试:滑出冰块后必须保留惯性(空中几乎不衰减,地面缓慢收住)
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 { Level } = await import('../js/level.js');
const { Player, PHY } = await import('../js/player.js');
const { Input } = await import('../js/input.js');
const W = 44;
// 地板 row 11:冰面 col1..6,之后 tail(普通地面或空)
function grid(tail) {
const g = [];
g.push('#'.repeat(W));
for (let y = 1; y <= 10; y++) {
g.push((y === 10 ? '#S' + '.'.repeat(W - 3) : '#' + '.'.repeat(W - 2)) + '#');
}
g.push('#' + '%'.repeat(6) + tail + '#');
g.push('#'.repeat(W));
g.push('#'.repeat(W));
return g;
}
let fail = 0;
const err = m => { console.log(' ✗ ' + m); fail++; };
function run(tail, label, releaseAt = 1e9, jumpAt = 1e9) {
const L = new Level({ id: 'ice', name: 'ICE', type: 'jump', width: W, grid: grid(tail) });
const P = new Player(L);
for (let i = 0; i < 10; i++) { L.update(1, P, null); P.update(1); Input.endFrame(); }
down('ArrowRight');
const log = [];
for (let i = 0; i < 90; i++) {
if (i === releaseAt) up('ArrowRight');
if (i === jumpAt) down('ShiftLeft');
if (i === jumpAt + 1) up('ShiftLeft');
L.update(1, P, null); P.update(1); Input.endFrame();
log.push({
f: i, x: +P.x.toFixed(1), vx: +P.vx.toFixed(2), y: +P.y.toFixed(1),
g: P.grounded, sl: P.sliding,
ice: P.grounded && L.iceAtBottom(P.x, P.y, P.w, P.h),
});
}
up('ArrowRight');
console.log(`\n=== ${label} ===`);
console.log('帧 x vx 地 滑 冰');
let crossAt = -1;
for (const r of log) {
const cross = !r.ice && log.find(o => o.f === r.f - 1)?.ice;
if (cross && crossAt < 0) { crossAt = r.f; console.log('--- 离开冰面 ---'); }
if (r.f % 3 === 0 || cross) console.log(`${String(r.f).padStart(3)} ${String(r.x).padStart(6)} ${String(r.vx).padStart(6)} ${r.g ? 'Y' : '.'} ${r.sl ? 'Y' : '.'} ${r.ice ? 'Y' : '.'}`);
}
const off = log.filter(r => r.f >= crossAt && crossAt >= 0);
const dist = off.length ? Math.max(...off.map(r => r.x)) - off[crossAt === 0 ? 0 : 0].x : 0;
console.log(`离冰时 vx=${off[0]?.vx ?? 0},离冰后位移=${dist.toFixed(1)}px`);
return { log, crossAt, off };
}
// A:一直按住右键冲出冰面 —— 惯性要把 4.32 平滑收敛到正常走速,不能瞬间刹停也不能一直超速
{
const { off } = run('='.repeat(34), 'A 冰面 -> 普通地面(一直按住右键)');
const v0 = off[0].vx, v10 = off[10]?.vx ?? 0, v30 = off[30]?.vx ?? 0;
if (v0 < PHY.HSPD) err(`A 离冰瞬间就丢了速度 vx=${v0}`);
if (v10 < 2) err(`A 离冰 10 帧后速度掉太快 vx=${v10}(应仍在滑行)`);
if (Math.abs(v30) > PHY.HSPD + 0.01) err(`A 离冰 30 帧后仍超速 vx=${v30}(应收敛回 ${PHY.HSPD})`);
}
// B:松手靠惯性滑出到普通地面 —— 地面摩擦收住,但不能一帧刹死
{
const { off } = run('='.repeat(34), 'B 冰面 -> 普通地面(第30帧松开方向键)', 30);
const v0 = off[0].vx, v5 = off[5]?.vx ?? 0;
if (v5 <= 0) err(`B 离冰 5 帧内就刹死了(v0=${v0} v5=${v5})`);
if (v5 > v0) err(`B 松手后速度反而上升`);
}
// C:滑出冰块边缘掉下去 —— 空中必须几乎不衰减,能一路飞出去
{
const { off } = run('.'.repeat(34), 'C 冰面 -> 悬空(第30帧松开后滑出边缘)', 30);
const air = off.filter(r => !r.g);
if (air.length < 5) { err('C 没掉下去(场景构造失败)'); }
else {
const k = Math.min(10, air.length - 1);
const v0 = air[0].vx, vk = air[k].vx;
if (vk < v0 - 0.5) err(`C 空中惯性衰减过快:${v0} -> ${vk}(${k} 帧)`);
else console.log(` ✓ 空中 ${k} 帧后仍保留 vx=${vk}(起 ${v0})`);
}
}
// D:冰面上滑行中起跳 —— 空中保持滑行速度
{
const { off } = run('.'.repeat(34), 'D 冰面滑行中起跳(第30帧松开 + 第34帧跳)', 30, 34);
const v0 = off[0]?.vx ?? 0, v12 = off[12]?.vx ?? 0;
if (v0 < 2) err(`D 起跳时速度太小 vx=${v0}`);
if (v12 < v0 - 0.6) err(`D 跳跃后空中丢失惯性:${v0} -> ${v12}`);
else console.log(` ✓ 起跳后 12 帧仍保留 vx=${v12}(起 ${v0})`);
}
console.log(fail ? `\n❌ ${fail} 项失败` : '\n✅ 冰面惯性全部通过');
process.exit(fail ? 1 : 0);