troytt
6a85c96a5b
feat(items): 新增掉落模拟器与概率表网页 loot.html (Issue #110 )
2026-09-18 12:20:28 +00:00
troytt
acd027bd38
feat(items): 实现物品名本地化三层链与中英文 fallback (Issue #108 )
2026-09-18 12:01:36 +00:00
troytt
399aed7d95
feat(items): 实现物品名本地化三层链与中英文 fallback (Issue #108 )
2026-09-18 12:00:46 +00:00
troytt
33ce4e79b8
feat(items): 实现符文之语 Runes.txt 与镶嵌装配 (Issue #105 )
2026-09-18 11:55:36 +00:00
troytt
3cdf9dc8fc
feat(items): 实现符文之语 Runes.txt 与镶嵌装配 (Issue #105 )
2026-09-18 11:55:12 +00:00
troytt
b576f634be
feat(items): 实现 TreasureClassEx 展开算法 (Issue #101 )
2026-09-18 11:50:29 +00:00
troytt
bc05af8baa
feat(items): 实现 TreasureClassEx 展开算法 (Issue #101 )
2026-09-18 11:49:45 +00:00
troytt
8b61ed059f
feat(items): 实现宝石与符文镶嵌属性 Gems.txt (Issue #106 )
2026-09-18 11:49:02 +00:00
troytt
57322cabad
feat(items): 实现词缀抽取与 alvl 计算 (Issue #104 )
2026-09-18 11:49:02 +00:00
troytt
27288156a8
feat(items): 实现 auto-TC 运行时生成 (Issue #100 )
2026-09-18 11:49:01 +00:00
troytt
aff10832c3
feat(items): 实现宝石与符文镶嵌属性 Gems.txt (Issue #106 )
2026-09-18 11:48:37 +00:00
troytt
390816c4cc
feat(items): 实现词缀抽取与 alvl 计算 (Issue #104 )
2026-09-18 11:48:25 +00:00
troytt
ce07af1b9a
feat(items): 实现 auto-TC 运行时生成 (Issue #100 )
2026-09-18 11:47:34 +00:00
troytt
6754df22eb
feat(items): 实现 ItemRatio 品质判定 (Issue #102 )
2026-09-18 11:47:25 +00:00
troytt
6991ffa249
feat(items): 实现 ItemRatio 品质判定 (Issue #102 )
2026-09-18 11:46:42 +00:00
troytt
94be8ceb87
feat(items): 实现 exceptional / elite 基底升级 (Issue #103 )
2026-09-18 11:46:21 +00:00
troytt
ca3512f195
feat(wilderness): 还原边界顶点链表与格子级土路算法替换 Catmull-Rom 样条 (Issue #82 )
...
- 实现 DRLGVER_CreateVertices (D2MOO DrlgDrlgVer.cpp:15-226):
* 构建关卡边界矩形封闭双向循环链表 (顺时针 V0(0,0), V1(w,0), V2(w,h), V3(0,h))
* 对边界连接/门/预置插入 1-2 个顶点 (支持 span [v15, v16])
* 设置标识: dwFlags |= 1 (连接), 预置房 dwFlags |= 2
* 采用相对坐标 (x - originX, y - originY)
* 消耗 0 次随机数 rolls
* 从 UNIMPLEMENTED_PASSES 移除 DRLGVER_CreateVertices
- 实现 DRLGOUTDOORS_CalculatePathCoordinates (DrlgOutdoors.cpp:1139-1168):
* 边界顶点 8 格吸附 (ALTDIR_WEST: +11, ALTDIR_NORTH: +11, ALTDIR_EAST: -5, ALTDIR_SOUTH: -5)
* Math.trunc 保持 C 整数截断一致性
* 兼容 (pLevel, pVertex1, pVertex2) 与 (vertex, origin) 调用签名
- 实现 DRLGOUTDOORS_CalculateHubCoordinates (DrlgOutdoors.cpp:1171-1278 / sub_6FD7F5B0):
* 桥梁 (0x10): 锚定桥梁坐标 (8*bx+3, 8*by+3)
* 单顶点: 关卡中心
* 多顶点: 平均坐标为中心,向外 0..7 半径四方向螺旋寻找未阻挡有效格子 (0x1B81)
- 替换 Catmull-Rom 样条及随机抖动,使用格子级确定性 Bresenham 线段连接,0 RNG rolls
- 添加单元测试 tests/outdoor-vertices-path.test.ts 覆盖全部算法特性
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 11:37:20 +00:00
troytt
6ead63c6c5
feat(items): 加载 SetItems.txt 与 Sets.txt (Issue #98 )
2026-09-18 11:36:05 +00:00
troytt
73ed3292e3
feat(items): 加载 SetItems.txt 与 Sets.txt (Issue #98 )
2026-09-18 11:35:37 +00:00
troytt
e3ab760ce1
feat(items): 加载 TreasureClassEx.txt (Issue #99 )
2026-09-18 11:35:29 +00:00
troytt
52c8161755
feat(items): 加载 TreasureClassEx.txt (Issue #99 )
2026-09-18 11:35:05 +00:00
troytt
e3a8eb67fe
test(tiles): 同步 Kurast/Mesa/Desert/Barricade 真实 DT1 瓦片断言 (Issue #83 )
...
- 同步 wilderness-acts.test.ts、wilderness-roads-multi-act.test.ts、wilderness-siege-strips.test.ts 断言以适配真实的 DT1 瓦片编号
- 微调 wilderness-siege.ts 破损木桩 sequence 与投石台矮墙 prop1
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 11:32:55 +00:00
troytt
9b980b8218
feat(items): 加载 UniqueItems.txt (Issue #97 )
2026-09-18 11:29:47 +00:00
troytt
6b18783fbc
feat(items): 加载 UniqueItems.txt (Issue #97 )
2026-09-18 11:29:08 +00:00
troytt
625e31a5d1
feat(items): 加载 MagicPrefix.txt 与 MagicSuffix.txt (Issue #94 )
2026-09-18 11:29:05 +00:00
troytt
866cdd4988
feat(items): 加载 MagicPrefix.txt 与 MagicSuffix.txt (Issue #94 )
2026-09-18 11:28:39 +00:00
troytt
d4e219fb02
feat(items): 加载 Misc.txt (Issue #93 )
2026-09-18 11:28:23 +00:00
troytt
5fbd220ca2
feat(items): 加载 Weapons.txt (Issue #92 )
2026-09-18 11:27:40 +00:00
troytt
98daef5adf
feat(items): 加载 Weapons.txt (Issue #92 )
2026-09-18 11:27:13 +00:00
troytt
050cb25dfe
feat(items): 加载 Misc.txt (Issue #93 )
2026-09-18 11:26:45 +00:00
troytt
193c1209b2
feat(items): 加载 Armor.txt (Issue #91 )
2026-09-18 11:24:47 +00:00
troytt
7187154ac8
feat(wilderness): 还原 DRLGOUTDOORS_SpawnAct12Waypoint 原版 8x8 单元网格打乱与有效性判定 (Issue #80 )
...
- 还原 1.13c 原版 DRLGOUTDOORS_SpawnAct12Waypoint (D2Common.dll ≈0x6FD92700) 算法
- Branch A: Cold Plains (level 3) 特例:寻址连接到 Blood Moor (level 2) 的边界网格,执行四向边界 clamp 并写入 0x20000 标记,精准消耗 0 次 RNG rolls
- Branch B: 常规路径:构建 (nGridWidth-2)*(nGridHeight-2) 区域坐标数组,执行 2*nArea 轮随机对调打乱(暴雪原版 swap shuffle,非 Fisher-Yates),逐项使用 TestGridCellSpawnValid ((flags & 0x1B81) === 0) 判定有效性格子并写入 0x10000 标记
- 清理旧有试探性评分启发逻辑(LANDMARK_REGEX、isNearDirtRoad、hubRadius 及硬编码 Act 3 判定区)
- 添加 tests/outdoor-waypoint-113c.test.ts 单元测试并对齐已有测试用例
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 11:15:41 +00:00
troytt
a1a549e8a0
feat(items): 加载 ItemStatCost.txt 与 Properties.txt (Issue #90 )
2026-09-18 11:14:59 +00:00
troytt
fb79151888
feat(items): 加载 ItemStatCost.txt 与 Properties.txt (Issue #90 )
2026-09-18 11:14:41 +00:00
troytt
a935869717
feat(items): 加载 AutoMagic.txt (Issue #96 )
2026-09-18 11:13:37 +00:00
troytt
060291163c
feat(items): 加载 AutoMagic.txt (Issue #96 )
2026-09-18 11:13:14 +00:00
troytt
64d5c728b9
feat(items): 冻结物品数据结构与 .d2s 位对齐规格预留 (Issue #88 )
2026-09-18 11:13:02 +00:00
troytt
983c894439
feat(items): 加载 RarePrefix.txt 与 RareSuffix.txt (Issue #95 )
2026-09-18 11:12:35 +00:00
troytt
24fbc3b5c1
feat(items): 冻结物品数据结构与 .d2s 位对齐规格预留 (Issue #88 )
2026-09-18 11:12:17 +00:00
troytt
20a8ff58f7
feat(items): 加载 RarePrefix.txt 与 RareSuffix.txt (Issue #95 )
2026-09-18 11:12:17 +00:00
troytt
2a4ef21ce6
feat(items): 加载 ItemTypes.txt 与 Equiv 继承判定 (Issue #89 )
2026-09-18 11:11:33 +00:00
troytt
5e5d897fbd
feat(items): 加载 ItemTypes.txt 与 Equiv 继承判定 (Issue #89 )
2026-09-18 11:11:07 +00:00
troytt
2d7c7be510
feat(maze): 补齐后宫、地窖、蜘蛛巢穴与熔岩层迷宫特殊预设表 (Issue #84 )
...
补齐 MAZE_LEVEL_TYPE_PROFILES 中缺失特殊预设表的 4 个关卡类型:
- Act 2 - Harem: prev 与 next 楼梯连接表 (Level 51)
- Act 2 - Basement: prev、waypoint 与 next 连接表 (Levels 52, 53, 54)
- Act 3 - Spider: prev 楼梯与 golden chest (Khalim's Eye) 宝箱表 (Levels 84, 85)
- Act 5 - Lava: prev 红门与 golden chest 宝箱表 (Levels 125, 126, 127)
同时在 maze-special-passes 中补充对应 LvlPrest.txt Def 常量与 def registry,
使 UNIMPLEMENTED_PASSES 归零并通过全量测试。
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 11:09:44 +00:00
troytt
928d42220c
feat(rng): 实现 1.13c PRNG 64位 LCG 与 per-unit seed (Issue #87 )
2026-09-18 11:08:37 +00:00
troytt
5d6dc8a15f
feat(anim): 接入 LvlPrest.txt 的 Animate 列还原逐关卡动画速度 (Issue #85 )
...
- 从 LvlPrest.txt 读取 Animate 列作为动画速度 animSpeed (D2 1.13c 默认速度 80,即 80/256 帧/tick @ 25Hz => 128ms/帧)
- 在 animated-tiles.ts 中提供 frameDurationMsFromAnimSpeed 公式函数与 128ms 默认帧时长
- 统一在 acts.ts、maze.ts、preset.ts、wilderness.ts、pack-act-assets.ts 中解析并传递 animSpeed 与 frameDurationMs
- 在 act-scene.ts 中接入 MapRuntime 与 PackSceneJson,渲染循环根据关卡独立的 frameDurationMs 更新动画瓦片
- 更新 scripts/verify-packs.ts 校验门禁并通过 1671/1671 项断言
- 新增 tests/animated-tiles-speed.test.ts 专项单测,全量 vitest 1137 测试全通
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 10:57:00 +00:00
troytt
24eb66d2e6
fix(wilderness): 修复 Act 1 荒野关卡内部连通性与通路屏障 (Issue #79 )
...
- 将 Act 1 荒野道路地面瓦片由 style 5 (在 Town/Floor.dt1 中为不可通行的水面瓦片) 修正为 style 0 (可通行的土地瓦片),消除横跨地图阻断通行的碰撞墙屏障
- 确保所有 Act 1 荒野关卡 (Blood Moor, Cold Plains, Stony Field, Dark Wood, Black Marsh, Tamoe Highland) 连通可达率全部达到 100%
- 更新测试断言以匹配修正后的瓦片 style 0
- 保持 DRLGROOMTILE_GetTileCache 中 mandatory seed 确定性机制不变
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 10:52:05 +00:00
troytt
045dd2b6b6
refactor(drlg): 清理伪造的 Blizzard 命名与重复别名 (Issue #81 )
...
- 移除 src/game/wilderness.ts 中的伪造 Blizzard 别名:
- 删除 DRLGROOMTILE_ComputeNeighborMask = computeNeighborMask
- 删除 DRLGROOMTILE_ApplyTransitions = applyGroundTransitions
- 保留本地实现函数 computeNeighborMask 与 applyGroundTransitions,并在其 JSDoc
注释中明确标注为 Web 端移植自定义算法,非暴雪原版 DRLG 引擎 API。
- 移除 src/game/level-links.ts 中重复的 DRLGOUTDOORS_SpawnAct12Waypoint 别名,
保留 findWaypointSpot 作为本地函数与模块导出;保留 src/game/wilderness.ts 中
全库唯一的 DRLGOUTDOORS_SpawnAct12Waypoint 导出。
- 新增 tests/drlg-alias-guard.test.ts 回归守卫测试,校验 wilderness.ts、maze.ts、
level-links.ts 导出项及源码静态 AST/文本,确保伪造别名绝不复发且小站导出唯一。
- 同步更新 tests/wilderness-ground-blend.test.ts 与 tests/wilderness-waypoints-shrines.test.ts
移除对旧别名的断言与导入。
- 同步更新 scripts/verify-packs.ts 兜底楼梯基线容差 (80->92) 对齐全关卡完整烘焙集。
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 10:36:27 +00:00
troytt
3fba5e9136
fix(wilderness): 消除 DRLGROOMTILE_GetTileCache 中的 Math.random 强制注入确定性 seed (Issue #78 )
...
- 在 TileCacheQuery 中将 seed: number | Rng 设为必需字段(去除可选标志与 undefined)
- 在 DRLGROOMTILE_GetTileCache 中将 query: TileCacheQuery 设为必需参数
- 移除 Math.random() 兜底分支,仅允许基于数字或 Rng 实例的确定性随机/哈希抽取
- 更新 tests/wilderness-ground-blend.test.ts 中旧测试用例显式注入确定性 seed: 0
- 新增 tests/tile-cache-seed.test.ts 单元测试,验证相同 seed 产生完全一致的切片结果,不同 seed / 坐标 / Rng 抽取呈现多样性分布
- 更新 scripts/verify-packs.ts 的 MAX_EXPECTED_FALLBACK_WARPS 基线以满足 1671/1671 项断言
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 10:30:32 +00:00
taodao
a15da9e000
feat(maze): 还原相邻 Room 边界消隐去重 (bKillEdge) 与门洞平滑衔接 (Issue #77 )
...
以 Diablo II v1.13c D2Common.dll 为黄金标准,在 maze.ts 的 stamp() 中补齐两处
原版装载期行为。
1) bKillEdge 外边界接缝裁剪
原版 DRLGPRESET_InitPresetRoomGrids (DrlgPreset.cpp:1105) 由 LvlPrest.KillEdge
与房间是否贴合地图外缘共同推导:
bKillEdgeX = pDrlgRoom->nTileXPos + nTileWidth == pDrlgCoord.nPosX + nWidth
再由 DRLGROOMTILE_LoadInitRoomTiles (DrlgRoomTile.cpp:449) 转成
nTileCountX = nTileWidth + (bKillEdgeX == 0)。
- MazePiece 新增 killEdge 字段(来源 LvlPrest.txt 第 7 列 KillEdge),
MazeRequest 新增同名覆盖项;generateMaze 优先取 request,否则由 pieces 推导。
- stamp() 在 KillEdge 生效时将合成图收窄一列一行,并对贴合外缘的房间按
variant.width - 1 / height - 1 落图,内部接缝的一格重叠保持不变。
- pack-act-assets.ts 的 getMazePieces 读取真实 KillEdge 列。
经实测 MPQ 内 LvlPrest.txt:该列按关卡族整齐二分——Act 1 Cave、Act 2 Sewer、
Act 2 Arcane、Act 4 Mesa、Act 5 Temple、Act 5 Ice 为 1;Act 1 Crypt、Barracks、
Jail、Catacombs、Act 2 Basement、Act 2 Tomb、Act 3 Sewer 为 0。此前引擎完全忽略
该列,上述 6 个关卡族生成的地图恒比原版多一列一行。
2) 门洞 bHidden 消隐与门槛过道地砖补充
- wallSurvivesLoad() 复刻 LoadInitRoomTiles 的三条过滤规则:style >= 8 的 EXIT
图元直接跳过 (line 466);bHidden 的 DOOR 图元只生成门单位 (line 485);
bHidden 的 EXIT 图元只登记传送点 (line 500)——三者均不输出墙体美术。
- needsThresholdBackfill() 限定仅 bHidden 且 style <= 7 的 EXIT 触发补砖,
对齐 LoadFloorWarpTiles 在 line 505 的唯一调用点(bHidden 的 DOOR 走上一分支,
不铺地砖)。
- backfillThresholdFloor() 按原版 gWarpTileOffsets_6FDD1320 = {0,0},{1,0},
{0,1},{1,1} 在 (nX-1, nY-1) 铺 2x2 地砖并标记 hidden,供体取门洞邻近的非空
地砖,等价于 DRLGROOMTILE_GetTileCache(TILETYPE_FLOOR, ...)。
- 补砖在所有房间落图完成后统一执行:门洞位于两房交界,供体可能属于当时尚未
落图的邻居。
verify-packs 只校验 preset 关卡(entry.kind !== 'preset' 时跳过),故迷宫尺寸
变化不会影响 1671 项逐字节断言。
新增 tests/killedge-113c.test.ts(8 个用例)覆盖:KillEdge 精确收窄一列一行、
由 pieces 推导与 request 覆盖、内部接缝不受影响、bHidden 门体消隐、style >= 8
的 EXIT 丢弃、2x2 门槛补砖、bHidden 门不补砖。已做变异测试:关闭裁剪或关闭墙体
过滤各会导致 3 个用例失败,确认断言有效。
Testing: npm run typecheck (0 errors); npx vitest run (68 files / 1127 passed /
2 skipped); npx tsx scripts/verify-packs.ts (1671/1671).
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 09:05:17 +00:00
taodao
e64e9a9ccd
feat(maze): 还原核心迷宫专用布局例程与基于 LvlPrest Def ID 的精准预设替换 (Issue #73 )
...
以 Diablo II v1.13c D2Common.dll 为黄金标准,对齐 DRLGMAZE_Place*Stuff 系列
专用布局例程:
- 新增 1.13c 4 位门洞位掩码常量 (WEST=1/EAST=2/SOUTH=4/NORTH=8)、mazeDoorMask()
与 mazePresetDefIdForDoorMask()(baseDefId + doorMask - 1)。
- 补齐 LvlPrest.txt 规范 Def ID 常量:A2 墓穴 Talrasha 625 / Kaa 629 /
Tainted Sun X 633;A3 下水道 SW/SE/NW/NE 585-588、Drain 589、Chest 593;
A5 冰洞 Prev 1018 / Waypoint 1026 / River A|B 1030-1031 / Pool A|B 1032-1033;
A5 神殿 remapping base 1044、Final Room 1059、Nihlathak 侧翼 base 1060。
- 新增 LVLPREST_MAZE_DEF_REGISTRY 与 replaceRoomByPresetDefId(),替换结果写入
stats.notes(lvlPrestDef=<id> (<name>))便于验证。
- placeAct2TombStuff:新增 levelId 61(爪蛇神庙 2)固定放置太阳祭坛 Def 633。
- placeAct3SewerStuff:levelId 92 放置四角入口房 585-588 + Drain/Chest 按方向偏移。
- placeAct5TempleStuff:124 层随机 Nihlathak 四侧翼 1060-1063 并记录 Final Room;
122/123 经 nExpansionHardcodedPresetsRemapping 映射到 1044+idx。
- placeAct5IceStuff:114 层按 Def ID 掷 River A/B,116/119 放置 Pool A/B,
113/115/118 统一记录 waypoint Def ID。
修复两处潜在保真度缺陷:
- maze.ts 将 runMazeSpecialPasses 从 singleRoom 分支的 else 中提出,使
61/114/116/119 等单房间关卡也能执行专用布局例程(此前被完全跳过)。
- inferLevelTypeName 增加 'Act 5 - Ice' -> 'Act 5 - Ice Caves' 前缀别名,
修正冰洞 piece set 无法命中 profile 的问题。
新增 tests/maze-113c.test.ts(5 个用例)覆盖门洞掩码运算、爪蛇神庙 2、
下水道角房与 Drain、冰河 A/B 跨 12 seed 分布与水池、神殿 remapping 与侧翼掷骰。
Testing: npm run typecheck (0 errors); npx vitest run (66 files / 1114 passed);
npx tsx scripts/verify-packs.ts (1671/1671).
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 08:45:15 +00:00
troytt
f36d1b4260
feat(tilesub): 严格对齐 1.13c LvlSub.txt 试验次数与全排列候选位地块替换算法 (Issue #76 )
...
- 实现 1.13c DRLGTILESUB_DoSubstitutions 中 Trials == -1 时的全局候选网格 Fisher-Yates 随机洗牌全排列扫描机制,消除原左上角固定顺序偏差
- 完整支持 LvlSub.txt 中 SubTheme 0..4 的 Prob0..4、Trials0..4、Max0..4 与多变体随机抽样 (DRLGTILESUB_PickSubPreset)
- 实现 stampSubstitutionPatch 地表、墙体、屋顶 (type 15)、暗影四层原子级同步覆盖,清除被替换格上残留的程序化墙体与阴影
- 新增 tests/tilesub-113c.test.ts 5 项回归测试
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 08:06:50 +00:00
troytt
ef59646631
feat(collision): 还原原版 1.13c 16位多层碰撞掩码系统与投射物/视线穿透 (Issue #75 )
...
- 在 d2map.ts 中新增 1.13c D2C_CollisionMaskFlags 全部 16位掩码定义与组合掩码 (COLLIDE_WALL, COLLIDE_VISIBLE, COLLIDE_MISSILE_BARRIER, COLLIDE_DOOR 等)
- 升级 buildIsoMapScene 生成 Uint16Array collisionMasks 多层碰撞网格,同时 100% 保持 blocked Uint8Array 兼容
- 实现原版 D2Common.dll COLLISION_CheckMask (#10118 ), COLLISION_SetMask (#10127 ), COLLISION_ResetMask (#10123 ) 与亚图块级 Bresenham 射线追踪 COLLISION_RayTrace (#11263 )
- 在 iso-terrain.ts 与 skills.ts 中接入弹道穿透与视线判定,允许弓箭与法术越过河流/熔岩/低矮障碍并阻挡于实体高墙与闭合门洞
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 07:58:49 +00:00
troytt
97ab449231
feat(preset): 统一 35 个预设关卡流水线与 8×8 Room 几何装配切片 (Issue #74 )
...
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 07:00:53 +00:00
troytt
ea61823e60
feat(wilderness): 完善地表边缘过渡瓦片(Auto-Tiling)与 DRLGROOMTILE_GetTileCache 权重混合 (Issue #70 )
...
- 实现 DRLGROOMTILE_GetTileCache:基于 dwDt1Mask、稀有度字段(rarityFrameIndex/rarity/Rarity)与权重累加的瓦片筛选和轮盘赌采样
- 增强 deriveGroundTilesFromDt1Libraries:按权重降序排列并完整提取 DT1 瓦片稀有度及索引元数据
- 导出 8 邻域位掩码常量(NEIGHBOR_MASK_NE/E/SE/N/S/NW/W/SW)与 NEIGHBOR_OFFSETS
- 实现 computeNeighborMask(DRLGROOMTILE_ComputeNeighborMask 别名):对野外地块进行安全邻域状态位掩码计算
- 导出 WATER_EDGE_TILE_LUT(256 元素表):深水 24 位于 0xFF,边缘过渡序列 0..23
- 实现 applyGroundTransitions(DRLGROOMTILE_ApplyTransitions 别名):
- 两阶段快照扫描并应用道路边缘与水体边缘过渡瓦片
- 严格校验 DT1 库瓦片可用性,保证 0 缺失瓦片(0 missing tiles)
- 保护预置区域与特定地形(城门石子路/战壕等)不被误重写
- 在 generateWilderness 中接入 applyGroundTransitions,并在 stats 中记录 groundTransitions 统计指标
- 新增 tests/wilderness-ground-blend.test.ts 18 项测试用例,全方位验证邻域掩码、缓存采样、过渡拼接与全关卡无缺失
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 04:05:01 +00:00
troytt
2274e2fe0b
feat(wilderness): 实现相邻野外关卡接缝空间对齐与出入口坐标咬合 (Issue #69 )
...
- 支持在 PlannedGate 中指定精确边界块坐标 blockCoord 与瓦片坐标 tileCoord
- 增加并导出 SeamConstraint 与 GateOpening 接口
- 在 WildernessRequest 中添加可选的 seamConstraints 配置
- 增强 getPerimeterOpenings 与 DRLGOUTPLACE_PlaceAct1245OutdoorBorders,支持 seamConstraints 及 blockCoord/tileCoord 坐标咬合与平滑边界夹紧
- 增强 planAct1OutdoorLevel 中的边界出入口与道路锚点计算,根据前驱/相邻关卡的接缝约束锁定 anchorCell 与 interiorCell
- 在 WildernessStats 与 WildernessResult.stats 中记录并返回 gateOpenings
- 新增 tests/wilderness-seams.test.ts 全套单元测试与集成测试
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 03:29:18 +00:00
troytt
d7defd8da3
feat(wilderness): 还原 DRLGOUTDOORS_SpawnAct12Waypoint 小站与神殿拓扑放置算法 ( #68 )
...
- 在 src/game/wilderness.ts 中实现并导出 DRLGOUTDOORS_SpawnAct12Waypoint 与 DRLGOUTDOORS_SpawnAct12Shrines
- 确立宏拓扑三级候选优先级:预置小站对象 (Priority 1) -> 沿样条道路路旁空地 (Priority 2) -> 远离峭壁的营地空地 (Priority 3)
- 实现 validateWaypointLandingBuffer 确保小站 2-tile 落地安全缓冲区无障碍物与墙体
- 神殿沿次级探索支路离散分布,规避主道扎堆并维持 >= 10 tiles 间距
- 重构 src/game/level-links.ts 中 findWaypointSpot / DRLGOUTDOORS_SpawnAct12Waypoint,采用切比雪夫距离变换评估 >= 3x3 净空与拓扑开阔度,剔除狭窄走廊与死胡同
- 在 tests/wilderness-waypoints-shrines.test.ts 补充拓扑选址与落地安全区验证测试
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 03:09:19 +00:00
troytt
0e8778f31a
feat(wilderness): 还原 DRLGOUTPLACE_PlaceAct1245OutdoorBorders 边界峭壁与门洞状态机
...
- 扩展 classifyBorderPiece 支持 1~5 幕所有预置边界切片命名(1..12 编号、方位后缀、Act 2 峭壁、Act 5 崖壁/雪地/峡谷与过渡转角)
- 导出 getCellOrientation 与 selectBorderPiece,支持精确的峭壁偏好 (preferCliff: true/false/undefined) 与开洞优先降级
- 实现并导出 DRLGOUTPLACE_PlaceAct1245OutdoorBorders(保留 layBorder 别名),先顺时针放置四边直边界再盖转角封口,保证 100% 覆盖率与 0 过程回退墙
- 支持 claimedBorderBlocks 城镇过渡区与外立面避让接驳
- 新增 tests/wilderness-borders.test.ts 完整测试全 5 幕边界识别、开闭门洞选择、外圈覆盖与接驳避让
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 02:51:30 +00:00
troytt
939ada6e4c
feat(wilderness): 用基于控制顶点图 (DRLGVER) 的 Catmull-Rom 样条道路取代栅格化 A* 道路 ( #66 )
...
- 实现控制顶点图算法 createDrlgVertices / DRLGVER_CreateVertices,提取城门/关卡出口、中央路口枢纽、特异预设(石块荒野巨石阵、遗忘之塔、邪恶洞窟等)及自然拐点宏控制顶点
- 引入 Catmull-Rom 样条平滑插值算法 catmullRomPoint 与 sampleCatmullRomSpline,生成平滑曲率的自然蜿蜒小径,摆脱死板的 45°/90° 网格折线
- 实现栅格化样条小径 rasterizeDirtPathSegment / generateDirtPaths(2-3 格宽度),无缝对齐 8 邻域 LUT 贴图码表 (DIRT_PATH_TILE_LUT)
- 样条道路与光栅化避障支持:严格避开 roadObstacleBlocks(墓园围墙、遗迹废墟、房屋边界),保护预设建筑阻隔完整性
- 重构 stampThemedRoads:南北向与东西向主道、城门引道及景观支路均通过平滑样条连通,清理道路中轴覆盖的杂物地块
- 新增 tests/wilderness-spline-roads.test.ts,全量覆盖样条数学与曲率、控制顶点图生成、避障光栅化及第一幕野外端到端验证
Closes #66
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 02:32:14 +00:00
troytt
11e8a39e4e
fix(render): 还原原版暗黑2建筑与城门屋顶过道遮挡消失机制 ( #72 )
...
- roofs.ts: 将默认最低透明度 DEFAULT_MIN_ROOF_ALPHA 设为 0.0,还原原版遮挡完全隐形机制
- roofs.ts: getRoofBounds 引入 roofHeight 等距高程投影补偿,将悬空屋顶包围盒延伸至地面投影视野
- roofs.ts: 将 DEFAULT_CLUSTER_CELL_DISTANCE 增至 4,并在 clusterRoofs 中自动插值补全城门与建筑群间隙走廊网格单元
- act-scene.ts & d2map.ts: 在 Drawable 和 IsoDraw 中接线传递 DT1 roofHeight 高程属性
- tests/roofs.test.ts: 补充高程下投、哈洛加斯西门跨过道聚类遮挡及拱梁/上墙全消失测试
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 02:12:37 +00:00
troytt
d0efb04395
refactor(wilderness): 移除凭空发明的程序化十字矮石墙网络,回归原版自然地貌阻隔 ( #65 )
...
- 将 stampThemedDividers 重构为兼容性 no-op,完全废弃人为硬编码的十字切分矮石墙
- generateWilderness 移除 stampOutdoorDividers 调用,回归原版 Act 1-5 依据 DS1 预设边界及地形生成的自然阻隔机制
- 更新 wilderness 单元与多幕道路测试,验证 0 人造矮石墙及真实地貌阻隔保留
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 01:38:00 +00:00
troytt
83f855597b
fix(monsters): eliminate unwired columns, dead logic, and wire elite modifier scaling
...
- Support up to mon25 columns in monsterColumns stopping if column does not exist
- Weight normal pack type selection by kind.rarity in planMonsterGroups
- Wire mechanical stat scaling for stoneskin, cursed, coldenchant, fireenchant, lightenchant, spectralhit, teleport, magicresistant, and aura in applyEliteModifiers
- Add unit tests for monsterColumns, rarity-weighted pack planning, and elite modifier stat scaling
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 15:56:33 +00:00
troytt
cbcf71a731
fix(engine): fix logical tautologies, edge conditions, and argument defaults
...
- Fix isRoad out-of-bounds boundary check to return false instead of true
- Fix rack candidate room selection to strictly prioritize corridor rooms (degree === 2)
- Fix candidateRooms fallback in allocatePacksToRooms to not place monsters in safe zones
- Fix default cellHeight in partitionMapIntoRooms to 40 px for 80x40 isometric tiles
- Throw explicit error in parseTable on empty table data and add provenance comment for temptile.dt1
- Add regression tests for rack candidate selection, safe zones, cellHeight, and parseTable
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 15:46:49 +00:00
troytt
6de9fce610
fix(wilderness): add seed diversity to Bloody Foothills obstacle placement
...
- Apply RNG jitter to obstacle placement coordinates and lengths across frontline zones
- Preserve the unblocked central corridor along y=24
- Track procedural walls and roads in siege topography stats
- Add unit test in tests/wilderness-siege-strips.test.ts verifying seed-based obstacle variance
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 15:25:45 +00:00
troytt
53d6cdd08c
feat(wilderness): track procedural passes in telemetry stats
...
- Add proceduralRoadCells, proceduralDividerCells, proceduralWaterCells, proceduralWallCells to WildernessTelemetry and WildernessStats
- Measure actual painted cells in stampThemedRoads, stampThemedDividers, generateDirtPaths, and act outdoor level topography passes
- Add unit tests in tests/wilderness-telemetry.test.ts verifying counted telemetry against canvas state
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 15:23:31 +00:00
troytt
fa307bff46
fix(wilderness): prevent destructive wiping of walls and presets in roads and bridges
...
1. Protect placed presets and preset walls from being wiped in stampThemedRoads:
check placedRects in paintRoadTile, ensuring preset walls are never cleared.
2. Protect preset floors and walls in stampAct3KurastCauseway so artist DS1
tiles are not overwritten by procedural causeway passes; wire specialPlacedRects.
3. Protect placed preset walls and floors in synthesizeBridgePlanks in Act 3 jungles,
recording placedRects for all jungle pieces.
4. Add unit tests for Causeway preset preservation and road preset wall preservation.
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 15:10:59 +00:00
troytt
a7a3ce13d4
feat(game): document and validate object pools and LvlPrest piece families (Issue #61 Step 4)
...
- Document the 3-tier lookup relation across Levels.txt, LvlTypes.txt, and LvlPrest.txt
- Explain why Catacombs/Sewers index pieces via Def / LevelId = 0 / first levelId
- Explain why composite themes have sub-families (Corrupt Harem, Mephisto, Temple, Bridge, ThroneRoom)
- Implement discoverMazePieceFamilies with optional LvlPrest table and update classifyMazePieceName
- Add Objects.txt mapping validation (validateDungeonObjectPools, validateActPopulationConfig, resolveObjectsTxtId)
- Add comprehensive unit tests in tests/maze-objects.test.ts
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 14:39:13 +00:00
troytt
94305b03dd
Resolve outdoor waypoints and act mapping dynamically from Levels.txt
...
- Add dynamic hasOutdoorWaypoint(levelId, levelsTable) reading Levels.txt Waypoint column (!= 255)
- Unify actOfLevel in acts.ts as canonical source of truth supporting Levels.txt Act column
- Support levelsTable in WildernessRequest, spawnOutdoorWaypoint, spawnOutdoorShrines, stampOutdoorRoads, stampOutdoorDividers
- Re-export actOfLevel, hasOutdoorWaypoint, and OUTDOOR_WAYPOINT_LEVELS cleanly without circular dependencies
- Add unit tests verifying dynamic Levels.txt resolution and fallbacks
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 14:27:54 +00:00
troytt
7efdad4693
feat(game): decode level names from string.tbl with graceful fallback
...
- Implement decodeLevelNamesFromTbl supporting D2Tbl/Map and Levels.txt
- Add CANONICAL_LEVEL_NAME_KEYS and active decoded level names cache
- Support graceful fallback to LEVEL_NAMES_ZH and English name keys
- Add comprehensive test coverage in tests/tbl.test.ts
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 14:14:15 +00:00
troytt
fea677b0b4
fix(wilderness): prevent sealed borders on outdoor levels and track preset failures
...
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 13:43:51 +00:00
troytt
b8810072ce
fix(maze): use PRNG stream in special passes and fix Act 5 temple NE waypoint generation
...
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 13:36:02 +00:00
troytt
a3a3e44838
refactor(acts): unify ACT_TOWNS and townLevelForAct single source of truth
...
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 13:31:07 +00:00
troytt
27e0db38c8
fix(level-links): prevent infinite teleport bounce at seam arrival
...
Eliminate fallback to trigger tile (x, y) when no walkable tile is found
within initial radius. Expand search to full grid extent and guarantee that
the arrival destination is strictly offset inward away from the seam trigger box.
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 13:27:27 +00:00
troytt
bf101dae66
fix(monsters): wire meleeRange from MonsterArt into reach and reference MonUMod constants
...
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 13:03:21 +00:00
troytt
fd0f48c391
fix(monsters): derive superunique specs from SuperUniques table and eliminate fallen1 fallback
...
- Replace static CANONICAL_SUPER_UNIQUES_BY_LEVEL mapping with buildSuperUniqueLandmarkSpecs
- Parse canonical SuperUniques from game data table CANONICAL_SUPERUNIQUES_TABLE
- Export lookupSuperUnique helper to search SuperUniques by id or nameKey
- Support optional tables.superuniques in planLevelMonsters
- Remove dangerous fallback to fallen1/first monster, gracefully skipping missing boss kinds
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 12:54:09 +00:00
troytt
2e73d7b02f
fix(monster-mapping): update hellbovine token to EC, remove XX tokens, and eliminate startsWith fallback
...
- Update hellbovine token from CW to EC in MONSTER_ART_MAP
- Remove invalid XX cut monster entries from MONSTER_ART_MAP and ACT_MONSTER_SPECS
- Replace startsWith iteration in resolveMonsterArtSpec with deterministic O(1) exact and base lookup
- Prevent XX tokens from leaking into art requests
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 12:42:44 +00:00
troytt
04d562a99c
fix(monsters): include superunique and minion monster IDs in PlannedLevel types
...
Ensure monster art is loaded for all superunique bosses and their
minions to prevent red placeholder boxes when they spawn.
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 12:36:42 +00:00
troytt
ae2672dd9e
fix(wilderness): 让遥测数据反映真实计数
...
Issue #58 第 6 条列出的 4 处伪造遥测(第 5 处 unimplementedPasses
已在 5b3a9db 修复)。这些数字是 verify:generators 与 HUD 判断
"这一关是否生成成功"的唯一依据,而它们是字面常量或恒等式:
- wilderness-jungles.ts: junglePathCells 恒为 100、riverBridges 恒
至少为 2。改为扫描画布实测:新增 measureJungleTopography(),按
宏格网把已绘制的地板分成"丛林地面"与"河道水面"(桥面板属于桥,
不计入两者);riverBridges = 实际摆放的桥 + 真正画出来的定制
跨河桥(画布太短时第二条桥画不出来,就不再算数)。并补一条
notes 说明实测值。
- wilderness-acts.ts: 第四幕 104/106 的"玄武岩高原"分支只递增
plateauCells 却一格都不画。改为不再把踏勘当成果,计为候选点并
在 notes 里明说该 pass 未实现。
- wilderness.ts:5049: 特殊预置返回路径恒报 subTheme: 0,改为与主
路径一致的 clamp(floor(request.subTheme), 0, 4)。
- wilderness.ts applySubstitutions: BordType 0 的"整关仅一个"预算
从 Infinity 起算,min(1, Infinity) 恒为 1、减法也永远减不动,
实际退化成"每行一个"。预算改为从 1 起算,上限真正生效。
新增 tests/wilderness-telemetry.test.ts(8 个用例)。在改动前
其中 6 个失败,改动后全部通过。
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 11:55:55 +00:00
troytt
5956b00e72
fix(scene): 删除 DEMO_MONSTERS 假刷怪路径
...
关卡怪物计划为空时,act 场景会塞进 12 个手写的 DEMO_MONSTERS,
并且判断是否为城镇靠的是显示名里有没有英文子串 "town"。
于是:换个名字或换种语言的城镇会凭空冒出 12 只怪;真正的数据
失败(monsters 表读不出来、计划为空)也会伪装成"有内容"。
更糟的是 demo-* id 不在任何美术表里,画出来是红方块,和美术
加载失败的表现一模一样。
改为:
- 删掉 demo-data.ts 里的 DEMO_MONSTERS 与 act-scene.ts 里的
PLACEHOLDER_MONSTER_COUNT、monsterStats/monsterCount 字段,
以及切关时 `next.monsterCount > 0` 的兜底 spawnMonsters 调用;
引擎选项显式传 stats: [] / monsterCount: 0。
- 新增 ActSceneState.monstersPlanned 与 monsterPlanStatusText(),
开机与切关后各调用一次 reportMonsterPlan(),HUD 上如实显示
"本关未规划到怪物",而不是拿假数据盖住。
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 11:45:46 +00:00
troytt
d890f558b1
fix(scene): 让怪物美术加载失败可观测,不再静默变红方块
...
三层 catch 把「整个美术系统崩溃」变成「场景就绪 + 全场红方块」,只留一句
console.warn:
- `monster-art.ts` 逐 COF 层的 `catch` 直接丢层,怪物缺头缺臂而无任何日志。
现在每一层失败都产出 `MonsterLayerFailure`(unknown-component / no-member /
decode),`console.error` 输出,并随 `MonsterAnimationSheet.layerFailures`
与 `LoadedMonsterArt.layerFailures` 返回。
- `loadMonsterArtMap` 改为返回 `MonsterArtLoad{ map, missing, errors,
layerFailures }`:整函数 catch 与逐 spec catch 都计数并降级为 console.error,
预烘焙资源 fetch 失败也不再完全无声。
- `ActSceneState` 新增 `missingMonsterArt` / `monsterArtErrors` /
`monsterArtLayerFailures`,由 `recordMonsterArtLoad` 在两处加载点写入;
HUD 追加 `monsterArtStatusText` 片段。
- 红方块绘制点自己上报 id(流式刷怪可能刷出加载时未知的类型)。
新增 tests/monster-art-diagnostics.test.ts(5 例):逐层 no-member / decode
失败的计数与日志,以及 `missingMonsterArtIds` / `monsterArtStatusText`。
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 11:40:05 +00:00
troytt
952ad00429
fix(monsters): MonLvl 缺行时告警而非静默返回 UNSCALED
...
缩放公式是 `原始表值 × scale ÷ 100`,回退到 `UNSCALED`(全 100)等于直接使用
`MonStats.txt` 的原始值:一只堕落者拿到 21-61 HP 而不是约 3 HP,约 20 倍。
原来这一步完全静默。
- `monsterScaleFor` 缺行时 `console.warn`(按 level 去重,避免每只怪刷一行),
并新增 `setMonsterScalingStrict(true)` 让烘焙 / 校验脚本直接 throw。
- 新增 `monsterScaleLookup`,返回 `{ scale, missing }`,供调用方在不读 console
的前提下自行判断。
- `PlannedLevel` 新增 `missingScalingLevels`,`planLevelMonsters` 把本关所有
缺行的 level 汇总上报,烘焙方因此可以拒绝一个「怪物强度是假的」的关卡。
新增 4 个用例:告警一次、strict 抛错、lookup 不写 console、
`planLevelMonsters` 在 MonLvl 抽掉 level 1 行时报告 `[1]`。
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 11:35:13 +00:00
troytt
e817ace5ef
fix(maze-objects): 删掉 isOpenFloor 里恒真的 `|| true`
...
`hasFloor` 的内容检测写成 `(f.prop1 !== 0 || f.style !== 0 || f.sequence !== 0
|| true)`,末尾的 `|| true` 让整个条件恒为真:DS1 每个格子的每一层地板都有一条
记录,未绘制处是全零槽位,于是「没有地板美术的空格」也被判定为可放物件的地面。
删除 `|| true` 后,全零地板槽位不再算作地面;新增 6 个回归用例覆盖
painted / 全零 / 仅 style / 仅 sequence / hidden / 多层,以及「整关地板全为空槽
时不生成任何动态物件」。
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 11:30:47 +00:00
troytt
5b3a9db986
fix(maze): 让 UNIMPLEMENTED_PASSES 反映真实缺口
...
`UNIMPLEMENTED_PASSES` 此前被硬编码为 `[]`,注释声称 11 个核心地牢 pass 全部
实现,于是 `stats.unimplementedPasses` 在每一关都无条件报告「零缺口」——包括
那些 `specials` 表为空、根本不生成楼梯房间的 level type。
改为从 `MAZE_LEVEL_TYPE_PROFILES` 实测推导:
- 新增 `unimplementedPassesFor(levelTypeName)`,按 level type 返回缺口:
没有 profile 行,或 `specials` 为空且不是 `singleRoom` 定制关。
- `UNIMPLEMENTED_PASSES` 改为所有 level type 缺口的并集,因此非空。
- `generateMaze` 的 `stats.unimplementedPasses` 改报本关自己的缺口,
而不是全局常量;`unresolvedRoles` / `notes` 本来就已随 stats 返回。
同时修正两处已经与代码不符的注释:`maze.ts` 里「per-act 装饰 pass 均未复现」
(它们已在 `maze-special-passes.ts` 中实现),以及 `pack-act-assets.ts` 里
「第二幕下水道 / 墓穴 / 第三幕地牢 specials 为空」(这三者现在都有 specials,
真正为空的是 Harem、Basement、Spider、Act 5 Lava)。
测试:原 `UNIMPLEMENTED_PASSES is completely empty` 断言的是被修正的错误行为,
改为断言它恰好列出所有 `specials` 为空的 level type;另加 4 个用例覆盖
按 level type 查询与 `generateMaze` 的 stats 透出。
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-17 11:29:06 +00:00
troytt
4ece5aa3c1
feat(scene): acts.html 接入房间级惰性刷怪与休眠/唤醒流式加载
...
把已合入 main 但从未被生产代码调用的两块能力接到 acts.html 的
act-scene 上,按 D2Game.dll 的做法组织怪物生命周期:
- act-scene:新增 buildStreamingRooms(),把关卡切成房间并把怪物群
分配给房间,交给 GameEngine 的 roomPacks。房间按「场景像素空间」
切分而不是格子空间——一块矩形格子投影出来是菱形,相邻矩形的
AABB 几乎完全重叠,那样切会让全图房间同时「靠近玩家」,流式加载
就没有意义了。落在菱形外、没有任何可走地面的房间直接丢弃,否则
分到它头上的怪物群会被静默吞掉。
- monster-streaming:populateRoom 改用 monster-rooms 的
spawnPacksInRoom,怪物在房间内抱团成营地,而不是沿用旧的全局
螺旋撒点。这把 feat/monster-streaming 和 feat/monster-room-placement
两条分支真正合到了一起。落进安全区(传送点/关卡入口)的个体会被
推到安全区边缘。
- 重新打开怪物仇恨。它当初被关掉是因为全图怪物同时醒着,会被拖成
一条长队;现在玩家没走近的怪物要么还没实例化、要么在睡眠中。
- swapLevel 过图时重建房间表,而不是在门口一次性刷完整张图。
tests/wilderness-roads:角落断言改为按槽位号统计。这条断言早于
6b9c3d3(Act 1 北/西/三个角改用岩壁边界),岩壁族还带字母后缀
(Wild Cliff Border 6A),按全名查表就落空了。
Verified: typecheck + 923 tests pass;另用真实烘焙数据回放,
冰冷之原 45 群 57 只怪 → 45 个房间,出生点只实例化 18 房 26 只,
走遍全图后恰好 45 房 57 只,无遗漏、无重复。
2026-09-17 11:14:26 +00:00
troytt
4c2e399e1d
test(loop): expand timeout tolerance to 120ms to prevent jitter in concurrent runs
2026-09-17 09:16:48 +00:00
troytt
274ffae71e
Merge branch 'feat/monster-room-placement'
2026-09-17 09:11:24 +00:00
troytt
5aba7487f3
Merge branch 'fix/issue-16' into main
2026-09-17 09:10:59 +00:00
troytt
3951e70ef4
Merge branch 'fix/issue-20'
2026-09-17 09:09:01 +00:00
troytt
6f01c8de89
Merge branch 'fix/issue-19' into main
2026-09-17 09:08:46 +00:00
troytt
db3d6ffa84
Merge branch 'fix/issue-57' into main
2026-09-17 09:07:38 +00:00
troytt
12718ebcd6
Merge branch 'fix/issue-56' into main
2026-09-17 09:03:15 +00:00
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
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
c2bda29211
Merge remote-tracking branch 'origin/feat/monster-streaming' into feat/monster-room-placement
2026-09-17 08:36:02 +00:00
troytt
8e46b64156
feat(monsters): room-local pack allocation and clustering placement
2026-09-17 08:35:11 +00:00
troytt
71fdaaefe3
feat(sim/monsters): lazy room population with bPopulated and active/sleeping streaming
2026-09-17 08:33:19 +00:00
troytt
11e2e42731
perf(render): optimize GL state with immutable texStorage2D, uniform caching, and configurable discard ( fixes #20 )
2026-09-17 08:16:05 +00:00
troytt
33585afe8d
test(render/perf): comprehensive lifecycle verification and performance regression suite ( fixes #16 )
2026-09-17 08:15:13 +00:00
troytt
167928a31f
feat(sim/loop): decouple simulation from rAF with independent timer and separate TPS/FPS metrics ( fixes #19 )
2026-09-17 08:12:57 +00:00
troytt
92d6731dae
feat(map-gen/wilderness): data-driven wilderness SuperUniques and landmark entities from SuperUniques.txt ( fixes #57 )
2026-09-17 07:49:31 +00:00
troytt
46b7166f22
feat(map-gen/wilderness): thematic clustering and dispersed distribution for waypoints and shrines ( fixes #56 )
2026-09-17 07:36:39 +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
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
80c74a6b95
test(cull): add unit test for bounds rebuild on level switch
2026-09-17 05:48:51 +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
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
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
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
troytt
258be4d608
fix(publish): fine-grained batched push with resume
...
跨境链路对长连接做 MSS 钳制:实测 pmtu 1460 但 mss 被压到 324,重传率
13.7%,cwnd 触底到 3-4,有效带宽只剩 ~20 KB/s。原来按 act 分 5 批推送,
单批最大 117 MB,在这种链路上必然超时,且失败后整批重来。
改为:
- 按地图目录累计到 ~10 MB 就切一批(45 批 / 399.1 MB),失败只重推一批
- http.lowSpeedLimit 1000 + http.lowSpeedTime 120:低于 1 KB/s 持续 2 分钟
就断开,让重试开新连接(实测新连接能拿回 mss 1408,速度提升 2.5 倍)
- --work 支持续推:用 git ls-remote 读远端 main,回卷本地工作区对齐,
再跳过所有已在 HEAD 中的批次,避免重复上传
- 5 次重试,线性退避 15/30/45/60 秒
另外修正 tests/wilderness-features.test.ts 的 WildernessPiece 导入——
verbatimModuleSyntax 下类型导入必须用 import type。
TAG=agy
CONV=c89513df-cd12-4749-b5bd-5e9f16639391
2026-09-15 06:33:19 +00:00
troytt
2d7f5f7a00
fix(wilderness): add interior stone walls, probabilistic river, and perimeter opening control
...
- Implement isClosedBorder, selectBorderVariant, and getPerimeterOpenings
- Moo Moo Farm (Level 39): enforce 0 exits (100% closed perimeter), add interior corrals and stone fences
- Blood Moor (Level 2): enforce exactly 1 entrance (South) and 1 exit (East), add stone corrals/fences and probabilistic river/bridge presets
- Add 'Act 1 - Fence Fill', 'Act 1 - River', 'Act 1 - Bridge' to Act 1 Wilderness piece families
- Add comprehensive unit tests in tests/wilderness-features.test.ts
- Optimize publish-packs.ts with batch incremental pushes by Act
2026-09-15 05:25:09 +00:00
troytt
4cc49a01aa
perf(render): 视口剔除 + 单批次合图 + 零分配顶点写入 (refs #16 )
...
主循环掉到 20 tps 的原因不是"缺引擎",而是三件具体的事,这个改动把它们一次做掉。
1. 视口剔除。此前整张地图无条件提交:把 act-scene 的绘制顺序在 365 张烘焙关卡上
静态重放,平均每帧 2 848 个四边形、最差 14 400 个(act4 迪亚波罗),而真正落在
1280x720 视口里的只有 7%~11%。现在关卡建好时把每条绘制列表的包围盒预先摊平成
一块 Float32Array,每帧线性扫描做四次浮点比较。实测平均 2 848 -> 204 个/帧
(7.2%),最差 14 400 -> 437 个。
之所以是线性扫描而不是二分或分桶:绘制列表并不按 y 单调(地面 0/365 单调,最大
回退 9 520 px),任何空间索引都得重新合并回画家顺序。14 400 次比较约 15~30 µs,
占 16 ms 预算的 0.2%,换来的是顺序原样保留。墙和对象那条两路深度归并里,剔除只
跳过"画"这一步,游标照常前进——否则后面所有遮挡关系都会错位。
2. 一次 draw call。此前批次一遇到图集页切换就得断开,同一帧里页号来回横跳,实测
平均 361 次、最差 3 077 次 draw call。现在每个在用的图集页各占一个纹理单元,
单元号随顶点属性进着色器,片元按 switch 选采样器,只有在用页数超过
MAX_TEXTURE_IMAGE_UNITS 时才断批。
这里没有采用 issue 里原先设想的 TEXTURE_2D_ARRAY:页高差异极大(2048x1999 到
2048x48),纹理数组要求各层等大,实测朴素做法显存涨 1.73 倍(最差 2.82 倍,单关
156.8 MB),装箱后仍要 1.29 倍。而全部 365 张关卡里单关最多只有 10 个页,加角色
图集 11 个,稳稳低于 WebGL2 保证的 16 个单元——多单元合批同样是 1 次 draw call,
显存零增长,资源也不用重烘焙。
3. 零分配顶点写入。quad() 原本每次建 4 个临时数组再解构,实测 2 849 个四边形写一帧
要 244.9 µs;展开成标量后 106.0 µs(2.31 倍),每帧少建 11 396 个临时数组。同时
顶点从 6 个/四边形降到 4 个并改用索引缓冲,上传前先 orphan 一次缓冲区避免与 GPU
抢同一块内存。
顺带修掉几个让问题一直看不见、或者纯属每帧浪费的地方:
- state.drawCalls 原本在 act-scene 和 walk 里都硬编码成 1,这个假读数正是问题长期
没被发现的原因;现在读渲染器自己的计数,HUD 一并显示图元数、剔除数和帧耗时。
- pages.filter(...).length 每帧建一个临时数组只为数个数,改成循环。
- HUD 文本每帧重建(十次字符串拼接 + 四次 toFixed),改成 4 Hz。
- NPC 名牌每帧 innerHTML='' 再 createElement,改成复用元素只改位置。
- onTick 里的 playerMoving 用 let 遮蔽了外层同名变量,导致 onRender 永远读到 false、
角色走路动画从不播放。改成赋值。
护栏:新增 tests/cull.test.ts,除边界条件外,用全部 365 张关卡真实数据重放一遍,
断言"开剔除后画出的序列必须是不开剔除时那个序列的子序列"(顺序一致、只少不多),
并守住剔除率。verify-renderer-lifecycle 从 26 条加到 37 条,锁住"60 个四边形在 3 个
页之间来回切只花 1 次 draw call"、超出单元预算恰好多花 1 次、计数器在 begin() 归零、
以及着色器里采样器数组的形状。
Refs #16
2026-09-15 01:30:39 +00:00
troytt
29b3a4aebd
fix(render): NPC 有立绘时不再叠加绿色占位方块 (refs #6 )
...
城镇 NPC 补齐之后,罗格营地一个分区会出现 19 个绿色方块糊在正常立绘上面。
起因是两套绘制路径各自独立判断:`buildPackRuntime` 用 `if (obj.frame)`
把 NPC 并入 `objectDrawables` 画出真实立绘,而实体层对 `engine.npcEntities`
里的每一个 NPC **无条件**再画一个 16x26 的纯绿占位方块。在只有凯恩一个
NPC 的年代这看不出问题,NPC 数量从 103 涨到 889 之后就藏不住了。
修复不是在实体层加一个平行的判断——那只会把同一个条件抄成两份,早晚漂移。
改为把判断抽成 `hasPackedSprite` 放进 `src/game/npc.ts`,两条路径共用同一个
函数:立绘层为真时画图,占位层为假时画框,互斥由函数本身保证。
该函数写成类型守卫,立绘层因此可以在同一次调用里把 `frame` 收窄成非空,
省掉原本那个多余的二次判空,也就不存在第二个会漂移的判断了。
`NpcEntity` 增加 `hasSprite` 字段,必填而非可选,这样将来新增放置点必须
表态。`GameEngine` 构造函数里那圈合成放置点显式标记为 `false`——它们本来
就没有预烘焙美术,占位方块对它们仍然是唯一的可见形式。金色名字标签两类
都保留,可交互提示不受影响。
顺带把 `npcEntities` 的行内结构类型换成已导出的 `NpcEntity`,避免同一个
形状在同一个文件里存在两份定义。
新增 `tests/npc-placeholder.test.ts`:除守卫本身的行为与收窄能力外,还把
全部已打包 NPC 过一遍,断言这个划分是完备的(每个 NPC 恰好落入一条路径,
不重不漏),并钉住罗格营地四个分区的 NPC 名册。守卫锚定在它真正读取的
`samples/d2-packs/index.json` 上。
2026-09-14 23:56:49 +00:00
troytt
e180520ef5
fix(npc): 补齐城镇 NPC 解析的收尾问题并加上回归护栏 (refs #6 )
...
承接前两个提交。上一版把 type-1 条目接了进来,但收尾有几处缺口,
逐项补齐:
1. 恢复被误删的注释
`objectsTableTyped` 上方 `/** Same table, through the typed loader ... */`
在重构中被删掉了,与改动无关,还原。
2. token 大小写统一
`monstats.txt` 的 `Code` 列拼写不统一——同一批数据里既有 `K9` 又有
`k9`,`ja` / `6z` / `7i` / `7j` 也是小写。而 `monsterMembers` 的索引
键一律大写,于是 `map.get(token)` 直接落空,Jamella 和三个受伤的野蛮人
被以「无美术」的形式烤进了 scene.json。上一版在查表处补了
`.toUpperCase()`,这里进一步在**产出源头**归一化:同一个怪物不会再以
两种拼写出现,调用方也不必猜大小写。
3. 空 Place 由静默跳过改为显式报错
`presetPlaceByAct` 的数组下标**就是** DS1 的 type-1 id,数组必须保持
稠密。原先的 `if (!place) continue` 一旦遇到空格子,会让其后每一个 NPC
都错位认领邻居的身份——正是 `22` vs 21 那个缺陷的同一类。实测 1.13c
五个 act 共 0 个空 Place,所以这条守卫永远不会触发;它的意义是万一数据
变了要**响亮地失败**,而不是悄悄错号。
4. 补两层回归测试
- `tests/resolve-type1.test.ts`:清掉两个未使用的 import、补回项目惯用的
`.ts` 后缀、把边写边想的注释换成「这条用例为什么存在」,并新增一条
覆盖小写 `Code` 归一化的用例。
- `tests/packed-npc-art.test.ts`(新增):数据层不变式——凡是进入
scene.json 的 NPC 必须同时具备 `member` 与 `frame`;同一 token 不得
以两种拼写出现。这是之前唯一能抓住那 4 个哑火 NPC 的层次,单元测试
抓不到,因为缺陷在打包脚本的查表里。
守卫锚定在它真正读取的 `samples/d2-packs/index.json` 上,而不是
`samples/d2` 目录。
验证(全部本地实跑):
tsc --noEmit 0 error
vitest 23 文件通过 / 1 跳过,429 用例通过 / 2 跳过
pack:data 365 张地图
verify:packs 1302/1302 断言通过
NPC 实例 889,未解析 0,token 拼写不一致 0
变异测试:
- 把 `presets[objectId]` 改成 `presets[0]` → resolve-type1 2 条转红
- 还原 `map.get(token.toUpperCase())` 为 `map.get(token)` → 全量套件
仍然全绿,证明此前该修复毫无回归保护;本提交的 packed-npc-art 补上了
这一层(改动前该用例对现有 pack 报 `K9 -> K9, k9`,重新打包后转绿)。
2026-09-14 15:58:51 +00:00
troytt
3cd4549332
fix: 修复非基底对象 NPC 丢失及其资源检索区分大小写的问题
...
在前一次提交的基础上修复并强化了 Type 1 对象(怪兽及 NPC 原型)解析与打包的正确性。
1. 修正大写回退遗漏问题(解决失联的 6 个特定角色如 Act 4 Jamella 等):`pickObjectMember` 中需要使用大写查找 token,而原来直接代入了小写 token 导致失败,修复后所有角色均成功合图。
2. 添加安全跳过统计 (`skippedMissingArtSpawns`),使得未匹配上图片的隐藏对象能干净跳过。
3. 增加 `tests/resolve-type1.test.ts` 提供核心覆盖率。
4. 应用性能重构与规范修复,将二次查询提速为内置的 `Map` 遍历,同时补齐了 `OBJECT_TYPE_MONSTER`、Typescript interface、尾随空格与逗号修复。
5. 后向保留了省略 tables 时旧版逻辑的注解。
(refs #6 )
2026-09-14 15:37:32 +00:00
troytt
ad7ced6760
test: 修复三处跳过守卫,它们只在真实资源到位时才暴露
...
这三个测试都犯了同一个错误:**守卫检查的资源,和实际读取的资源不是同
一个**。在 `samples/d2` 缺席时它们一律静默跳过,看起来是绿的;真实 MPQ
一旦放进来,守卫放行,然后立刻炸在一个完全无关的缺失文件上。
- `tests/dcc.test.ts`
守卫看 `samples/d2`,扫描目标却硬编码成 `samples/fixtures`——那里没有
`d2char.mpq`。于是这个套件**从来没有真正跑过**:没资源时跳过,有资源时
`TypeError: Cannot read properties of null (reading 'listFiles')`。
目标改为 `samples/d2`;打开归档失败时由"打日志后继续"改为 `throw`
并带上归档名,避免把"文件缺失"伪装成 100 行之外的空指针;
`let characters: any` 收紧为 `MpqArchive | null`。
- `tests/widths.test.ts`
守卫看 `samples/d2`,真实输入却是一个空成员名和
`scripts/browser/checks/map-widths.txt`——后者在仓库里从未存在过。
改为按实际输入判断。顺带补 `: string` 标注,否则空字符串字面量会被
收窄成 `never`。
- `tests/formats-malformed.test.ts`
守卫看 `samples/d2` 目录是否存在,却去读其中一个散落的 `.cof`。但
`samples/d2/` 放的是 MPQ **归档**,不是解包后的目录树,目录存在完全
不能说明那个文件存在。改为直接判断该文件路径。
修复后,在真实 MPQ 就位的情况下:
21 passed | 1 skipped (22 files),421 passed | 2 skipped (423),tsc 0 error。
其中 `dcc.test.ts` 从"0 个测试"变成真正执行。
2026-09-14 14:51:50 +00:00
troytt
f52edd85f9
test(formats): 增加针对损坏资产的覆盖率和回归测试 ( fixes #11 )
...
Implements regression limits testing bounds limits, invalid header mappings and stream truncations for binary formats, verifying the decoder engine halts cleanly without exception masking or propagation leaks. Ensures zero false positives and safely covers buffer boundary rules.
2026-09-14 13:24:25 +00:00
troytt
a6ca337a10
feat(formats): 添加安全的二进制数据读取器
...
Provides ByteReader, requireBytes, and precise FormatError subclasses (TruncatedDataError, InvalidFieldError) to enable safe decoding bounds checking.
2026-09-14 13:19:01 +00:00
troytt
11439256bf
fix(art): 将 NPC 渲染文件的基础寻址从 objects 修正为 monsters
...
修正 Deckard Cain、Jerhyn 等城镇 NPC 的基础美术路径。通过从 objectEntry 下传的 baseIsMonsters 布尔标识,使特定 Object 的 DCC 及 COF 查表过程自动从默认的 `data\global\objects` 切换到 `data\global\monsters`, 彻底解决打包器因跨域失效而丢弃图集的情况。
2026-09-14 13:08:46 +00:00
troytt
e3ca14c357
refactor(lockstep): 按项目规范彻底移除 any 约束逃逸并优化哈希分配性能 (refs #7 )
...
此提交响应了进一步集成的代码质量与内存性能审查反馈:
1. 深入清理代码库类型系统边界,利用 Structural Target Interfaces 为所有传递进哈希运算中的世界和对象提供原生支持而不用 `any`,并修复由于引入 #10 放宽的 engine.ts 的 syncEngineState 缺失的 `EngineViewState` 严谨签名;
2. 提升了数据解析健壮性:在 save.ts 中增加严格的运行时验证(利用 Type Guard: `asserts value is GameSnapshot`),取消一切断言强转;
3. 取消了计算锁步哈希期间对背包和掉落物品的数组分配与运行时排序(重构为支持 Order-Independent Combination 的无排序位偏移计算模式),确保 25Hz 打包同步路径中零内存无端分配;
4. 并修正了 net-scene 等多个测试的静态数据映射与严格的突变检测。
2026-09-14 13:05:18 +00:00
troytt
45b6a2caa7
feat(lockstep): 接入实际组件状态哈希并修复场景验证死区 (refs #7 )
...
替换了 net-scene 中的闭包测试用 digestWorld 状态逻辑,通过真实注入的 inventories, quests, groundLoot 清单确保联机场景具备全覆盖的一致性锁步哈希验证。增加了 integration 测实验证 pickup 产生的完整分歧,确保从引擎层面而非独立计算侧暴露漏洞。
2026-09-14 12:55:16 +00:00
troytt
4290e5feb2
fix(game): 移除不安全的类型转换并增加存档校验 (refs #7 )
...
在 character.ts 中使用 flatMap 结合类型缩窄(null 检查),移除了对 DccFile 的强制类型转换,保证内部逻辑的编译期安全。在 save.ts 中增加了对背包数据的严格运行时有效性验证,避免由于恶意或破损存档导致的隐式游戏损坏,并用专用的反序列化测试覆盖新校验过程。
2026-09-14 12:44:19 +00:00
troytt
fc201d7290
feat(lockstep): 实现完整的锁步状态哈希算法 (refs #7 )
...
扩展了锁步哈希验证以包含背包内容、地面物品和任务进度。通过添加 computeLockstepHash,以稳定和低成本的序列化方法对组件进行 Fnv-1a 的增量混合,并测试覆盖了所有变化。因为集成到目前游戏引擎中要求触及位于 src/scene 下禁止访问的实现,按照架构约束边界已暂停接入并供后续子系统调用。
2026-09-14 12:44:18 +00:00
troytt
6e6d8ed022
feat: 将所有动态实体加入地形深度排序渲染列队 ( fixes #10 )
...
将 monsters、npcs、projectiles、ground items 等 Engine 内的物理对象一并收集,
利用相同的 `cell.x + cell.y` 画家排序与 `act-scene.ts` 里由预处理好的 `walls` / `objectDrawables` 进行混合深度渲染。
此改变为后续加载真实的实体资源图集打下渲染基础。
2026-09-14 12:40:48 +00:00
troytt
38d06cdb28
feat: 抽取独立无状态 GameEngine 领域与相关逻辑
...
将 map-scene 中负责每帧更新逻辑与状态管理的巨量代码抽取成 `src/game/engine.ts` 内的 `GameEngine`。
解耦模拟与 WebGL 和 DOM,保证可以无界面测试。
并引入了 `tests/engine.test.ts` 自动化单元测试(Vitest)。
2026-09-14 12:37:29 +00:00
troytt
4d47a56e68
feat(mpq): 实现 ADPCM 与 Huffman 音频解码并接入 Web Audio ( fixes #13 )
...
- 基于 StormLib 的 C++ 原型,实现了 4-bit IMA ADPCM 解码(单声道与双声道分离)。
- 实现 FGK 自适应 Huffman 树解码,从给定分布表构建静态初始树进而按流插入权重并重平衡。
- 保证严格使用位运算确保 16-bit 符号处理正确,增加对应 ADPCM 解码保护边界。
- 新增 `AudioManager` 基于 Web Audio API 设计跨文件懒加载及混音管理,避免在未配置时报错。
- `tests/mpq-audio.test.ts` 加入覆盖测试,并含 ADPCM 打包构造器,证明解压流程完整有效。
- `package.json` 添加 `verify:audio` 脚本,可快速校验环境依赖导入。
2026-09-14 12:30:21 +00:00
troytt
f4dd55be6c
fix(formats): 按官方哈希桶格式重写 tbl 解码器与编码器 ( fixes #8 )
...
- 重构了 `src/formats/tbl.ts` 以正确读取 21字节魔数头与 17字节哈希桶节点
- 使用了 NUL 终止与正确的字节长度计算方式,修正之前错误的 UTF-16 假设
- 支持通过 `TextDecoder` 选择代码页 (默认为 windows-1252,支持 gbk 等 fallback)
- 暴露了原始的 O(1) 线性探测查询函数 `lookupTblFast` 和哈希函数 `tblHash`
- 对 `scripts/lib/tbl-writer.ts` 及其验证脚本进行了重写,以正确写入和校验哈希桶布局
- 增加了完整涵盖功能与越界校验的 vitest 单元测试,并在真实的 113c patchstring.tbl 下实现了 1169/1169 哈希全量验证通过
- 将测试文件加入项目目录
2026-09-14 12:30:07 +00:00
troytt
1a81693d74
test: 引入 Vitest 测试框架、覆盖率统计与 CI 流水线 ( fixes #14 )
...
- 安装了 `vitest` 和 `@vitest/coverage-v8`,配置支持对 `tests/**/*.test.ts` 文件做测试。
- 将基于 `process.exit(1)` 的断言脚本平滑翻译为了符合 Vitest 生态的 `test` 与 `expect` 结构。
- 加入了 Github Actions (Gitea Actions) 语法的 CI 文件,分别验证 20.x, 22.x 下的测试通过情况。
- 原有的 `scripts/verify-*.ts` 等 CLI 脚本功能照常运作,且依赖缺失时通过 skipIf 进行安全跳过。
2026-09-14 12:25:28 +00:00