Files
kidgo/.workbuddy/memory/2026-10-22.md
T

58 lines
8.2 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-22
## 工作区被清空后从对话记录重建 + 移动端虚拟手柄
- 本会话开始时 `/Users/cancerai/tmp/iwanna` 被清空(workspace_recovery 重建为空目录)。游戏全部源码并未丢失:完整对话记录在 `/Users/cancerai/.workbuddy/projects/Users-cancerai-tmp-iwanna/dd6693af-142a-4164-8c3a-fb33ab9626d8.jsonl`(约 7.9MB)。
- 重建方法:按 JSONL 行序重放所有 `Write`/`Edit` 调用;另有 7 处**通过 Bash 的 sed/perl 修改项目文件**(diamond→vgap、R-3→R-2、corner→block、j2 台阶加宽、3 个测试脚本 `mv` 进 `tools/` 并改 import)必须在对应行号处同步插值,否则 `jump.js` 大量 Edit 会 miss。重建后 `check.mjs` 与 `solve.mjs` 全部通过(23 关可达)。
- 新增移动端虚拟手柄:
- `js/input.js` 增加 `touch` 状态层 + `setTouch(action,bool)` / `clearTouch()`,与键盘共用 `now/prev` action 模型。
- 新模块 `js/touch.js`:左下 `◀▶` 移动、右下大「跳」键 + 🔫射击(按住连发)+ ▼下蹲 + 暂停/重生小键;pointer 事件 + `setPointerCapture` 支持多指;`show/hide/setGun`。
- `index.html` 加 `#touch` 覆盖层 + viewport 锁缩放;`css/style.css` 加 IWBT 风格半透明控件(含 `env(safe-area-inset-bottom)` 安全区)。
- `js/main.js`:触屏自动检测(`?touch=1` 强制开 / `?touch=0` 关);`startLevel`/`resume` 显示、`pause`/`finish`/菜单隐藏;HUD 同步 `hasGun` 控制射击键置灰;触屏射击走 `shoot()` 回调(受 `hasGun` 与冷却限制)。
- 验证:`node --check` 三个 JS 均通过;mock-DOM 运行测试确认按下/松开正确置位、pause/shoot 回调触发、hide 清理卡键;`check`/`solve` 仍全绿。
## 桌面端不显示触屏按钮(用户明确要求)
- 原来只靠 JS 切换 `#touch.hidden` 类来显隐,若某处误调 `Touch.show()` 桌面端会冒出按钮。
- 改为「CSS 硬兜底 + `<html>` 类门控」:仅当检测到移动端/触屏(`ontouchstart` / `maxTouchPoints>0` / `(pointer:coarse)`)或 `?touch=1` 时,给 `documentElement` 加 `.touch` 类;`css/style.css` 新增 `html:not(.touch) #touch{display:none !important}`。这样桌面端无论 JS 状态如何都绝对不显示,触屏设备才显示。`?touch=0` 仍可强制关。
- 验证:node 模拟 5 种环境(纯桌面/触屏笔电/手机/`?touch=1`/`?touch=0`),输出与预期一致;服务端 `css/style.css` 已含该规则。
## 修复:滑出冰块后惯性丢失
- 根因:`js/player.js` 水平移动里 `onIce` 每帧按当前位置重算;一旦脚下滑出冰面,`onIce=false` 走 `else` 分支 `this.vx = target`,把冰面攒出的速度瞬间归零。
- 修复:新增 `this.sliding` 状态。在冰上攒出 `|vx|>0.6` 即标记 `sliding=true`;离开冰后只要 `sliding` 且 `|vx|>0.05`,就用比冰面稍大的地面摩擦(0.25)缓慢收速,保留惯性;顺向按键保持/恢复满速、反向按键较快抵消;`|vx|<0.1` 才清零并退出滑行,恢复 IWBT 标准「松手即停」。`reset/respawn/kill` 三处都重置 `sliding=false`。
- 验证:node 模拟「冰上按右 20 帧→vx=3.60,滑出冰面松手后 12 帧 vx 由 3.35 缓降到 0.60 仍滑行」;`solve.mjs` 仍全绿(可达性未退化)。
## 二次修复:滑出冰块后仍「感觉不到惯性」(真正根因)
- 上次只解决了「离冰瞬间归零」,但残留两个致命点(用 `tools/ice-test.mjs` 实测暴露):
1. **空中也按地面摩擦 0.25 衰减**(`roadFric` 不区分空/地)→ 滑出冰块边缘掉下去时水平速度 7 帧内归零,根本飞不出去,这是玩家最直观感到「没惯性」的场景。
2. 松开键时 0.25 的强摩擦一下刹死(实测只滑 2.5px);而按住同向键反而每帧 +0.5 一路涨到 4.05 且永不回落,滑行比走路还快。
- 现在 `PHY` 新增滑行常量:`SLIDE_FRIC 0.12`(地面收住)/ `SLIDE_AIR 0.02`(空中几乎无损)/ `SLIDE_EASE 0.14`(超速时收敛回走速)/ `SLIDE_BRAKE 0.45`(反向刹车)/ `SLIDE_MIN 0.35` / `SLIDE_STOP 0.05`;`ICE_MAX = HSPD*1.35`。
- 行为约定:空中保留惯性;地面缓慢收住(4.05→0 约 34 帧 ≈ 70px);按住同向键只让超速**收敛**回 3(不再加速),低于走速即交还玩家控制;反向键刹车后立刻反向;撞墙/归零即退出滑行。
- 另加可视化反馈:超速滑行且着地时每 5 帧在脚下溅 2 粒冰屑(`js/player.js`),让惯性看得见。
- 新增回归测试 `tools/ice-test.mjs`(4 个场景 + 断言,退出码 0/1),已并入全套:`check / solve / smoke / save-test / key-test / dom-smoke` 全绿。
- 顺手补了 `tools/key-test.mjs` 与 `tools/dom-smoke.mjs` 缺失的浏览器桩:`location`、以及移动端手柄引入后需要的 `navigator`(用 `Object.defineProperty` 覆盖,Node 22 只读) 与 `matchMedia`——这两个脚本此前在 Node 22 下直接崩,现在可跑。
## 死亡即复活(无死亡动画)
- 用户要求:kid 死亡后立刻复活,不要死亡动画。
- `js/player.js`:`kill()` 去掉粒子爆炸、震屏、死亡张嘴,只留 `sfx.death()` 与 `onDeath` 计数;`update()` 顶部 `if (this.dead) this.respawn();` 让 R 键自杀/假存档触发的死亡下帧即复;边界坠落与 `deadlyRect` 两处致死点改为 `kill()` 后**同帧** `respawn()` 并 `return`(不再等 34 帧的死亡动画);`respawn()` 末尾置 `justRespawned=true`。
- `js/main.js`:复活帧镜头 `snapTo` 瞬移(`wasDead && !P.dead` 或 `justRespawned`);删除渲染里的死亡血色闪烁 `rgba(200,0,40,.18)`。
- `js/sprites.js`:删除 `p.dead` 时的张嘴绘制。
- 新增回归测试 `tools/death-test.mjs`(10 项断言:同帧复活/无粒子/无震屏/justRespawned/连续 5 次/R 键自杀后立即可操作/30 帧无特效残留)。全套 7 个测试仍全绿。
## 死亡地点加 IWBTG 风格血迹效果(用户追加要求)
- 需求:即时复活仍保留,但在**死亡地点**留下模仿 IWBT(I Wanna Be The Guy)的 kid 死亡效果(血腥飞溅)。
- 实现:在 `js/level.js` 新增常驻 `deathMarks` 列表(仅 `reset(true)` 整关重开时清空,`softReset()` 不碰 → 复活后血迹仍在)。
- `addDeathMark(x,y)`:在死亡坐标落一处血洼 + 10 个卫星血点(基于 seed 的稳定伪随机,形状逐帧一致),外加 18 个红色方块飞溅碎块(自带 parts 列表,受重力、~32 帧后消失);最多保留 60 处。
- `drawDeathMark(ctx,dm)`:世界坐标下绘制常驻血迹 + 动画碎块;放在 `draw()` 的实体/子弹之后、全局粒子之前。
- `update()` 末尾演化各 mark 的碎块(重力、衰减、移除死亡碎块),血点常驻。
- `js/player.js` 的 `kill()`:`sfx.death()` 后调用 `this.level.addDeathMark(this.cx, this.cy)`(死亡坐标)。仍不触发震屏/屏幕级粒子(保持「无死亡动画」)。
- `tools/death-test.mjs` 扩充到 14 项:新增「死亡地点留下 IWBT 血迹」「30 帧后血迹常驻」「多次死亡血迹累积」「整关重开清空」。全套仍全绿。
## 死亡血迹改为「只显示上一次死亡」(用户再次调整)
- 用户要求:多次死亡时只显示**上一次**死亡的血迹,不要累积成尸体堆。
- `js/level.js` 的 `addDeathMark` 改为新增前先 `this.deathMarks.length = 0`(清掉旧血迹),所以任意时刻场上仅保留最近一次死亡的血迹;整关重开 `reset(true)` 仍清空。
- `tools/death-test.mjs` 地图 row2 加第二枚刺(col10),测试 5 改为「4 次死亡(前后两处)后 `deathMarks.length === 1` 且位于最后一次 col10」,验证只保留上一次。全套仍全绿。
## 死亡标记从血迹改为「红叉(X)」(用户再次调整)
- 用户要求:把死亡效果从血洼/血点/飞溅碎块改成简单的**红叉**即可。
- `js/level.js`:`addDeathMark` 只记 `{x,y}`(不再生成 parts 碎块);`drawDeathMark` 改为画一个带深色描边的红色 X(半边长 9,lineWidth 5 黑描边 + 3 红主体);`update()` 去掉碎块演化循环(红叉为常驻静态标记)。仍只在死亡地点显示、只保留上一次、整关重开清空。
- `tools/death-test.mjs` 措辞「血迹」→「红叉」。全套仍全绿。