Commit Graph

4 Commits

Author SHA1 Message Date
troytt ae94755401 M6:舍弃 map-scene.ts,收敛到 act-scene.ts 单轨
真地图上一直没有玩法,原因不是「两轨没合流」——act-scene.ts 早就通过 GameEngine
接入了战斗、物品、技能、任务、存档。真正的原因只有一行:act-scene.ts 给引擎传的是
`stats: []`,而 combat.ts 的 spawnMonsters 在 stats 为空时直接 return 0。

同时 map-scene.ts 被查出三个确凿缺陷,不值得保留:
1. 它建了一个从不被 tick 的孤儿 world,`let player = world.player` 之后再没用过
2. 怪物动画取帧读的是那个孤儿 world 的 tick,而 world.tick 只在 tickCombat 里自增,
   map-scene 从不直接调它,于是 tick 恒为 0,每只怪只显示一个固定帧——追击和攻击时
   怪物是静止的
3. 存读档绕过引擎,在 tick 之外直接读写 localStorage
外加 12 个死 import。

本次改动:

碰撞语义从布尔改为计数(src/game/iso-terrain.ts)
  combat.ts 的脱困规则是 `if (overlap(next) <= current) 才走`。原来 act-scene 传的是
  `collides(...) ? 1 : 0`,在墙里读到的永远是 1,`1 <= 1` 恒真,于是角色在墙里随机
  游走。改成计数之后同一条规则变成了梯度下降:陷进去 5 格深可以走到 3 格深,朝 7 格
  深的方向会被拒绝。
  不能直接复用 map.ts 的 blockedOverlap:那个函数遍历一个矩形的格索引区间,只有在
  sub-tile 是屏幕矩形时才成立;等距下 sub-tile 是 16×8 的菱形,屏幕矩形映射过去是
  错切且交错的,没有矩形索引区间可遍历。所以这里改为在包围盒上采样一个固定点阵,
  逐点问 isBlockedAt。点阵固定而非自适应,保证计数是位置的纯函数——存档与 lockstep
  都依赖这一点。

引擎硬编码下沉为选项(src/game/engine.ts)
  monsterCount(原写死 8)、monsterSpread(原写死 260)、npcRingFallback(NPC 合成
  圆环,act-scene 本来就用真实 DS1 坐标覆盖掉了,现在干脆不构造)。

补上一个死接口:EngineInput.saving / loading
  这两个字段声明了但 tick() 从不读,也就是说 act-scene 的 K/L 其实什么也不做。
  现在引擎新增 SnapshotStore 端口(read/write),在 tick 末尾按上升沿处理:
  存档点永远是一个已结算的 tick;按住键不会每 tick 存一次;localStorage 仍留在场景
  层,模拟层不碰宿主环境。配额满、隐私窗口、旧格式存档都记进 metrics.saveError。

删除 map-scene.ts 与 map.html
  map-save-load.js 这个无头检查改写为纯逻辑的 tests/engine-save-load.test.ts:
  同样的断言(走位砍怪 → 存档 → 继续跑 → 读档,比对位置/击杀/金币/地面/等级),
  但不需要 headless Chromium 和 WebGL 上下文,而且顺带覆盖了浏览器版从未走到的
  in-tick 存读档路径。verify-m4 / verify-m5 只依赖 src/game 与 src/net,原样保留。

顺带修了 inspect-page.mjs 的一个目标选择缺陷:它按「第一个 type 为 page 的目标」
附着,而 Chrome 在打开命令行 URL 之前会先建一个空白标签页,轮询往往抢到空白页,
之后的断言就报「没有场景状态」——和页面启动失败完全无法区分。改为等待 URL 匹配的
目标。(注:当前沙盒里 headless Chrome 完全没有网络,浏览器实测仍待补,已在
scripts/browser/README.md 记录判定方法。)

验证:
  npx tsc --noEmit    0 错误
  npm test            461 passed / 2 skipped(基线 442,新增 14,无回归)
  npm run verify:all  全绿
  npm run build       4 个入口,1.89s

Fixes #21

TAG=agy
CONV=c89513df-cd12-4749-b5bd-5e9f16639391
2026-09-15 07:40:56 +00:00
troytt b28ce37e8a docs(roadmap): correct M6 — discard map-scene.ts instead of merging
原诊断断言 act-scene.ts「无战斗/无物品/无技能/无存档」,据此把 M6 定为
「两轨合流、抽出第三个共享层」。逐行核查后发现这个断言是错的。

