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

63 lines
13 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.
# iwanna(IWBT C 同人网页游戏)项目长期笔记
## 物理与判定(像素级跳刺的核心)
- **用户工作流偏好(2026-10-24 起,二次收紧)**:用户明确说「以后没有明确说明,你不要给我自动测试地图的可达性」——改物理/手感参数时直接落地 `player.js` + 同步 `lib-reach.mjs`,**不主动跑 `solve.mjs`、不为重调地图修图**(用户另行要求才跑/才修)。求解器常量仍要保持同步(供用户日后自行诊断用)。其他非通关类回归(smoke/dj-test/ice-test 等)可轻量跑。
- 物理常量写在 `js/player.js` 的 `PHY`(2026-10-24 四调定格):GRAV 0.25(上升)/ GRAV_FALL 0.32(下落,分手重力;0.35→0.32 用户反馈"下落有点沉")/ GRAV_DJ 0.35(二段上升专属;试过 0.25,平均上升速度掉到 2.72px/帧 反而"肉")/ MAXFALL 9 / JUMP 6.6(满力一段 ≈84px=2.6 格,起跳初速 -6.6)/ DJUMP 6.7(起跳初速 -6.7 与一段几乎一致)/ HSPD 2.6 / DJ_INERTIA 0。手感调整链见日报;`tools/lib-reach.mjs` 有同名常量必须同步,且 jump/fall 的积分行是 `vy + (vy<0 ? (djUp?GRAV_DJ:GRAV) : GRAV_FALL)`。松手即停、无惯性、可变跳跃高度不变。
- **满力二段总高 = 4.5 格定标(144px)**:一段 83.9px + 二段 60.8px。帧量化使 DJUMP 取值有跳变,且引擎积分顺序是**先加重力再移动**(预估必须按此算,否则高估 ~6px):GRAV_DJ 0.35 下 DJUMP 6.0→47px(总 4.09 格)、6.5→57px(4.40)、6.7→60.8px(4.52 ✓)。定标流程:改参数→跑 `tools/dj-test.mjs` 看"二段顶点离平台"列→微调 DJUMP 到 4.5 格。
- **二段跳惯性规则(2026-10-24 三轮反馈后定型:INERTIA=0,覆盖式二段)**:`vy = max(vy,0) * DJ_INERTIA - DJUMP`,当前 DJ_INERTIA=0 即任何时机接二段初速恒为 -DJUMP(同 IWBT 原版)。**"沉"的根源是起跳初速被下落速度吃掉,不是上升重力**:用户三次反馈链路——全额抵消(顶点后 11 帧二段仅 4.8px,"非常短")→ 0.5(仍"短")→ 0.35 且误把 GRAV_DJ 降到 0.25(平均上升速度 3.18→2.72px/帧,更"肉",用户报"还是好沉+短")→ 0(初速恒 6.7 与一段 6.6 一致,起跳"嘭"感同款)。教训:用户说"沉"先查起跳初速(vy 被 max(vy,0)*k 吃掉),别急着调重力。现行曲线(dj-test):任意时机二段自身提升均 60.8px、顶点总高 4.52 格。回归:`tools/dj-test.mjs`。
- 玩家判定盒 **11×21**(`Player` 构造函数里 `this.w = 11; this.h = 21`)。改这里会同时影响 `level.js` 的 `spawn` 偏移和全部可达性工具。
- 刺碰撞是**三角×AABB(SAT)**:`js/level.js` 的 `spikeTri(t,x,y)` 顶点与 `drawSpike` 一致(上刺尖在格顶 y、左右刺尖在格缘中点),`triRectOverlap` 做精确判定;`deadlyRect` 网格刺+掉落刺都走它。旧的 `spikeBox` 矩形盒已删除。`T.KILLER` 必须有兜底 return。
- **kid 绘制零形变**:起跳/落地 squash 已全部删除(`player.js` 无 `squash` 字段,`sprites.js` 的 `drawKid` 直接按原始 11×21 画)。用户明确要求跳跃/落地都不要身体晃动。
## 死亡与重生流程(2026-10-24 新语义,推翻早期"立即复活"需求)
- **死亡后不自动重生**:`kill()` 后玩家定格在死亡点,世界继续运转(敌人/子弹/粒子/相机照走)但玩家不响应输入(`player.js` update 开头 `if (this.dead) { this.deadT++; return; }`)。等玩家手动按重生键。
- **重生键 = R**(复用 IWBT 标准自杀键):活着按 R = 自杀回最近存档点;死亡中按 R = 手动重生(`main.js` 的 `suicide()` 按 `G.player.dead` 分流)。触屏重生键走同一入口(`touch.js` → `api.suicide`)。Backspace 仍是整关重来。
- **死亡动画("小"三件套,红叉之外附加)**:① `kill()` 时 16 个红白粒子爆发(`L.particles.spawn`,life 34);② `L.shake=6` 屏幕轻震(camera 原生支持,0.86/帧衰减);③ 死亡点扩散冲击环(`player.draw` 里 `dead` 分支,`deadT<=14` 帧内半径 4→29px 淡出,kid 本体不画)。
- **死亡坐标 clamp**:`deathX/deathY` 记录时 clamp 到关卡内(掉坑死亡时玩家已在屏幕外),红叉/粒子/圆环都落在可见区域;红叉(deathMarks)仍常驻保留、多次死亡只留最后一次、`reset(true)` 清空。
- **render 死亡提示**:`main.js` render 的 playing 分支画居中"按 R 重生"(呼吸闪烁,`Math.sin(t/12)`)+ 副行"Backspace 整关重来";触屏文案"按右下角重生键"。
- 历史:早期用户曾要求"死亡立即复活、无任何动画"(旧 `death-test.mjs` 首行注释为证),2026-10-24 明确推翻。回归:`tools/death-test.mjs`(23 断言:定格/输入无效/manual respawn/连续死亡/红叉/clamp/渲染冒烟);`key-test`(R 两段式语义)、`save-test`、`smoke` 的死亡段已同步改。
## 冰面滑行惯性(易反复踩坑)
- 冰面物理只在 `js/player.js` 的水平输入段,`sliding` 是唯一惯性开关:冰上 `|vx|>SLIDE_MIN` 置位,离冰后仍按 `sliding` 走惯性分支,撞墙/归零/交还控制时才清位。
- **空中与地面必须分开摩擦**:`SLIDE_AIR 0.02`(几乎无损,能滑出冰块边缘飞出去)vs `SLIDE_FRIC 0.12`(地面约 34 帧/70px 收住)。曾经两者都用 0.25,导致「滑出冰块掉下去 7 帧就没速度」——玩家最直观的「没惯性」就来自这里。
- 按住同向键只能让超速**收敛**回 `HSPD`(`SLIDE_EASE`),不能继续加速;低于走速立刻 `vx=target` 交还控制。反向键 `SLIDE_BRAKE` 刹车后马上反向。
- 回归测试:`tools/ice-test.mjs`(冰→普通地面/悬空/起跳 4 场景 + 断言),改冰面物理必跑。
## 关卡设计硬规则(都踩过坑)
- `tools/solve.mjs` 是唯一权威:改完任何地图必须跑到「✅ 所有关卡终点与存档点均可达」。
- 之字竖井用 `needle.js` 的 `well(xL, xR, topRow, botRow, floorSide)`:每级升 2 格、台阶宽 5 且两级重叠 2 格、顶台与上层地板**同高相接**、靠地板侧井壁从 topRow+1 到 botRow-4(底部留 3 格入口)。
- 台阶**不能铺满井宽**(会变成 1 格高封闭走廊,跳不起来)。
- 顶台**不能压在地板洞口正上方**(会把井封死)——曾因此 j2 整关不可达。
- 完整四向钻石(上下左右刺齐全)在矩形判定盒下横向**无解**,只能当装饰;要「跳穿门」必须用 `vgap`(上下夹缝)或 `hgap`(左右夹缝)。
- 垂刺(压顶)与落脚点的关系:单跳最高 ~85px、二段跳 ~172px。压顶刺放在落脚点上方 5 行 = 单跳安全/二段必死;放太近(3~4 行)会让单跳也撞死。冰面上**不要**加压顶垂刺(滑行无法控制跳跃高度,实测 j8 直接不可达)。
- **刺毯缺口间距统一 3 格(跳 2 连刺 = 64px)**:JUMP 7.5 + GRAV_FALL 0.55 下落地前一帧竖直速度 8.9px/帧,3 连刺(96px)起拍窗只有 0~1 帧(实测 j1/j2 第一跑窗口=单点或负值,不可玩);2 连刺留 55px 射程余量。2026-10-23 从 4/3 交替 → 统一 4 → 统一 3,配合 `carpet(m, x0, y, n, gaps)` 的显式 gaps 拆断特殊位置的 3 连刺。
- **井口/左端出口的 3 连刺必须显式拆断**(walk 限制使起拍位比所需位窄 1~2px,右行必卡):j2 L0 x25-27、L2/L4 井口 x11-13、L3 左端 x10-12(左行跳落点差 0.8px 蹭刺左坡)都是这么拆的。判据:任何"出口紧接着 3 连刺"的段,不管求解器当时过不过,加 gap 拆成 2+1。
- **hgap 夹缝的两段式穿越**:通道列 x 必须为空(carpet gap 对齐),且左右接近列 x±2 也要是缺口/已清——否则从左侧跳过来落在 '>' 刺上,变成 4 格跳(128px > 单跳 118.8px)必死。j2 L2 的 hgap(22)/(42)、j2 L4 的 hgap(52)、j7 顶层的 vgap(52) 都靠补通道列缺口修复。
- **天花板(实心顶/垂刺)会挡跳的横向漂移**:高跳(apex 97px)撞到洞顶后横向停滞,下坠时砸在原点刺上——j9 矮洞入口 x57→x61 四格跳就是这么死的,拆成两个 2 格跳(x59 加缺口)即解。
- **apex 与刺顶点的 1px 竞争是隐形杀手**:`spikeTri` 的 '^' 顶点就在格顶 y(无收紧)。apex 盒底与航线上下方 '^' 顶点只差 1px 时,顶点会在起跳后约 4 帧整段落在玩家盒内(SAT 铁死)——JUMP 8.5 apex 盒底 466 vs 顶点 480 从容擦过,8.0 时 479 就死。修法:去掉该格地刺只留压顶刺(j8 x86),或给落点台加宽(治标不治本,上升段照样死)。
- `hgap(x,y)` 的通道列 x **必须本身是空的**(carpet 缺口或显式 clear):`hgap` 只清 x±2、在 x±1 放对刺。若 carpet 缺口没对齐 x(如 j9 L1 的 x36、j1 回廊的 x92),通道里会留一根 '^',整门变成 3 连刺——往回跳必被 '<' 刺别住。修法:给 carpet 加对齐的 gap。
- **对刺门的侧刺('<'/'>')会把起跳窗切成 2px 级窄窗**:从门前起跳时,侧刺尖在起跳后第 3~5 帧别住盒子(上升不够),必须从门前偏右 ≥半个身位起跳。求解器 `reach()` 的 6px 状态桶对齐与否能直接翻转整关可解性——改物理后若某跳「全灭」,先逐帧 trace 看是不是侧刺别住起拍,而不是先怀疑跳距。j6 hgap(43) 曾只靠 fx≥1448 的 2px 窗可解,GRAV 0.34→0.33 时 hop 落点左移 2px 即全灭;修法=清掉一侧侧刺留双刺门(j6 x44)。
- **调试利器:逐帧 trace**。复刻 `lib-reach.mjs` 里 `sim.jump` 的主循环(vy 积分→x 移动→y 分段→每帧打印 x/y/feet/vy + `L.deadlyRect` + 涉及格子),比手推几何可靠得多;手算 SAT 三角形截面极易错(连错三次的教训:盒**右沿**压刺左坡、盒顶压垂刺尖,都要用精确坐标复算)。
- **kid 绘制零形变**(见物理节):起跳/落地 squash 已删,别再加回"落地挤压"——用户明确说过落地拉伸也不要。
## 工具链
- `tools/lib-reach.mjs` 是 solve / reach-diag 的**共享 BFS 核心**,改建模只改这一处。
- 关键修正:跳跃水平方向被墙挡住时**不能把 vx 永久清零**(真实 `player.js` 里按住方向键下一帧会恢复)。清零会丢失「贴墙上升→越过台面→横移」这条经典路线,曾导致整个 j2 竖井判为不可达。
- `reach()` 默认 DJSET/RELSET 必须与 `solve.mjs` 的 `DJ`/`REL` 环境变量一致,否则诊断和求解结果对不上。现值:DJ=0,6,12,18,24,30 / REL=0,4,5,6,8,14。
- **RELSET 必须有 5 和 6**:vgap 夹缝(缝高 32px、盒高 21px)要求短跳 apex 落在 32~43px 且上升早期躲开地刺左坡——rel=4(apex 35.7)t=7 蹭坡 2.6px,rel=6(apex 44.7)t=11 擦垂刺尖 0.6px,只有 rel=5(apex 38.9)同时满足(j2 x16 夹缝);j7 顶层 x52 夹缝则要 rel=6。松键短跳是玩家的连续技巧,采样必须覆盖这两帧。
- **fy 规范化(canon)**:jump/fall 返回的落点常比地面高零点几 px(如 1535.5),与精确落点 1536 落进同一个 3px 桶——先占桶者赢,走链被错位几 px 的跳链顶掉,芝麻窗口翻转(加 rel=6 后 j2 状态数反而从 1572 暴跌到 80 的根因)。`push()` 里按 canStand 门控把 fy 吸附到 32 倍数(真实游戏重力下一帧即按到底)。
- 状态桶 3px:6px 桶会把"贴右腰 1~3px"的起拍/落点窗量化吞掉(j2 连续三段因此误判不可达)。
- 其余:`check.mjs` 格式校验、`save-test.mjs` 存档点、`key-test.mjs` 按键语义(R=回存档点 / Backspace=整关重来)、`smoke.mjs`、`dom-smoke.mjs`。
- 本地预览:`python3 -m http.server 8123`(ES module 必须走 HTTP,file:// 打不开)。
## 关卡现状(23 关)
跳刺 j1~j10(68×52 ~ 150×44,刺密度 3.8%~11.6%)、耐久 e1~e4、boss b1~b3(可达性求解跳过)、坑向 r1~r4。
## 移动端虚拟手柄(2026-10-22 新增)
- 触屏自动检测:`matchMedia('(pointer: coarse)')` / `ontouchstart` / `maxTouchPoints>0`;`?touch=1` 强制开、`?touch=0` 关(桌面预览用)。
- `js/input.js` 的 `touch` 状态层 + `setTouch/clearTouch` 与键盘共用 action 模型(`now/prev`),不破坏可达性工具。
- `js/touch.js`:左下 `◀▶` 移动、右下大「跳」+ 🔫射击(按住连发,受 `hasGun`/冷却限制)+ ▼下蹲 + 暂停/重生小键;pointer 事件支持多指,`hide()` 会 `clearTouch()` 防卡键。
- 仅在 `playing` 态显示(`startLevel`/`resume` 显示,菜单/暂停/结算隐藏);HUD 每帧 `Touch.setGun(P.hasGun)` 控制射击键置灰。
- 若改键位含义,记得同步 `js/input.js` 的 `BIND` 与 `touch.js` 的 `HELD`/`api` 映射。