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

22 KiB
Raw Blame History

2026-10-23 工作日志

首页移除「清除存档」按钮(用户要求)

  • 需求:把首页(scr-title)的「清除存档」按钮删掉。
  • 改动:仅删 index.html:41 的 <button class="btn small" data-act="wipe">清除存档</button> 一行。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 397%),缩放则按「当前指针到中心距离 ÷ 按下时距离」缩放 size(24160px)。第一帧必然停在正确位置,不再跳角。还顺手加了 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。