Files
kidgo/.workbuddy/memory/2026-10-24.md
T
2026-10-24 02:35:03 +08:00

107 lines
17 KiB
Markdown
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.
# 2026-10-24
## HSPD 2.7→2.6 走路再慢一点点(用户:「kid走路速度再稍微慢一点点」)
- `js/player.js` PHY + `tools/lib-reach.mjs` 同步:HSPD 2.7→2.6。扫参:2.5 断 j7+j10 两关,2.6 只断 j10 一个存档,选 2.6。
- **j10 修图**:L3 钢丝区 x55 加过渡柱 `m.vline(55, 5, 7, '#')`。根因(逐帧 trace + 组合探针实证):x58→x52 是 6 格=192px,2.7 下唯一生路是"晚二段 dj=30"(先走完单跳全程再二段,滞空 83 帧水平 224px)落 tile 52.94;2.6 下同一组合水平少 8px,轨迹到达柱区时已掉过柱顶 8.7px,盒左沿蹭柱右壁后摔进 x53 刺毯。dj=12 的轨迹在 3 格处 feet 恰回 160、4 格处 feet 恰回 192,故 x55 拆成 3+3 格两跳,两跳都精确接住。save/终点/steps 数组全未动。
- 验证:solve 23 关终点+存档+苹果全可达(j10 11/11,状态 3796→4003);16 项回归全绿。
- 教训(已入 MEMORY.md):GRAV_FALL 0.55 下"高等抛物线接远处柱顶"类路线全部失效(落地高度时水平只飞 113~130px,够不到 160px+ 柱距);HSPD 每降 0.1 让晚二段落点整体平移 8px 级,钢丝柱/跳台类窄窗关卡首当其冲——修法优先"拆间距加过渡柱"而非加宽(轨迹低于柱顶时加宽接不住)。
## GRAV_FALL 0.55→0.40 下落变飘(用户:「下落速度还是不够飘」)
- 注意这是用户对下落的**反向修正**:早些时候要求「下落重力加速度再明显点」(当时 0.33→分出 0.55),现在要飘,落 0.40。跳跃高度/JUMP/GRAV 均不动。
- `js/player.js` PHY + `tools/lib-reach.mjs` 同步。滞空变久:二段跳射程 132.6~240px(原 120~231px)。
- 破坏 j6(缺 5 存档+终点)、j9(缺 1 存档 1 苹果),断点都是**从右往左过 hgap 对刺门**(j6 @x95、j9 @x68):GRAV_FALL 0.40 让满跳轨迹在门区下降变慢,正好嵌进通道列里留的 '^'(carpet 缺口间距 3 对齐不到门列)。trace 实证 t=43 盒底 416.1 已擦 x69 '>' 刺左缘、t=45 嵌入 x68 '^' 顶点区。
- 修图(两处 gaps,纯补通道列净空,不动对刺):j6 L1 `carpet(m,9,15,112,[86])`;j9 L1 `carpet(m,9,13,108,[27,59])`。修后 23 关全可达(j6 状态 852→1875、j9 1149→1460)。
- 教训(已入 MEMORY.md):**GRAV_FALL 变小会让"回跳过对刺门"的轨迹在门区滞留更久**,门芯 '^'(hgap 通道列未净空)从"能勉强擦过"变成"必嵌进去"——改下落重力后重点复查所有从右往左的 hgap 门。
- 16 项回归全绿。
## GRAV/GRAV_FALL 再各降一档 + 工作流变更(用户:「跳跃和下落再稍微飘一点点,你以后不用测试自带地图是否能通关,直接改就行」)
- `js/player.js` PHY + `tools/lib-reach.mjs` 同步:GRAV 0.29→0.27、GRAV_FALL 0.40→0.37。
- **用户免除地图通关验证流程**:今后改物理直接落地,不跑 solve、不为可达性修图(已入 MEMORY.md)。本次仅跑 smoke/dom-smoke 确认代码无坏。
- 预计影响:单跳 apex 91→94px、二段滞空更久;按用户偏好未验证/未修图。
## GRAV/GRAV_FALL 再降一档(用户:「再飘一点」)
- `js/player.js` PHY + `tools/lib-reach.mjs` 同步:GRAV 0.27→0.25、GRAV_FALL 0.37→0.35。
- 按用户豁免的工作流直接落地,未跑 solve/未修图;smoke/dom-smoke 通过。
## JUMP 7.5→6.6 满跳高度定为 2.8 格(用户:「一个满力跳的高度应该大约是2.8个方块」)
- `js/player.js` PHY + `tools/lib-reach.mjs` 同步:JUMP 7.5→6.6(GRAV 0.25 不变)。实测满跳顶点 90.4px = 2.83 格(目标 2.80 格=89.6px,偏差 0.8px;JUMP 6.5 会偏浅到 2.74 格)。二段跳顶点 209.8px = 6.56 格(未按用户要求调整)。
- 用户豁免地图通关验证,仅跑 smoke/dom-smoke/jumpdist(jumpdist 是纯物理测量,保留)。
## DJUMP 7.6→5.0 + 二段跳惯性叠加(用户:「满力二段跳的最高高度应该约为4.5个砖块,且如果在下落时二段,需要考虑下落的惯性的速度叠加跳跃的速度」)
- `js/player.js` PHY + `tools/lib-reach.mjs` 同步:DJUMP 7.6→5.0。二段实现从直接覆盖 `vy = -DJUMP` 改为叠减 `vy = Math.max(vy, 0) - DJUMP`(上升中按等价覆盖,与原版语义一致;下落中按下落惯性抵消冲量)。
- 实测(探针复刻积分):顶点接二段(满力最高)142.9px = 4.47 格 ≈ 目标 4.5 格;起跳瞬间接二段 52.5px = 1.64 格(比单跳低,与原版一致);下落 vy=+2 时二段回升 19.5px、vy≥+5 时完全失效(vy_new≥0 升不起来)——惯性叠加的直接后果,生效窗口为顶点后约 14 帧(GRAV_FALL 0.35)。
- 用户豁免地图通关验证;smoke/dom-smoke/jumpdist/ice/death 5 项通过。
## 二段跳改同款飘:GRAV_DJ 0.15 + DJUMP 4.0(用户:「二段跳的高度和一段差的也太明显了,二段也要和一段一样飘,但是高度在4.5格」)
- 问题:DJUMP 5.0 + 通用 GRAV 0.25 时二段上升只有 20 帧/52.5px,与一段的 27 帧/90.4px 观感差距大("短冲"vs"慢飘")。
- 方案:新增 `PHY.GRAV_DJ: 0.15`(二段上升段专属轻重力)+ `DJUMP 5.0→4.0`。二段初速小、重力更轻 → 上升帧数与一段持平。
- `js/player.js`:二段时置 `this.djUp = this.vy < 0`;积分行改 `vy + (vy<0 ? (djUp ? GRAV_DJ : GRAV) : GRAV_FALL)`,到顶(vy>=0)交回普通重力;`djUp` 在构造/reset/水中/落地四处清零。惯性叠保留:`vy = Math.max(vy,0) - DJUMP`。
- `tools/lib-reach.mjs` 同步 GRAV_DJ 常量 + jump() 内 djUp 标志同款逻辑。
- 实测:一段 27 帧/90.4px=2.83 格;顶点接二段 27 帧慢飘、总高 145.7px=4.55 格≈4.5 ✓;下落惯性叠加保留(vy=+1 回升 0.98 格、+2→0.45、+3→0.12,均为慢飘)。
- smoke/dom-smoke/jumpdist/ice/death 5 项通过;按用户豁免未跑 solve。
## 二段跳去悬停:DJUMP 4.0→6.0 / GRAV_DJ 0.15→0.35(用户:「现在二段就像在空中停了一段时间,请你参考视频里的完美复刻」)
- 用户发了参考视频(微信录屏,某 IWanna 系手游)。cv2 抽帧分析:该游戏 TILE≈44.75px、kid 高 35px、一段约 30 逻辑帧(60fps)升 2.9 格(平均 3.3px/帧)、上升干脆顶点不拖沓;kid 沿砖柱贴墙反复跳。
- 病根:上轮 GRAV_DJ 0.15 + DJUMP 4.0 使二段平均上升速度仅 2px/帧、耗时 27 帧——又慢又长=悬停感。
- 修正:DJUMP 6.0 / GRAV_DJ 0.35。二段 18 帧、平均 3.02px/帧(一段 3.35,速度感一致)、总高 144.8px=4.52 格 ✓。下落惯性叠加保留(vy=max(vy,0)-DJUMP),窗口加宽:vy=+1/+2/+3/+5 接二段分别回升 38.3/24.9/14.4/1.9px。
- 5 项冒烟通过;按用户豁免未跑 solve。
- 方法论备忘:无 ffmpeg 时用 `pip install opencv-python-headless`(装进 /tmp venv,全局 pip 会被 SIGTERM)抽帧;定位游戏内物体先量参照物(砖柱网格)反推 TILE 像素,再用特征色(kid 蓝衣/棕发)追踪。
## 二段跳「非常短」bug 修复(用户报告)
- 病根:`player.js` 二段公式 `vy = max(vy,0) - DJUMP` 把当前下落速度**全额**抵消冲量。满力一段到顶点后,人类反应(松手再按)天然晚 10+ 帧,vy 已 +3.5~7,二段实际提升仅 5~15px 甚至归零。
- 修复:`PHY.DJ_INERTIA = 0.5`,公式改 `vy = max(vy,0) * DJ_INERTIA - DJUMP`;`tools/lib-reach.mjs` 同步同值同式。
- 实测(`tools/dj-test.mjs`,真实引擎,二段自身提升高度):顶点 46→47px、+3帧 30.6→39px、+7帧 14.9→29.3px、+11帧 4.8→20.9px、+15帧 0.3→13.9px、+19帧 -1.1→8.4px。顶点附近保持满力,摔得快仍按惯性变弱(满足用户此前「下落惯性叠加」的要求)。
- 回归:dj-test / smoke / ice-test / spike-test 全过;solve 对照实验(DJ_INERTIA 临时置 1.0)证实 **j8 不可达是更早物理轮次的既有问题**(状态数 158 两次一致),非本次引入,按用户豁免未修图。
- 教训:抄 jumpdist 的 harness 时 `addL` 漏了 `handlers[t] =` 赋值,监听全丢、测试全 0——先跑最小实验(dbg3:裸 addEventListener 替换生效)再怀疑业务代码。
## 二段"好沉+高度短"二连修(用户报告)+ 工作流禁令收紧
- 用户新规矩(原话):「以后没有明确说明,你不要给我自动测试地图的可达性」——即使用户要求改物理也不主动跑 solve.mjs,已写入 MEMORY.md 工作流偏好节。
- 病根①「沉」:GRAV_DJ 0.35 比 GRAV 0.25 重 40%,二段上升减速快、发闷。修复:GRAV_DJ 0.35→0.25(与一段完全一致,"二段和一段一样飘")。
- 病根②「短」:DJ_INERTIA 0.5 对下落接二段的冲量抵消过多。修复:0.5→0.35。
- 配套定标:DJUMP 6.7→5.4 时顶点总高仅 4.32 格(引擎积分是**先加重力再移动**,此前预估公式高估 ~6px);微调 5.65 后顶点二段提升 59.9px、总高 4.49 格 ✓。
- 最终曲线(dj-test 实测,二段自身提升):顶点 59.9px / +3帧 52.7 / +7帧 44.0 / +11帧 36.0 / +15帧 28.8 / +19帧 22.4。
- `tools/lib-reach.mjs` 常量同步:GRAV_DJ 0.25 / DJUMP 5.65 / DJ_INERTIA 0.35。
- 回归:dj-test / smoke / ice / spike / save / key 全过(未跑 solve,遵守用户禁令)。
- 踩坑记录:Edit 工具对 player.js 含中文注释的多行块连续匹配失败(原因不明),单行 Edit 正常;php/python 改文件可行。以后改 player.js 优先单行 Edit。
## 二段"好沉+短"根治(用户二次反馈"还是和上一个问题一样")
- 诊断翻案:前两轮误把"沉"归因于 GRAV_DJ 0.35,实际根源是**起跳初速被下落速度吃掉**——一段起跳初速 -6.6,而下落接二段时 `vy = max(vy,0)*k - DJUMP` 使初速只剩 -3.3~-4.4(k=0.5/0.35),起跳那一下就没劲;初速小→上升自然短。调重力治标不治本。
- 修复:DJ_INERTIA 0.35→**0**(覆盖式二段,同 IWBT 原版,任何时机接二段初速恒 -DJUMP);GRAV_DJ 回 0.35(平均上升 3.18px/帧 与一段 3.23 一致;0.25 时只有 2.72 偏"肉");DJUMP 回 6.7。
- 实测(dj-test):任意时机(顶点~顶点后19帧)二段自身提升全部 60.8px,曲线完全持平;顶点总高 4.52 格 ✓。
- 用户最初提的"下落惯性叠加"经三次实践证明给不出好手感,放弃;参数保留 DJ_INERTIA(现值 0)供回退。
- 回归:dj-test/smoke/ice/spike/save/key 全过;未跑 solve(用户禁令)。
- dj-test 断言已随 INERTIA=0 语义重写(各时机 ≥55px 且与顶点差 ≤3px)。
## 死亡流程改造:手动重生 + 小死亡动画(用户需求)
- 需求原话:「当角色死亡后不要立马重生,需要用户手动按重生键重生,并且死亡时除了原先的红叉,还要附加一个小的死亡动画」。推翻 2026-10-23 的"立即复活、无动画"旧需求。
- player.js:kill() 重写(定格 + 粒子爆发 16 个 + shake=6 + deathX/deathY clamp 到关卡内,红叉位置改用 clamp 后坐标);update() 开头 `if (this.dead) { this.deadT++; return; }` 早退;移除 4 处"kill 后当帧 respawn"(掉坑/刺/update 末尾兜底/假存档);draw() dead 分支画扩散冲击环(deadT<=14,半径 4→29px 淡出,kid 不画);reset() 初始化 deathX/deathY。
- main.js:suicide() 分流(alive→kill;dead→respawn),R 键两段式语义;render 的 playing 分支加死亡提示("按 R 重生"呼吸闪烁 + "Backspace 整关重来",触屏文案"按右下角重生键")。
- 触屏重生键(touch.js→api.suicide)自动获得死亡中重生能力,无需改。
- 测试:death-test.mjs 全面重写为 23 断言(定格/输入无效/手动 respawn 回存档点/连续 5 次死亡/红叉常驻+去重+reset 清空/掉坑 clamp/渲染冒烟)全过;key-test(R 自杀→定格、死亡中 R→重生)、save-test(kill 后手动 respawn)、smoke(死亡段改新语义)同步更新;dj/ice/spike/dom-smoke/editor-pick 全过;未跑 solve(禁令)。
- 踩坑:测试 grid 里 'S' 是出生点、存档点字符是 'G'(level.js:24),写测试地图别搞错。
## 重生键语义简化:任何状态按 R 都直接重生(用户反馈)
- 用户原话:「如果没死,由玩家手动按了重生键就直接重生」。此前 R 是两段式(活着=kill 定格→再按一次才回存档点),用户嫌按一次没反应。
- main.js suicide() 改为无条件 `G.player.respawn()`:活着按 = 放弃当前进度秒回存档点(不死亡、不计死亡数——主动重生不是失误);死亡中按 = 复活。触屏重生键走同一入口自动生效。
- key-test R 段重写:先站到出生点→按 R→验证回到存档点且 alive/dead/死亡数不变;再 kill→死亡中按 R→验证复活。全过。
- 回归:key/death/save/smoke/dj/ice/spike/dom-smoke/editor-pick 全过;未跑 solve(禁令)。
## 重生键最终形态:按一次 = 死亡动画 + 自动重生(用户二次反馈)
- 用户先要求「没死按重生键直接重生」,AI 做成无动画秒回;用户再反馈「不是,直接按重生键有死亡动画的」——要的是**一次按键=播死亡动画→演完自动重生**。
- 最终语义(main.js suicide):死亡中按 R = 立即复活;活着按 R = kill({mark:false, count:false, autoRespawn:30})——粒子+轻震+冲击环照播,不留红叉、不计死亡,30 帧(0.5s)后自动回存档点。
- player.js:kill() 参数化 opts{mark,count,autoRespawn};update 死亡早退里 autoRespawn 到期自动 respawn;reset/respawn 清理该字段。软重置(softReset 清粒子+shake)会收走动画残余粒子,属预期(爆发与冲击环已看完)。
- 测试:death-test 新增场景 9(主动重生键 7 断言)全过;key-test 活着按 R 段重写(演出中 dead/零计数/零红叉→33 帧后回存档点);save/smoke/dj/ice/spike/editor-pick/dom-smoke 全过。
- 补充(用户三次反馈):主动重生键演出期间**不显示**「按 R 重生」提示(main.js render 条件改为 `P.dead && !P.autoRespawn`)——演完自动重生,提示多余;真死亡时提示保留。
- 补充(用户四次反馈):主动重生键原定 autoRespawn=30 帧,冲击环 14 帧就播完,中间 16 帧只剩稀疏粒子=「空档」。改为 **autoRespawn=15**(冲击环 deadT<=14 播完的下一帧重生,零空档零腰斩)+ kill() 新增 opts.life 覆盖粒子寿命(主动模式 life=15 与冲击环同步收尾,避免被 softReset 腰斩满 alpha 粒子)。shake=6 每帧 ×0.86 衰减,15 帧时≈0.56px 无感,不会被重生清场掐断。
## 主动重生键终版:当帧重生 + 特效留在死亡位置播完(用户五次反馈)
- 用户原话:「不用等死亡动画结束再重生,直接重生就行了,动画在死亡位置放完」。此前 autoRespawn=15 帧的"演完再重生"方案废弃,autoRespawn 机制整个删除。
- 架构调整:冲击环从 player.draw 的 dead 分支**搬到 Level 层**(`deathFX` 数组 + addDeathFX + drawDeathRing + update 推进 14 帧移除)——否则玩家一重生环就消失,无法"在死亡位置放完"。
- player.js:新增 `deathBurst()`(纯演出:粒子+shake+addDeathFX,不进死亡状态);kill() 改为调 deathBurst + 红叉/计数(opts 仅剩 mark/count);respawn(opts) 透传 `softReset(opts.keepFX)`;draw() dead 分支简化为不画 kid。
- level.js:`softReset(keepFX=false)`——keepFX=true 时不清粒子/震屏(主动重生专用,让特效自然播完);reset(true) 清 deathFX。
- main.js suicide:活着按 = `deathBurst() + respawn({keepFX:true})` 当帧完成,零等待;死亡中按 = respawn()。
- 真死亡流程不变(定格+手动+红叉+计数);粒子 life 34 / 冲击环 14 帧 / shake ×0.86 全部自然播完(重生不清场)。
- 测试:death-test 场景 8/9 重写(当帧重生、纯演出不进死亡态、粒子/环/震屏全程自然播完断言),key-test 活着按 R 段重写;全套回归过。
- 补充(用户六次反馈):「kid 被动死亡的,死亡动画也应该放完,尽管玩家很快按了重生键」。根因:`softReset()` 无脑清 particles+shake——重生即清场,特效被腰斩。修复:softReset 永久不清粒子/震屏("演出层"语义:重生只重置关卡逻辑状态,不掐断演出;仅 reset(true) 整关重开才清空)。keepFX 参数随之删除,respawn() 恢复无参。踩坑:python 替换时把 `softReset(keepFX=false){` 函数头和 vanish/bullets 清逻辑一起吃进 old 串,文件语法损坏——已修复(补回函数头+4 行清逻辑,node import 验证)。教训:rep() 的 old 串跨函数签名行时,new 必须原样带回签名与后续语句。
- 测试:death-test 新增场景 10(真死亡 3 帧秒按 R:粒子/环/震屏全程保留并自然播完,7 断言);key-test 死亡中按 R 段加"残余粒子"断言;全套回归过。