# 2026-10-23 工作日志 ## 首页移除「清除存档」按钮(用户要求) - 需求:把首页(scr-title)的「清除存档」按钮删掉。 - 改动:仅删 `index.html:41` 的 `` 一行。`js/main.js` 里的 `wipe` handler(确认后 `Save.wipe()`)已无按钮触发,成为未接线死代码,保留无害(用户只要求删按钮,未要求删逻辑)。 - 验证:`grep 清除存档/data-act="wipe"` 零残留;`tools/` 无引用;`death/map/check/solve/smoke/save/key/dom-smoke/ice` 九套回归全绿;预览 8123 返回 200。 ## 主界面新增「设置」功能(用户要求) - 需求:主界面加「设置」入口;可自定义用户名;可拖动调整手机操作按钮的位置与大小;点「确定」保存、有「恢复默认」按钮。 - 新增 `js/settings.js`:`BUTTONS`(7 个手柄键 left/right/jump/shoot/down/pause/suicide,各带默认 `cx,cy,size` 百分比+像素),`meta()/place()/apply()/showUsername()`,`Settings.init/enter/confirm/reset`,`wire()` 给预览按钮绑「拖动移动 + 右下角圆点缩放」。工作副本 `work` 未点确定不落盘,点「返回」即丢弃。 - 存档:`js/save.js` 的 `settings:{username,touch}` 走 `Save.flush()`(localStorage `iwbtc_fan_v1`)。 - 接线:`index.html` 标题菜单加「设置」按钮 + `#title-user` + `scr-settings` 屏(用户名 input + `#touch-preview` 拖拽预览 + 确定/恢复默认);`js/main.js` 加 `settings/set-ok/set-reset` action 并 `Settings.init()`;`js/touch.js` `show()` 里 `Settings.apply()` 让自定义布局在游戏内生效。 - CSS:`.tc-btn` 改为 `position:absolute;transform:translate(-50%,-50%)`(active 变形同步保留 translate);新增 `.tc-rsz` 缩放手柄、`.touch-preview`、`.set-*`、`#title-user` 样式。 - 验证:新增 `tools/settings-test.mjs`(默认布局/apply 定位/enter+confirm 保存用户名并写 localStorage/reset 回出厂且不自动落盘,15 断言全过);dom-smoke(main 初始化成功)、check/smoke/map/save/key/solve/ice 全绿;预览服务返回 200 且含 `scr-settings`。 ## 修复:拖动设置里的手柄按钮时直接跳到右下角 - 根因:`js/settings.js` 原 `wire()` 用「指针(clientX) − 基准偏移(off/cx0)」做**绝对坐标映射**定位;一旦 `wrap.getBoundingClientRect()` 取值与渲染偏差(预览百分比定位 + translate(-50%,-50%) 场景下易偏),按下首帧就把 cx/cy 算到极值 → 弹到右下角。 - 修复:改为**相对位移累加**——按下只记录 `lastX/lastY` 与预览 `rect`;`pointermove` 把「相对上一帧的像素位移 ÷ rect 宽高 ×100」累加到当前 cx/cy(clamp 3~97%),缩放则按「当前指针到中心距离 ÷ 按下时距离」缩放 `size`(24~160px)。第一帧必然停在正确位置,不再跳角。还顺手加了 `e.stopPropagation` + try/catch 包裹 `setPointerCapture`。 - 回归:`tools/settings-test.mjs` 新增「模拟拖动/缩放」4 条——派发真实 pointerdown/move/up 事件断言:cx 按位移增量变化、逆向可精确归位、缩放放大尺寸且不改位置。settings-test 共 21 断言全过,dom-smoke 初始化正常。 ## 修复:设置界面打开后一片空白 - 根因:`js/ui.js` 的 `SCREENS` 数组没加 `'scr-settings'`,`UI.show('scr-settings')` 会把所有已知屏 hidden、却从不取消设置屏的 hidden——只剩画布标题背景(用户截图即此症状)。 - 修复:SCREENS 加入 `scr-settings`(一行)。教训:**新增 screen 必须同步 SCREENS 数组**。 - 回归:`tools/settings-test.mjs` 增加「SCREENS 含 scr-settings / 标题有设置入口」两条源码断言,防复发;settings-test 与 dom-smoke 全绿。 ## 设置界面:确定返回上一页 + PC/移动端分设 - 需求:① 点「确定」后返回进入设置前的界面(上一页);② 电脑端设置「键盘映射」,移动端设置「手机操作按钮」(按设备二选一显示)。 - `js/input.js`:新增 `DEFAULT_BIND`(出厂 BIND 快照)、`export defaultBindings()`(深拷贝)、`export applyBindings(map)`(用 `{action:[code]}` 覆盖生效键位)。 - `js/settings.js`: - 新增 `KEY_ACTIONS`(left/right/up/down/jump/shoot/suicide/restart/pause 共 9 项可重绑)、`keyLabel(code)`(←/→/Shift/空格等可读名)。 - `pcMode = !isTouch`(由 `Settings.init(useTouch)` 传入);`enter()` 末尾按 `pcMode` 切换 `#set-keys-row`/`#set-touch-row` 的 hidden:PC 显键盘映射、移动端显手柄按钮。 - `renderKeys()` 渲染每行「动作名 + 按键 chip(点×删除)+ +添加」;`wireKeys()` 点击委托 + `document` keydown 捕获下一次按键写入 `work.keys`(Esc 取消),同一动作支持多键。 - `confirm()` 保存用户名/touch/keys 并 `applyBindings(s.keys)`;`reset()` 把 keys 也回出厂;`makeDefaults()`/`meta()` 保证 `keys` 字段齐全。 - `wireKeys()` 加 `keysWired` 幂等保护(重复 init 不会重复绑监听)。 - `js/main.js`:模块级 `let settingsFrom='scr-title'`;`settings` action 里 `settingsFrom = UI.cur` 后 `Settings.enter()`;`set-ok` 改为 `Settings.confirm(); UI.show(settingsFrom)`(确定后回上一页);`Settings.init(useTouch)` 传入设备检测结果。 - `index.html`:设置屏新增 `#set-keys`(键盘映射容器)+ 两行 `#set-keys-row`/`#set-touch-row` 供切换显隐。 - CSS:`.set-keys/.krow/.kact/.kkeys/.kchip/.krm/.kadd/.klisten` 等键盘映射样式(`blink` 动画提示监听中)。 - 验证:`tools/settings-test.mjs` 扩到 30 断言(input API / PC·移动端面板显隐切换 / 点击 chip→keydown 重绑 / 添加 / 删除 / 确定落盘 / reset 回出厂 / main 里 set-ok 返回上一页源码断言)全过;dom-smoke、check、smoke、map、save、key、ice、solve 全绿;预览 8126 返回 200 且含 set-keys/set-keys-row/set-touch-row。 ## 设置·键盘重绑加冲突检测(用户要求) - 需求:电脑端在设置里改键时,要能「检测冲突」。 - `js/settings.js`: - 新增 `let conflict = null`(warning 状态)与 `conflictInfo(code, act, idx)`:遍历 `KEY_ACTIONS` 的 `work.keys`,若 `code` 已被**其它**动作(或同动作不同槽)占用则返回该动作标签;并额外检查不可重绑的系统动作 `BIND.menu`(如 KeyQ→「菜单」),避免把键绑到系统功能上。 - `onCaptureKey()`:捕获到非 Esc 的键后先查冲突;若冲突则**拒绝写入**、置 `conflict={code,label,act}` 并 `renderKeys()`(停留在监听态,等换键或 Esc);无冲突才写入并清 `conflict`。 - `renderKeys()`:面板顶部加 `.kwarb` 红色横幅「「X」已被【Y】占用,请换一个键(Esc 取消)」;监听行 tip 在冲突时变红显示「该键冲突,请换一个键」。 - `enter()/reset()` 重置 `conflict=null`;`Settings` 暴露 `_conflict()/_listening()` 供测试/调试。 - `js/input.js`:`const BIND` → `export const BIND`(供冲突检测引用 `menu` 键)。 - CSS:`.kwarb`(红底警示横幅)、`.klisten.bad`(红字警告)。 - 验证:`tools/settings-test.mjs` 扩到 36 断言,新增「冲突:jump 不会绑到被左移占用的 KeyA / 记录到被左移占用 / 顶部警告横幅 / 改用空闲键 KeyK 成功 / 同动作重复键不重复写入 / Esc 退出无残留 / KeyQ 被系统菜单占用」全过;dom-smoke、key、smoke、check、map、save、ice、solve 全绿。 ## 刺改为「单三角形 + 碰撞与形状完全对应」(用户纠正) - 用户纠正:不要 3 个小齿刺,要**单个三角形占一格**,且**碰撞必须和三角形形状完全对应**(不是矩形近似)。 - `js/sprites.js` `drawSpike`:由「3 个小三角」改为**一个占满整格的三角**(尖端朝 dir,底边贴相反侧),并沿底边画一条暗色基座(正好落在三角底边,不扩大判定)。`TILE=32`,故每刺格 32×32。 - `js/level.js`:删除 `spikeBox`(旧「收窄矩形近似」),新增 `spikeTri(t,x,y)`(顶点与 `drawSpike` 完全一致)与 `triRectOverlap`(分离轴定理,凸三角×AABB 充要条件)。`deadlyRect` 改用 `spikeTri`+`triRectOverlap`(网格刺 + dropspike 都用真三角;dropspike 用 `SPIKE_L` 与其 `drawDropSpike` 的 dir=2 一致)。 - 关键性质:底宽尖窄——底部两角现在**会致死**(旧矩形近似把它们当安全),尖顶外侧安全,与真实三角形状一致。 - 一致性利好:game 的 `player.js`、`tools/lib-reach.mjs`(求解器)、`save-diag.mjs`、`bot.mjs` 全部直接调用 `L.deadlyRect`,改碰撞后自然统一,无需另改求解器模型。 - 验证:新增 `tools/spike-test.mjs`(16 断言:spikeTri 四向顶点 / triRectOverlap 内-外-AABB / deadlyRect 集成上刺尖顶安全·底中致死·底左角致死·空地安全 / 与旧矩形近似在底左角结论相反的精确性对比)全过;`solve.mjs` 仍「所有关卡终点与存档点均可达」;editor-wysiwyg、dom-smoke、check、smoke、map、save、key、ice、settings 九套全绿。 ## 编辑器所见即所得(WYSIWYG)+ 刺显示真实三角形 - 需求:编辑器里所有方块要显示游戏内真实样子(原来用「彩色块 + 字母/箭头」表示);刺要显示成三角形(游戏里 `drawSpike` 本来就是三角,编辑器之前却画成 `↑↓←→` 箭头字符)。 - `js/editor.js`: - `import * as S from './sprites.js'`;新增 `drawTileReal(ctx, ch, px, py, props, t)` 把每个字符分派到 `S.drawX`(`#/@/=/M`→drawBlock 各 variant、`%/~/-\/V/^/*/D/X/G/P/F/J/W/T/O/C` 各对应函数,`^` 按 `props.dir` 转 0/1/2/3 画三角,`S` 出生点画小人 `drawKid`);`B`/`!` 等无独立美术的保留「色块+字母」兜底 `drawMarker`。 - `draw()` 重写:每格调用 `drawTileReal` 替代「填色块+画字母」;保留淡网格线、选中高亮;传送门额外叠加 `W{id}` 与连接状态(编辑器元数据)。 - `buildPalette()`:`?` 工具用 ⌖ 字形,其余每个方块用 `drawTileReal` 渲染成 32×32 缩略 canvas(`class="pal-cv"`),调色板即真实预览。 - `PALETTE` 改 `export const`(便于测试);删除不再使用的 `SPIKE_ARROW`。 - CSS:`.pal-cv{width:26px;height:26px;...image-rendering:pixelated}`。 - 测试:新增 `tools/editor-wysiwyg-test.mjs`(init 渲染全部调色板方块无异常 / 刺以三角形图元绘制 lineTo=6 / 方块真实纹理 fillRect / 23 种方块在画布绘制不抛异常);dom-smoke(init 跑 buildPalette 覆盖全部 25 字符分发)、check/smoke/map/save/key/ice/solve 全绿。 ## 设置·键盘重绑面板移除「下蹲」(用户要求) - 需求:把「按键设置」里的下蹲(down)项去掉。 - `js/settings.js`:`KEY_ACTIONS` 删除 `['down','下(下蹲)']`(由 9 项变 8 项)。仅影响键盘重绑面板与冲突检测范围;游戏内 `down` 逻辑(`player.js` 下蹲/下穿)与移动端触屏 `down` 按钮(`BUTTONS`/`touch.js`/`index.html` 的 `data-touch="down"`)保持不变——用户只要求移除「按键设置」里的项。 - 测试:`tools/settings-test.mjs` 的跳跃行索引 4→3(down 移除后 jump 成为第 4 项),36 断言仍全过;dom-smoke、key、check、solve 全绿。 ## 刺外观对齐参考图(冰蓝白渐变大三角) - 用户反馈「这也不是三角形呀」并给参考图:经典 I wanna 的淡蓝白渐变单三角。此前刺是灰色 `#c8c8d8` + 深色基座条,观感不像三角。 - `js/sprites.js` `drawSpike` 重写外观:顶点不变(仍与 level.js `spikeTri` 完全一致,碰撞不动),改为冰蓝白线性渐变(尖端 `#f4f9fd` → 底边 `#a8bccf`,沿刺方向渐变)+ 淡蓝描边 `rgba(150,175,200,.9)` + 尖端附近白色高光片;删除深色基座条。 - 删除已无引用的 `tri()` 助手;`C.spike/spikeDark` 更新为淡蓝(`#cfdde9`/`#a8bccf`)。 - 新增 `spike-preview.html` 预览页:用真实 drawSpike/drawBlock 渲染地面刺/天花板刺/贴墙刺,供用户对照参考图。 - 测试:spike-test(16 断言)、editor-wysiwyg、dom-smoke、solve(23 关可达)及全部回归绿。 - 修复:上一轮 drawSpike 的「尖端高光」坐标计算错误(ax + (bx+cx)/2 把顶点当标量相加,多边形飞出格子),游戏中刺后面拉出灰色长条。已整体删除高光,只留冰蓝白渐变三角 + 淡蓝描边(与参考图一致)。全部回归绿。 ## 冰面提速 + 删除首页操作说明 + 物理手感下调(用户要求三连) - **冰面最大速度阈值调大**:`js/player.js` 新增 `PHY.ICE_TOP: 1.6`(原内联 `HSPD*1.35`),冰面/滑行惯性速度上限 = HSPD×1.6;`ICE_MAX` 局部变量改由 `PHY.ICE_TOP` 计算。ice-test 注释数字同步为 4.32。 - **删除首页操作说明**:`index.html` 去掉 scr-title 的「操作说明」按钮和整个 `#scr-help` 屏;`js/main.js` 删 `help` action;`js/ui.js` 的 `SCREENS` 删 `'scr-help'`;`css/style.css` 删 `.help-*` 规则。 - **物理手感下调(用户:「kid移动速度减慢点,跳跃重力小一点」)**:`js/player.js` 与 `tools/lib-reach.mjs` 同步 GRAV 0.4→0.34、HSPD 3→2.7。扫描确认仅 j1/j9 终点不可达,按「调手感+重调关卡」方案修图: - j1 回廊:`carpet(m, 81, R-11, 15, [0, 8])` —— x89 加缺口。原因:新物理下从 x88 跳台落进 x90 时落点偏右(fx≈2895),满跳第 3 帧就蹭 x91 的「>」刺尖;有 x89 就能走进 x90 从左侧起跳越过对刺门。 - j9 L0:`carpet(m, 5, 27, 116, [54])` —— x59 加缺口。原因:矮洞天花板(row 24, x60-75)挡住 x57→x61 四格跳的横向漂移,玩家撞顶后下坠砸原点刺;拆成 57→59→61 两个 2 格跳(短跳 apex≈43px,碰不到洞顶)。 - j9 L1:`carpet(m, 9, 13, 108, [27])` —— x36 加缺口。原因:hgap(36,13) 的通道列 36 被 carpet 填了 '^',整门是 3 连刺,从右往左回跳被 x37 的「<」刺别住;补缺口后恢复两段式穿越。 - 全部回归绿:solve(23 关终点+存档+苹果全可达)、check/map/smoke/dom-smoke/save/key/spike/settings/editor-wysiwyg/ice/death/jumpdist/save-diag/bot 共 15 项。 - 教训(已入 MEMORY.md):① 4 格缺口跳在新物理下能跳但起拍位必须够靠左,跳台落点偏右是隐形杀手;② 实心天花板会挡高跳的横向漂移;③ hgap 通道列必须本身为空,carpet 缺口没对齐就会留一根 '^' 把门变成 3 连刺。 ## 编辑器:去掉「查看属性」工具,改为任意工具点击即看属性(用户要求) - `js/editor.js`:`PALETTE` 删除 `['?', '查看属性', ...]`(调色板 25→24 项,全是真实方块);`buildPalette()` 删掉 '?' 的放大镜字形特例;`act()` 删掉 `sel === '?'` 分支——现在所有点击都先 `selTile = {tx,ty}` 再 `renderProps()`,配合原有「放置不覆盖」规则,拿任何非橡皮方块点击已有格子都只查看不覆盖;空格子则放置新块并显示其属性;橡皮擦与右键保持擦除语义。 - 新增回归 `tools/editor-pick-test.mjs`(18 断言):假 DOM 的 addEventListener 真实收句柄,可模拟画布 mousedown;验证 PALETTE/UI 无 '?'、拿 ^/#/% 点 C/T 只查看不覆盖且面板渲染对应字段、拿 O 点空格放置并显属性、橡皮/右键擦除、空态引导文案。注意 renderProps 的字段是 appendChild 子行,断言要拼 `box.innerHTML + children.map(c=>c.innerHTML)`。 - `tools/editor-wysiwyg-test.mjs` 清理 '?' 死过滤。 - 全套 16 项回归绿(solve 23 关可达不受影响,本次只改编辑器交互)。 ## 重力再降一档 + 去掉跳跃时 kid 身体晃动(用户要求) - **GRAV 0.34 → 0.33**(`js/player.js` PHY + `tools/lib-reach.mjs` 同步)。"稍微调低一点点":单跳 apex≈109px、滞空更久。扫参发现 0.32 断 j2、0.33 断 j6(非单调——6px 状态桶对齐决定窄窗存不存在),选最小步长 0.33。 - **j6 修图**:`m.set(44, 15, '.')` 清掉 hgap(43,15) 的 '<' 刺。根因:该对刺门原本只靠「tile45.3(fx≥1448)满跳→tile41.3」这条 2px 宽起拍窗通过;'<' 刺尖会别住起跳(fx<1448 第 3 帧即死),0.33 下 hop 落点整体左移 2px 窗口够不着。清掉后留 ^(43)+>(42) 双刺门,整格起拍窗 + 左右两个方向都稳。修后 j6 状态数 535→934。 - **去掉跳跃晃动**:删 `player.js` 起跳/二段的 `squash=1.28/1.22` 拉伸;`sprites.js` 的 `drawKid` 改 `const sq = air ? 1 : p.squash`——空中恒定原始体型,落地挤压(0.72)残余也不得带入空中。落地挤压保留(在地面,不属"跳跃时")。 - 教训(已入 MEMORY.md):① 对刺门的侧刺('<'/'>')会把起跳/落点窗切成 2px 级窄窗,求解器 6px 状态桶的对齐就能翻转整关可解性——改物理后遇到"某一跳全灭",先查是不是侧刺别住起拍;② 追这类问题用逐帧 trace(复刻 sim.jump 主循环打印每帧 x/y/vy + deadlyRect),比手算几何可靠得多。 - 16 项回归全绿;solve 23 关终点+存档+苹果全可达。 ## JUMP 8.5→8.0 降低跳跃高度(用户:「跳跃的高度太高了,调低点」) - `js/player.js` PHY + `tools/lib-reach.mjs` 同步:JUMP 8.5→8.0。单跳 apex 109.5→97px、水平 139→131px。只降 JUMP 不动 GRAV——保留用户之前要的「小重力飘」手感。 - 扫参定位只破坏 j8(存档#4/#5+终点不可达)。逐帧 trace 实证:j8 x83-84 台(row18)→x88-89 台(row16) 这一跳的航线正对 vgap(86,14);`spikeTri` 的 '^' 顶点在格顶 y=480(无收紧),JUMP 8.0 单跳 apex 盒底 479——只差 1px,顶点 (2768,480) 在起跳后 t=21~25 共 4 帧落在玩家盒内,SAT 铁死。JUMP 8.5 时 apex 盒底 466 从容擦过。 - 修图(j8 两处,其余 22 关零改动): - `vgap(m, 86, R-8)` → `m.set(86, R-9, 'v')`:去掉 x86 地刺 '^'、保留压顶刺 'v'。试过「x88-89 台加宽到 x87-89」无效(上升段照样死,落点加宽治标不治本)。 - x87 加 '-' 台(配合上面修完后的落点 2802 能上台)。 - 修后 j8 状态数 307→469,23 关终点+存档+苹果全可达。 - 16 项回归全绿。教训已入 MEMORY.md:① apex 盒底与航线 '^' 顶点差 1px 时顶点会连续 4 帧在盒内,改 JUMP/GRAV 后遇到「某一跳全灭」先算 apex 盒底对比航线刺顶点;② 逐帧 trace 复刻 sim.jump 主循环是定位这类问题的唯一可靠手段(手算 SAT 截面连错三次)。 ## JUMP 再降一档 + 下落分手重力 + 零形变(用户三连要求)+ 求解器加固(本次会话主体) 用户原话:「跳跃的高度太高了,调低点」→「跳跃高度再调低一点,下落时的重力加速度再明显点,而且kid落地时候依然有拉伸效果,还是不行」。 **物理落地**(`js/player.js` + `tools/lib-reach.mjs` 同步): - JUMP 8.0→7.5:单跳 apex 97→85px、水平 131→118.8px(jumpdist 实测)。 - 新增 `GRAV_FALL: 0.55`:上升 0.33 轻、下落 0.55 重(分手重力)。落地前一帧竖直速度 8.9px/帧。 - **squash 形变全部删除**:`player.js` 去掉 squash 字段/起跳赋值/落地挤压逻辑;`sprites.js` 的 `drawKid` 按原始 11×21 画;`editor.js` 预览桩同步删字段。用户两次强调落地拉伸也不要。 **求解器三个加固**(`tools/lib-reach.mjs`,solve/reach-diag 默认值同步): - RELSET [0,4,8,14] → [0,4,5,6,8,14]:vgap 夹缝需要"第 5/6 帧松键"的短跳(apex 32~43px 窗口)。实证:rel=4 t=7 盒右沿蹭地刺左坡 2.6px;rel=6 t=11 盒顶擦垂刺尖 0.6px;只有 rel=5 两边都躲开(j2 x16);j7 顶层 x52 夹缝则要 rel=6。 - **fy 规范化(canon)**:落点常比地面高零点几 px(1535.5 vs 1536),与精确落点同 3px 桶——先占桶者赢,走链被错位几 px 的跳链顶掉。加 rel=6 后 j2 状态数从 1572 暴跌到 80 就是这么来的。push() 里按 canStand 门控吸附到 32 倍数。 - 状态桶 3px(上轮已改)。 **关卡重调**(`js/levels/jump.js`,核心矛盾:JUMP 7.5 + GRAV_FALL 0.55 下 3 连刺=96px 的起拍窗只有 0~1 帧,实测不可玩): - **carpet 缺口间距 4→3**(3 连刺→2 连刺=64px,射程余量 13→55px):一行改动全局生效。 - j2 五处显式 gaps(井口/左端出口的 3 连刺必须拆,walk 限制使起拍位比所需位窄 1~2px): - L0:`carpet(m, 5, 47, 50, [1, 18])` 保持(x25-27 已被间距 3 拆成 2 连刺)。 - L2:`[1, 11, 31]` = x12 拆井口 3 连刺;x22/x42 补 hgap 夹缝通道列(原来被 carpet 填 '^',整门变 3 连刺且左侧落点是 4 格跳 128px 超射程)。 - L3:`[0]` = x10 拆左端 3 连刺(左行跳落点盒右沿差 0.8px 蹭 x10 左坡;L1 同位置能过是因 vgap(12) 清空了 x10-14,L3 没有)。 - L4:`[1, 41]` = x12 拆井口 3 连刺;x52 补 hgap 夹缝通道列。 - 修后 j2 状态数 80→2421(10/10 存档+终点),23 关全可达。 **验证**:solve 23 关终点+存档+苹果全可达;check/map/smoke/dom-smoke/save/key/spike/settings/editor-wysiwyg/editor-pick/ice/death/jumpdist/save-diag/bot 共 15 项全绿。 ## GRAV 0.33→0.29 上升再飘一档(用户:「跳跃的重力参数再飘点」) - `js/player.js` PHY + `tools/lib-reach.mjs` 同步:GRAV 0.33→0.29(仅上升段,GRAV_FALL 0.55 不变)。单跳 apex 85→91px、水平 118.8→122px。 - 本轮零修图:23 关全可达(状态数与上轮持平微增)。原因:上轮已把 carpet 缺口间距改 3 格(2 连刺=64px),射程余量充裕,上升更飘不触任何窄窗。 - 16 项回归全绿。至此手感链:移速 2.7 / 上升重力 0.29 / 下落重力 0.55 / JUMP 7.5。