act-scene.ts:777 就实例化了 GameEngine,820 行 tick 它,962/968/978/984 行
分别绘制怪物、NPC、投射物、地面掉落,826-827 行的存读档也是经引擎走的。
之前 grep 不到是因为查的是 tickCombat/spawnMonsters 的直接导入,而
act-scene 是通过 GameEngine 间接获得这些能力的。

真相是 GameEngine(engine.ts,350 行)本身就已经是地图无关的共享层——
它只依赖 WorldMapProvider { widthPx, heightPx, overlap() }。原计划要新建的
src/scene/shared/gameplay.ts 不需要了。

「真地图上没有怪物」的全部原因是 act-scene.ts:785 的 stats: [],
因为 combat.ts:376 是 if (stats.length === 0) return 0。

反过来 map-scene.ts 查出三个确凿缺陷:

1. 孤儿世界:399-403 行 createWorld + spawnMonsters 建的世界从未被 tick,
   453 行的 GameEngine 又建了自己的世界;let player = world.player 是死变量。
2. 怪物动画冻结(真实 bug):572 行读孤儿世界的 world.tick,而 world.tick += 1
   只在 combat.ts:518 的 tickCombat 里发生,map-scene 从不直接调它。world.tick
   恒为 0,表达式退化成每只怪一个固定帧,追击/攻击时动画静止。
3. 存读档绕过引擎:488-489 行传 saving:false/loading:false,然后在 492-511 行
   于引擎外部读写 localStorage。在 tick 外做存读档对确定性是隐患。

另有 12 个死导入(tickCombat/rebindPlayer/damageMonster/castSkill/
tickProjectiles/QuestLog/npcDialog/captureSnapshot/parseSnapshot/
restoreSnapshot/serializeSnapshot/Rng),是逻辑搬进 GameEngine 后未清理的外壳。

因此 M6 改为:舍弃 map-scene.ts 与 map.html,收敛到 act-scene.ts 单轨。
范围收窄为三件事——打通 stats、修复 overlap 布尔化导致的脱困梯度丢失、
删除 map-scene 并把夹具回归检查改为直接驱动 GameEngine。风险由「高」下调为「中」。

TAG=agy
CONV=c89513df-cd12-4749-b5bd-5e9f16639391
2026-09-15 06:52:29 +00:00
troytt b9671b015f docs(roadmap): link milestones to tracking issues #21-#29
TAG=agy
CONV=c89513df-cd12-4749-b5bd-5e9f16639391
2026-09-15 06:36:31 +00:00
troytt de384f0744 docs: replace HANDOVER.md with ROADMAP.md (M6-M14)
HANDOVER.md 有三处已经过期:§4f 说 101 个生成关卡未烘焙(现已全部烘焙,
788/788 断言)、§7A-4 说 DCC/COF 未实现(现已实现,法师 16 层 8 方向)、
§7A 说 ADPCM+Huffman 音频未做(现已完成,11 项测试通过)。

新的 ROADMAP.md 分两部分:

第一部分是路线图。核心发现是一个此前没被点破的架构断层——acts.html
有真实等距地图和真实角色但零玩法,map.html 有完整玩法栈但跑在夹具地图
和 demo-data.ts 的 3 怪 / 3 物 / 2 技能上,两条轨道从未合流。因此把
「两轨合流」定为 M6,它是 M7-M14 全部里程碑的前提。后续依次为:真实怪物、
世界连通、真实战斗数学、真实物品系统、七职业与技能树、完整 UI/HUD、
.d2s 存档互通、可破坏物件美术与剧情。

第二部分保留 HANDOVER 中仍然有效的工程知识:23 条引擎铁律(模拟纯函数、
瓦片匹配键用 DT1 +20 的 Type 而非 +0 的 Direction、地面槽位只能画 type 0、
LvlPrest 用 LevelId 关联等)、差分验证方法论、验证命令清单、环境依赖、
性能基线与发布流程、目录结构。

README.md 同步更新:文档入口改指 ROADMAP.md,关卡数从「35 个预置」更正为
「365 张(含 70 迷宫 + 31 野外)」,「尚未实现」段落重写为真实缺口。

TAG=agy
CONV=c89513df-cd12-4749-b5bd-5e9f16639391
2026-09-15 06:32:59 +00:00