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

8.2 KiB
Raw Blame History

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 措辞「血迹」→「红叉」。全套仍全绿。