fix: 修复了冰块惯性问题和死亡效果

This commit is contained in:
cancerai committed 2026-10-22 19:43:01 +08:00
1 parent 47a462be01
commit 4b3dfd635b
15 files changed
+633 -38

No files matched your search

+135
View File
@@ -0,0 +1,135 @@
// 回归测试:kid 死亡后必须立刻复活,且不留下任何死亡动画(无粒子、无震屏、无延迟)
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 { Level } = await import('../js/level.js');
const { Player } = await import('../js/player.js');
const { Input } = await import('../js/input.js');
const W = 20, H = 8, TILE = 32;
// row2 放一枚上刺(col 5),row3 是地板,spawn 在 col1
const grid = [
'#'.repeat(W),
'#' + 'S' + '.'.repeat(W - 3) + '#',
'#' + '.'.repeat(4) + '^' + '.'.repeat(4) + '^' + '.'.repeat(9) + '#',
'#'.repeat(W),
'#'.repeat(W),
'#'.repeat(W),
'#'.repeat(W),
'#'.repeat(W),
];
let fail = 0;
const err = m => { console.log(' ✗ ' + m); fail++; };
const ok = m => console.log(' ✓ ' + m);
function fresh() {
const L = new Level({ id: 'dt', name: 'DT', type: 'jump', width: W, grid });
const P = new Player(L);
let deaths = 0;
P.onDeath = () => deaths++;
for (let i = 0; i < 12; i++) { L.update(1, P, null); P.update(1); Input.endFrame(); }
return { L, P, deaths: () => deaths };
}
// 1) 踩到刺:同一帧内就必须已经复活(不是等 34 帧)
{
const { L, P, deaths } = fresh();
const sx = L.spawn.x;
P.x = 5 * TILE; P.y = 2 * TILE + 11; // 直接压在上刺上
L.update(1, P, null); P.update(1); Input.endFrame();
if (!P.alive || P.dead) err('踩刺后本帧仍处于死亡状态(复活有延迟)');
else ok('踩刺后同帧即复活,无死亡等待');
if (deaths() !== 1) err(`死亡计数应为 1,实际 ${deaths()}`);
else ok('死亡计数 = 1(正常累加)');
if (Math.abs(P.x - sx) > 1) err(`复活位置不对:x=${P.x.toFixed(1)} 期望 ${sx}`);
else ok('复活位置 = 最近存档点/出生点');
if (L.particles.list.length) err(`死亡仍产生粒子(${L.particles.list.length} 个)`);
else ok('无屏幕级死亡粒子(血迹走常驻层,非瞬态粒子)');
if (L.shake > 0) err(`死亡仍触发震屏 shake=${L.shake}`);
else ok('无死亡震屏');
if (!L.deathMarks.length) err('死亡未在死亡地点留下 IWBT 血迹效果');
else {
const dm = L.deathMarks[0];
const ex = 5 * TILE + 5.5, ey = 2 * TILE + 21.5;
if (Math.abs(dm.x - ex) > 30 || Math.abs(dm.y - ey) > 30) err(`血迹偏离死亡点 (${dm.x.toFixed(0)},${dm.y.toFixed(0)}) 期望约 (${ex},${ey})`);
else ok('在死亡地点留下红色死亡叉(X)');
}
if (P.justRespawned !== true) err('未标记 justRespawned,镜头会慢慢飘回去');
else ok('justRespawned 已置位(镜头瞬移)');
}
// 2) R 键自杀路径:kill() 后下一帧必须复活
{
const { L, P, deaths } = fresh();
P.kill();
if (deaths() !== 1) err('kill() 未触发死亡回调');
L.update(1, P, null); P.update(1); Input.endFrame();
if (!P.alive || P.dead) err('自杀后未立刻复活');
else ok('自杀(R 键)后下一帧即复活');
// 立刻复活后应能马上操作:按住右键能走
const x0 = P.x;
key('keydown', 'ArrowRight');
for (let i = 0; i < 6; i++) { L.update(1, P, null); P.update(1); Input.endFrame(); }
if (P.x <= x0) err('复活后当帧无法操作');
else ok(`复活后立即可操作(6 帧走了 ${(P.x - x0).toFixed(1)}px)`);
key('keyup', 'ArrowRight');
}
// 3) 连续死亡:不能卡死在死亡状态
{
const { L, P, deaths } = fresh();
for (let k = 0; k < 5; k++) {
P.x = 5 * TILE; P.y = 2 * TILE + 11;
L.update(1, P, null); P.update(1); Input.endFrame();
if (!P.alive) { err(`第 ${k + 1} 次死亡后卡在死亡状态`); break; }
}
if (deaths() !== 5) err(`连续 5 次死亡计数为 ${deaths()}`);
else ok('连续 5 次死亡均即时复活,计数 = 5');
}
// 4) 死亡后不应残留任何死亡视觉:跑 30 帧确认没有死亡粒子产生
{
const { L, P } = fresh();
P.x = 5 * TILE; P.y = 2 * TILE + 11;
let maxParticles = 0;
for (let i = 0; i < 30; i++) {
L.update(1, P, null); P.update(1); Input.endFrame();
maxParticles = Math.max(maxParticles, L.particles.list.length);
}
if (maxParticles > 0) err(`死亡后仍出现全局粒子(峰值 ${maxParticles})`);
else ok('30 帧内无屏幕级死亡粒子(血迹由常驻层渲染)');
if (!L.deathMarks.length) err('死亡血迹未常驻保留');
else ok(`死亡红叉常驻保留(共 ${L.deathMarks.length} 处,30 帧后仍在)`);
}
// 5) 多次死亡:只显示上一次死亡的血迹(不累积)
{
const { L, P } = fresh();
for (let k = 0; k < 2; k++) { P.x = 5 * TILE; P.y = 2 * TILE + 11; L.update(1, P, null); P.update(1); Input.endFrame(); }
for (let k = 0; k < 2; k++) { P.x = 10 * TILE; P.y = 2 * TILE + 11; L.update(1, P, null); P.update(1); Input.endFrame(); }
if (L.deathMarks.length !== 1) err(`多次死亡应只显示最后一次血迹,实际 ${L.deathMarks.length} 处`);
else {
const dm = L.deathMarks[0];
const ex = 10 * TILE + 5.5, ey = 2 * TILE + 21.5;
if (Math.abs(dm.x - ex) > 30) err(`应只保留最后一次(col10)血迹,实际在 x=${dm.x.toFixed(0)}`);
else ok('多次死亡只保留上一次死亡地点的血迹');
}
}
// 6) 整关重开:血迹清空
{
const { L, P } = fresh();
P.x = 5 * TILE; P.y = 2 * TILE + 11;
L.update(1, P, null); P.update(1); Input.endFrame();
if (!L.deathMarks.length) err('死亡未生成血迹');
L.reset(true);
if (L.deathMarks.length) err(`整关重开后血迹应清空,实际 ${L.deathMarks.length}`);
else ok('整关重开(reset(true))后血迹清空');
}
console.log(fail ? `\n❌ ${fail} 项失败` : '\n✅ 死亡即时复活,全部通过');
process.exit(fail ? 1 : 0);