Commit Graph

104 Commits

Author SHA1 Message Date
troytt 74d2d1e36f test(wilderness): reconcile caravan and avenue floor style check with act topography passes 2026-09-17 09:02:05 +00:00
troytt dfce2f6cef Merge branch 'fix/issue-55' 2026-09-17 08:58:45 +00:00
troytt a6ab21881d Merge branch 'fix/issue-54'
# Conflicts:
#	src/game/wilderness.ts
2026-09-17 08:58:22 +00:00
troytt 2de9835b34 Merge branch 'fix/issue-53'
# Conflicts:
#	src/game/wilderness.ts
2026-09-17 08:56:41 +00:00
troytt 3ab024d12f Merge branch 'fix/issue-52' 2026-09-17 08:51:58 +00:00
troytt 334b957ab6 Merge branch 'fix/issue-51'
# Conflicts:
#	src/game/wilderness.ts
2026-09-17 08:51:55 +00:00
troytt 4f8e077bbb Merge branch 'fix/issue-50'
# Conflicts:
#	src/game/maze.ts
2026-09-17 08:50:47 +00:00
troytt 4c584db273 Merge branch 'fix/issue-49'
# Conflicts:
#	src/game/maze.ts
2026-09-17 08:49:32 +00:00
troytt 893e9b8550 Merge branch 'fix/issue-48'
# Conflicts:
#	src/game/maze.ts
2026-09-17 08:41:40 +00:00
troytt e1163b1253 Merge branch 'fix/issue-47'
# Conflicts:
#	src/game/maze.ts
2026-09-17 08:40:55 +00:00
troytt c310119ed2 Merge branch 'fix/issue-46' 2026-09-17 08:40:20 +00:00
troytt 80b2fd0f71 Merge branch 'fix/issue-45' 2026-09-17 08:40:15 +00:00
troytt c83499c591 Merge branch 'fix/issue-44'
# Conflicts:
#	src/scene/act-scene.ts
2026-09-17 08:40:10 +00:00
troytt 9af2c07c38 Merge branch 'fix/issue-43' 2026-09-17 08:39:26 +00:00
troytt 2fe5f4d892 feat(map-gen/wilderness): dynamic siege trench and fortification generation for Bloody Foothills (fixes #54) 2026-09-17 07:30:54 +00:00
troytt 3f463ba777 feat(map-gen/wilderness): extend road and stone divider networks to Acts 2, 3, and 5 (fixes #55) 2026-09-17 07:28:07 +00:00
troytt 81d2172cca feat(map-gen/maze): implement 11 official dungeon special passes (fixes #49) 2026-09-17 07:27:47 +00:00
troytt 723f5dce27 feat(map-gen/wilderness): implement Act 3 jungle river network and branch topology (fixes #53) 2026-09-17 07:25:07 +00:00
troytt 8baf91c51f feat(map-gen/wilderness): implement outdoor level initializers for acts 2-5 (fixes #51) 2026-09-17 07:11:19 +00:00
troytt ffc2b297f6 feat(map-gen/maze): implement dynamic dungeon object spawning pipeline (fixes #50) 2026-09-17 07:08:47 +00:00
troytt 12588ea948 feat(map-gen/wilderness): support multi-DT1 weighted ground tile blending (fixes #52) 2026-09-17 07:08:24 +00:00
troytt 801afd7ddb fix(map-gen/maze): enforce Levels.txt global bounds SizeX/SizeY on room growth (fixes #48) 2026-09-17 06:59:31 +00:00
troytt c56b808d7a feat(renderer/map): support animated tiles loop playback (fixes #43) 2026-09-17 06:34:02 +00:00
troytt 776e2d3c20 feat(map-gen/maze): integrate waypoint room into maze preset replacement pass (fixes #47) 2026-09-17 06:33:16 +00:00
troytt 8bb99ded7e refactor(map-gen/maze): align special room placement with D2 scanning rules (fixes #46) 2026-09-17 06:20:59 +00:00
troytt 72aa835260 refactor(map-gen/preset): move Chaos Sanctuary (Level 108) into preset pipeline (fixes #45) 2026-09-17 06:20:35 +00:00
troytt 57c13bdc26 feat(renderer/roofs): implement dynamic roof fade when player enters shelter (fixes #44) 2026-09-17 06:19:01 +00:00
troytt a01cffa0b5 refactor(map-gen/preset): fix variant selection and seam orientation alignment (fixes #42) 2026-09-17 06:18:42 +00:00
troytt 1fa3f8b7a2 chore(git): ignore node_modules symlinks in worktrees 2026-09-17 05:50:28 +00:00
troytt 87a575f4e8 chore(tooling): add git worktree helper script for multi-session development 2026-09-17 05:49:58 +00:00
troytt 80c74a6b95 test(cull): add unit test for bounds rebuild on level switch 2026-09-17 05:48:51 +00:00
troytt 4ea77e2c00 fix(scene): handle level transition entity cleanup, art reloading & wilderness floor gaps 2026-09-17 05:48:38 +00:00
troytt e9f2ceec9b feat(map-gen/population): implement wilderness population phase, waypoints, shrines, superuniques & safe zones (fixes #36) 2026-09-17 03:44:12 +00:00
troytt 93a854d9ad feat(combat): implement Diablo II 1.10+ combat math & PvE depth mechanics (fixes #24, fixes #39) 2026-09-17 02:52:00 +00:00
troytt 78b5351cce feat(maze): 拓扑 BFS 房间选择与 LvlPrest 楼梯预制体替换,消除迷宫兜底楼梯 (fixes #37)
基于 Scheme C 实现拓扑 BFS 死胡同/房间选择与 LvlPrest.txt 全间楼梯预制体替换,
彻底补齐全五幕 12 类迷宫的完整上下行楼梯配置,将迷宫兜底楼梯降为 0。

1. 词法分类器扩展 (classifyMazePieceName)
   - 在 KIND_PREFIXES 中新增 Pitspawn / Bonebreak (quest)、Cath (down)、
     Drain (next)、Warp (prev) 关键词映射。
   - 增加对反转词序语法 (/^([NSEW]+)\s+(up|down)$/i) 的支持,正确识别
     Act 5 - Temple SE up 与 NE/NW/SW Down。
   - 在 buildPieceIndex 中引入 prev 预制体兜底,解决尼拉塞克神殿东南角无普通
     房间预制体的问题。

2. 迷宫楼梯表补齐 (MAZE_LEVEL_TYPE_PROFILES)
   - 补齐此前 specials: [] 为空的全部 12 类迷宫上下行楼梯规则 (涵盖 Barracks,
     Jail, Sewer, Tomb, Lair, Kurast, Dungeon, Kurast Sewer, Lava, Ice Caves,
     Temple, Baal)。
   - 库拉斯特下水道一层按双地面入口拆分为 NW/NE 与 SW/SE 两组独立规则。
   - 王宫监牢一、二层限制变体为 0..2,排除专为第三层设计的无楼梯 File4。

3. 拓扑 BFS 房间选择与死胡同向外生长 (bfsDistances & placeSpecialGroup)
   - 从迷宫起点出发计算 BFS 拓扑距离:
     * 原地替换 (Phase 1):下行/任务房间优先选择 BFS 距离最远的死胡同或普通房间,
       上行入口优先选择离起点最近的房间。
     * 死胡同生长 (Phase 2):无匹配边掩码房间时,从最优候选父节点向外生长死胡同
       房间并重选父节点开门预制体。

4. 验证效果
   - 全量 365 张地图中,70 个迷宫关卡(210 张迷宫地图)的 fallback 楼梯数
     从 212 处彻底归零 (100% 升级为 DS1 Warp0..7 真实美术标记瓦片)。
   - 全量真实美术瓦片总数从 218 跃升至 430 (+212)。
2026-09-16 23:39:33 +00:00
troytt 29cfccaa37 feat(world): 全局世界连通、关卡无缝切换、传送点、传送门与小地图 (fixes #23)
实现《暗黑破坏神 II》全五幕(136 个关卡)的完整世界连通拓扑、烘焙期链接落位、
运行时无缝边界与楼梯渐变过渡、传送点网络、城镇传送门以及实时小地图。

1. 全局世界连通拓扑 (src/game/world-graph.ts)
   - 融合 Levels.txt (Vis0..7, Warp0..7)、28 对野外无缝相邻接缝 (SEAMLESS_ADJACENCY)、
     23 对双向传送门链接 (PORTAL_LINKS) 以及 LvlWarp.txt (88 行几何偏移)
     构建 136 关卡、288 条有向边连通图。
   - 实现确定性边界方向分配器 (assignGateSides),保证同一关卡的多条边不会冲突。

2. 楼梯瓦片识别与几何计算 (src/game/warp-tiles.ts, src/game/level-links.ts)
   - findWarpTiles: 扫描 DS1 墙体层 (type 10/11) 提取 style 0..7 对应 Warp0..7 瓦片。
   - level-links: 显式栈 4-连通分量洪水填充提取最大可行走主区域 (largestWalkableRegion),
     向内扫描边界缺口 (findBorderOpening),以及计算安全向内落脚点 (seamArrivalSpot)。

3. 野外道路与锚点导出 (src/game/wilderness.ts, src/game/acts.ts)
   - planAct1OutdoorLevel 导出城镇过渡口、野外边界门、洞穴入口与路边地标。
   - 实现泥土路网与 256 项 8 邻域位掩码地砖自动铺设。

4. 烘焙期流水线增强 (scripts/pack-act-assets.ts)
   - buildSceneLinks 5 级流水线:DS1 真实瓦片 -> 野外生成器锚点 -> 迷宫楼梯间 ->
     预设边界扫描与营地四朝向自适应 -> 兜底安全落脚点。
   - 全量重新烘焙 samples/d2-packs (365 张地图),输出 entrances, warps, waypoints
     与 world-graph.json。

5. 运行时交互与表现 (src/scene/act-scene.ts, src/scene/transition.ts, src/game/portal.ts, src/ui/minimap.ts)
   - 支持接触边界无缝切换 (90ms 淡入淡出) 与按 G 键 / 交互楼梯传送 (220ms 淡入淡出)。
   - swapLevel 保留玩家状态(位置、属性、背包、随从)并在对应配对出入口安全落地。
   - WaypointNetwork: 小站发现与跨图传送;TownPortalSlot: T 键回城与往返传送。
   - Minimap: Tab 键切换右上角小地图 / 全屏自动地图,绘制迷雾、玩家、出口、小站、回城门。

6. 验证护栏与测试
   - verify:world-graph: 40,003 项断言确认全 136 关卡全连通、边双向对称。
   - verify:world-walk: 模拟玩家徒步全图 338 张有出口地图,1046/1046 出入口 100% 互达。
   - 补齐 tests/world-graph.test.ts, tests/warp-tiles.test.ts, tests/wilderness-roads.test.ts。
2026-09-16 23:39:33 +00:00
troytt 1f2f19641f fix(map-gen): fix Act 1 road/stone wall connectivity, Blood Moor river+bridge layout & Act 3 jungle river alignment 2026-09-16 04:31:10 +00:00
troytt 602565030f fix(map-gen): fix Act 4 Chaos Sanctuary, Act 3 Jungles/Travincal/Kurast, Act 2 Deserts & Act 5 Frozen Tundra layouts 2026-09-16 03:30:40 +00:00
troytt 25ae7289ee fix(map-gen): assemble Act 5 Bloody Foothills 15 siege strips, Barricade fortifications & Act 1 stone walls/dirt roads (Issue #35) 2026-09-16 03:06:48 +00:00
troytt dcd3da0a22 feat(combat): disable monster aggro in act-scene so monsters stay idle in place 2026-09-16 02:21:17 +00:00
troytt d4fb176031 perf(renderer): implement Raw 8-bit GPU Index Texture (gl.R8) + Shader Hardware Palette Lookup (#34) 2026-09-16 02:10:34 +00:00
troytt 2527678c10 fix(packs): restore full Acts 1..5 index and map/bake all 741 monster IDs across all acts (#33) 2026-09-16 01:43:11 +00:00
troytt 270c743aef perf(entities): offline pre-bake Sorceress and monster atlases into Act packs (#30) 2026-09-16 01:16:38 +00:00
troytt d6e624829a fix(monster): fix loading performance, monster facing, and spawn logic (#30, #31, #32)
- Fix #30: Concurrent layer/animation decoding and shared MPQ archive caching to reduce load latency
- Fix #31: Use dir64ToDcc for permuted 8-direction DCC extraction in monster art compositing and align 8-way facing calculation
- Fix #32: Prevent monster spawning in town/safe zones, support hellbovine cow packs, and cluster packs properly
2026-09-15 15:03:28 +00:00
troytt 25ab2eea9d docs: 标记 M7 里程碑为已完成 2026-09-15 09:52:35 +00:00
troytt 9a0e91849e feat(monsters): 完成 M7 怪物 COF/DCC 8向动画与美术层接入
- 新增 src/game/monster-art.ts: 支持怪物 16 部位通道映射与多图层 COF/DCC 合成,支持 8 向行走 (wl) 与待机 (nu) 动画
- 新增 src/game/monster-mapping.ts: 建立怪物 ID 到美术 Token 与武器类型的映射规范
- 更新 src/scene/act-scene.ts: 怪物按类型按需异步加载美术图集,由红色占位色块升级为真·8向动态怪物体素
- 更新 scripts/verify-monsters.ts: 增加对血色荒野核心怪物 (FA, ZM, SI) 的动画解码断言 (74/74 checks passed)
- 新增 tests/monster-art.test.ts 与 tests/monster-mapping.test.ts (全部 528 个单元测试均通过)
2026-09-15 09:45:23 +00:00
troytt 4294a16a48 fix(monsters): 按 d2mods.info 官方规范修正 MonLvl 与 MonStats 等级/半径/稀有度逻辑
- 依据 KB 354: 读取 MonLvl.txt 单机专属 'L-' 列,确保单机模式数值准确
- 依据 KB 360: 实现两分支 monsterLevelFor 等级判定(普通难度与 Boss 维持自身等级,噩梦/地狱普通怪取区域等级)
- 依据 KB 360: aidist 为空或 0 时回退至默认 35 格 (560px 一屏距离)
- 依据 KB 360: 增加 sparsePopulate 稀有生成概率属性并在选怪时过滤
- 全量下载并整理 d2mods 官方 KB 120 篇技术文档至 docs/d2kb/ 并附索引
- 验证: 51/51 unit tests passed, 65/65 verify-monsters checks passed
2026-09-15 08:57:15 +00:00
taodao cc12e7bc66 M7 第二步:怪物接进关卡,并补上 MonLvl 缩放
Stage A 把 MonStats.txt 读了出来,接上去才发现读出来的数字不能用:
一只一级堕落者按表里的 A1MinD=51 / A1MaxD=101 算,一击 76 点伤害,
而玩家只有 60 点血。

MonStats.txt 里的数字不是游戏里的数字。真实公式是

  最终值 = MonStats[列] × MonLvl[怪物等级][对应列] ÷ 100

这不是猜的。同一个公式让五个互不相干的列同时对上游戏已知数值:
fallen1 的 Exp 61→18.3(游戏 18)、zombie1 的 111→33.3(33)、
quillrat1 的 71→21.3(21)、fallen1 的 AC 84→5.0(5)、
A1TH 101→8.1(8)。五发全中不是巧合,所以照做而不是推给 M9。

顺带纠正我在 Stage A 里写下的判断:MinHP(N)/MinHP(H) 不是噩梦和地狱
的血量,594 个有血量的行里有 426 行的 MaxHP(N) 比普通还低。难度靠的
是怪物自己的 Level(N)=36、Level(H)=67 再过一遍 MonLvl 的 HP(N)/HP(H),
这才是噩梦堕落者有 131-289 血而不是 25-55 血的原因。MonLvl 的 L- 前缀
列(扩展版那套)在 1.13c 里和原列逐字节相同,读哪套都一样。

boss 查过要不要豁免缩放,没有豁免:缩放后的 Diablo 是 13818,记忆里
是 13741;缩放后的 Andariel 是 1025,记忆里约 960;但 Duriel 常被引用
的 4757 是原值。凭这种强度的记忆去开特例就是猜了,幕 boss 单独处理。

接线:
- monsters.ts 新增 readMonsterScaling / monsterScaleFor / UNSCALED,
  monsterStatsOf 接受一个 MonsterScale。先掷骰再缩放——低等级的乘数
  只有百分之几,先缩放边界会把 21..61 压成 1..4 再四舍五入,一个范围
  里四个值有三个取不到。
- 新增 planLevelMonsters:从关卡 id 一路走到掷好点数的怪物群。在线
  路径和资源包烘焙都走它,「烘焙出来的关卡和在线的一致」因此是个性质
  而不是巧合。
- engine.ts 新增 monsterPacks 选项,优先于 stats + monsterCount。
- act-scene.ts 两条路径都接上;资源包路径读 scene.json 里新烘焙的
  monsters 字段,浏览器在这条路径上没有 Levels.txt 可读。
- pack-act-assets.ts 烘焙怪物,每张图 scene.json 增加约 4 KB。

血色荒野现在是:堕落者 2-4 血 / 2 伤 / 18 经验,僵尸 8-13 血 /
33 经验 / 移速 28(堕落者是 142),棘鼠 2-5 血 / 21 经验。51 只怪
分 30 群。

验证:tsc 零错误;npm test 523 通过(基线 461);
npm run verify:monsters 65/65;npm run verify:all 通过;
npm run verify:packs 1302/1302 逐像素断言通过。

TAG=agy
CONV=c89513df-cd12-4749-b5bd-5e9f16639391
2026-09-15 08:22:48 +00:00
taodao 70b299569e M7 第一步:真实怪物数据层
把 MonStats.txt 读进来,并给它一个能发现错误的检验。

新增 src/game/monsters.ts。它和 combat.ts 里的 monsterStatsFromRow
是两个读取器,不是一个:后者读的是九列的测试夹具(Id/Name/HP/...),
真实的 MonStats.txt 有 255 列且几乎没有一个同名——是 NameStr 不是
Name,是 minHP/maxHP 不是 HP,是 Exp 不是 XP。把夹具读取器指向真表
不会报错,只会让 735 只怪物拿到同一套默认值,看起来像是能用。

新增 scripts/verify-monsters.ts(52 项检查)与 tests/monsters.test.ts
(41 项)。检验脚本一上来就发现了七处问题,其中两处是数据本身的事实,
值得记下来:

- MinHP(N)/MinHP(H) 不是「更高难度的血量」。fallen1 普通 21-61,
  噩梦和地狱都是 25-55;594 个有血量的行里有 426 行的 MaxHP(N) 比
  maxHP 更低;Duriel 从普通的 4757 掉到地狱的 1295。真正的难度缩放
  来自 MonLvl.txt 对普通值的逐等级放大,这些列是只对 boss 生效的覆盖,
  而且数据自己用得并不一致。按铁律六,读取器照实读、照实记,不假装
  它们是缩放,缩放留给 M9。
- MonStats2.txt 只有 610 行,配 735 只怪物。缺的 119 只是编号变体
  (quillrat6、slinger7、deathmauler6 等),游戏用它们 BaseId 的美术
  来画。只按 id 查会让这 119 只变成一片空地而不是一个报错,所以
  readMonsterArt 走 BaseId 回退。

另外三处是数据里的既有瑕疵,现在被显式承认而不是被抹平:
MonStats.txt 里 cr_lancer8 重复出现两次(检验断言「唯一的重复是这一个」,
新的重复仍会失败);SuperUniques.txt 里的 Expansion 是分节行,有名字
没有 Class;还有两处是我手抄表格时抄错了(foulcrow1 的 MonType 是
foulcrow 不是 bat,hellbovine 的 NameStr 带空格)。

combat.ts 新增 spawnMonsterPacks。原有的 spawnMonsters 把怪物撒在黄金角
螺旋上,那是让互不相干的东西均匀散开的形状,恰好是堕落者营地最不该有的
形状——螺旋上相邻的两点是刻意离得远的。现在螺旋放的是营地,成员在自己
营地里成团。附 tests/monster-packs.test.ts(11 项),直接量距离而不是
看截图。

acts.ts 的 ActTables 增加 MonStats2/MonLvl/MonType/MonUMod/SuperUniques。

验证:tsc 零错误;npm test 502 通过(基线 461);
npm run verify:monsters 52/52。

TAG=agy
CONV=c89513df-cd12-4749-b5bd-5e9f16639391
2026-09-15 08:06:52 +00:00
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