fix: 优化跳跃和下落手感

This commit is contained in:
cancerai committed 2026-10-24 01:54:39 +08:00
1 parent ada64fe4be
commit afe2f64b9b
25 files changed
+1197 -188

No files matched your search

+22 -7
View File
@@ -1,7 +1,7 @@
// 共享可达性 BFS 核心:solve.mjs 与 reach-diag.mjs 共用
// 用真实物理常量模拟走/落/跳(含二段跳时机、空中转向、松键短跳)
export const TILE = 32, PW = 11, PH = 21;
export const GRAV = 0.4, MAXFALL = 9, JUMP = 8.5, DJUMP = 7.6, HSPD = 3;
export const GRAV = 0.25, GRAV_FALL = 0.32, GRAV_DJ = 0.35, MAXFALL = 9, JUMP = 6.6, DJUMP = 6.7, HSPD = 2.6, DJ_INERTIA = 0;
export function setupGlobals() {
const handlers = {};
@@ -49,12 +49,14 @@ export function makeSim(L) {
const overlap = (a, b) => a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
function jump(fx, fy, vx0, djump, flip, rel, targets) {
let x = fx, y = fy - PH, vy = -JUMP, vx = vx0, t = 0, used = false;
let x = fx, y = fy - PH, vy = -JUMP, vx = vx0, t = 0, used = false, djUp = false;
while (t++ < 240) {
if (djump && !used && t === djump) { vy = -DJUMP; used = true; }
// 与 player.js 一致:上升中按二段 = 直接 -DJUMP;下落中按只抵消 DJ_INERTIA 比例的下落速度
if (djump && !used && t === djump) { vy = Math.max(vy, 0) * DJ_INERTIA - DJUMP; used = true; djUp = vy < 0; }
if (flip && t === flip) vx = -vx;
if (rel && t === rel && vy < -3) vy = -3;
vy = Math.min(vy + GRAV, MAXFALL);
vy = Math.min(vy + (vy < 0 ? (djUp ? GRAV_DJ : GRAV) : GRAV_FALL), MAXFALL);
if (djUp && vy >= 0) djUp = false;
// 真实行为(player.js):本帧被墙挡住只是没推动,按住方向键下一帧 vx 仍为 ±3。
// 绝不能把 vx 永久清零,否则"贴着墙上升、越过台面边缘后再横移"这条经典路线会丢失。
if (vx !== 0) {
@@ -83,7 +85,7 @@ export function makeSim(L) {
let x = fx + dir * 3, y = fy - PH, vy = 0, t = 0;
if (solidAt(x, y, PW, PH, false)) return null;
while (t++ < 400) {
vy = Math.min(vy + GRAV, MAXFALL);
vy = Math.min(vy + (vy < 0 ? GRAV : GRAV_FALL), MAXFALL);
const prevBottom = y + PH;
let rem = Math.abs(vy), landed = false;
while (rem > 0.0001) {
@@ -107,7 +109,10 @@ export function makeSim(L) {
/** BFS 可达性:targets=[{rect,type,...}],onState 回调每个新状态 */
export const DEF_DJ = [0, 6, 12, 18, 24, 30];
export const DEF_REL = [0, 4, 8, 14];
// 2026-10-23:加 rel=5/6。vgap 夹缝(缝高 32px、盒高 21px)要求短跳 apex 精确落在 32~43px,
// 且上升早期要躲开地刺左坡:rel=4 apex 35.7 但 t=7 蹭坡 2.6px,rel=6 apex 44.7 但 t=11 擦垂刺尖 0.6px,
// 只有 rel=5(apex 38.9)同时满足(j2 x16 夹缝实证);j7 顶层 x52 夹缝则要 rel=6
export const DEF_REL = [0, 4, 5, 6, 8, 14];
export function reach(L, { DJSET = DEF_DJ, RELSET = DEF_REL, targets = [], onState = null, maxStates = 40000 } = {}) {
const sim = makeSim(L);
@@ -118,7 +123,16 @@ export function reach(L, { DJSET = DEF_DJ, RELSET = DEF_REL, targets = [], onSta
}
if (!start) return { err: '出生点下方找不到落脚点', states: 0, targets };
const key = s => `${Math.round(s.fx / 6)},${Math.round(s.fy / 6)}`;
// 3px 状态桶:6px 桶会把"贴右腰 1~3px"的起拍/落点窗量化吞掉(j2 连续三段因此误判不可达)
// fy 规范化:jump/fall 返回的落点常比地面高零点几 px(如 1535.5),与精确落点 1536 同桶——
// 先占桶者赢,导致走链被错位几 px 的跳链顶掉,芝麻距离的跳跃窗口翻转(加 rel=6 后 j2 状态数反而暴跌的根因)。
// 真实游戏里重力下一帧即把玩家按到底(地面顶 = 32 的倍数),故按 canStand 门控 snapping
const key = s => `${Math.round(s.fx / 3)},${Math.round(s.fy / 3)}`;
const canon = (fx, fy) => {
const r = Math.round(fy / TILE) * TILE;
return (r !== fy && sim.canStand(fx, r)) ? r : fy;
};
start.fy = canon(start.fx, start.fy);
const seen = new Set([key(start)]);
const q = [start];
let n = 0;
@@ -128,6 +142,7 @@ export function reach(L, { DJSET = DEF_DJ, RELSET = DEF_REL, targets = [], onSta
for (const tg of targets) if (!tg.hit && sim.overlap(box, tg.rect)) tg.hit = true;
const push = r => {
if (!r || !sim.canStand(r.fx, r.fy)) return;
r.fy = canon(r.fx, r.fy);
const k = key(r);
if (seen.has(k)) return;
seen.add(k); q.push(r);