troytt
6eaeab4619
feat(skills): authentic 1.13c Cast Overlay system and elemental spell animations ( Fixes #386 )
2026-09-23 03:53:57 +00:00
troytt
7d7925ab2b
Merge branch 'issue-390' into main ( Fixes #390 )
2026-09-23 02:36:16 +00:00
troytt
ff867a13e3
feat(skills): authentic baked missile DCC atlas and explosion rendering
2026-09-23 02:35:55 +00:00
troytt
30e8ecee5f
feat(ui): authentic Alt key ground item floating labels and ladder collision avoidance ( Fixes #390 )
2026-09-23 02:35:05 +00:00
troytt
2733ba625b
feat(render): authentic ground item rendering and glint sparkles ( Fixes #389 )
2026-09-23 02:27:31 +00:00
troytt
3f78ad782e
feat(items): authentic 1.13c ground items entity, safe drop radial search and item/gold dropping ( Fixes #388 )
2026-09-23 02:19:12 +00:00
troytt
627af83e97
feat(skills): authentic 1.13c Fire Bolt projectile physics from Missiles.txt ( Fixes #385 )
2026-09-23 02:09:50 +00:00
troytt
752d7e8066
feat(map): authentic Diablo II 1.13c shadow layer (type 13) rendering and pack verification
2026-09-23 01:22:51 +00:00
troytt
7f810e40e3
feat(class): complete 7-class switching with signature sets and stash treasures ( Fixes #379 )
2026-09-22 10:45:48 +00:00
troytt
bc6b5cd331
feat(skills): add normal attack icon and enable dual left/right slot binding ( Fixes #151 )
2026-09-22 06:19:19 +00:00
troytt
db82a5da32
feat(stash): implement camp town stash entities across 5 acts and 6x8 storage panel ( Fixes #150 )
2026-09-22 05:33:57 +00:00
troytt
a727343eb3
feat(arena): add switchAndCastSkill and hero class avatar overlay to ?arena=1
2026-09-22 03:30:22 +00:00
troytt
550787cb0a
feat(engine): implement R1 universal 1.13c skill engine, 7-class renderer, arena harness, and M2/E2E suites
2026-09-22 03:22:02 +00:00
troytt
09cc23b53c
fix: load localized map area names from tbl on transition ( Fixes #147 )
2026-09-22 02:55:24 +00:00
troytt
f24ad292ca
feat(char): pack and render all 7 hero classes with R8 atlases and tests
2026-09-22 02:38:22 +00:00
troytt
f9fd5eb3b8
feat(anim): 实现角色与怪物动作播放、武器切换感知与地表锚点对齐 ( Fixes #145 )
2026-09-21 14:51:18 +00:00
troytt
e2a74cc8f0
feat(ui/controls): 实现完整 UI/HUD 系统、操作快捷键对齐与装备词条染色 ( fixes #134 , fixes #135 , fixes #136 , fixes #137 , fixes #138 , fixes #139 , fixes #140 )
...
- R1 (Issue #134 ): 离线烘焙预生成 UI 图集 (public/ui/) 与 embedded DropTables,彻底消除 2 秒纯色占位回退,实现零运行期 MPQ/DLL 依赖
- R2 (Issue #135 ): 修复地图缩放 (0.4x~8.0x) 及多 DPR 下 NPC 名称标签与模型顶部的投影对齐
- R3 (Issue #136 ): HUD 与全屏画布采用 1:1 纵横比等比缩放与居中,彻底修复宽屏下红蓝球被横向拉伸为椭圆的问题
- R4 (Issue #137 ): 传送点菜单 (Waypoint Menu) 支持点击无缝跨场景传送,随机选取地图变体并平稳降落在对应小站子网格
- R5 (Issue #138 ): 角色属性面板 (InvChar6) 文本与数字完美对准背景凹槽石框,加点按钮对齐
- R6 (Issue #139 ): 物品栏 5 级回退 DC6 图标渲染,消灭空白占位框;装备 Tooltip 等比缩放与视口翻折防遮挡
- R7 (Issue #140 ): 任务日志界面集成 1~5 幕全量 27 个任务图标 (a1q1..a5q6) 及 27 帧已完成印章,第 4 幕自动进行插槽钳制保护
- Controls Parity: 还原原版左键移动/NPC交互/小站/攻击,Shift 强制原地站立攻击,右键释放技能与被动/无技能移动回退,解除 A/W/S/1..4 快捷键与移动冲突
- Tooltips & Dyes: 基于 1.13c PL2 调色板 Table 104 (cgrn) 渲染翡翠绿军帽,战争召唤按 descpriority 完整呈现 11 行符文之语属性与三行正统标题
2026-09-21 11:09:10 +00:00
troytt
79bf8f4cda
perf(scene): 解决首屏阻塞式 166 次 MPQ Range 请求导致的地图载入卡顿 (Issue #117 )
...
- 将 DropTables 移出首屏临界路径,消除 runScene 启动时 166 次 MPQ HTTP Range 阻塞
- 引入 getSharedDropTables 全局单例记忆化缓存,避免跨地图切关卡时重复挂载与重读 MPQ
- 在 boot() 初始阶段提早非阻塞式预热,并在 GameEngine 创建后异步注入 dropTables
- 在 GameEngine 中添加 setDropTables 方法支持动态掉落表注入
- 新增 tests/scene-drop-preload.test.ts 覆盖单例缓存与延迟注入行为验证
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-19 03:03:28 +00:00
troytt
370dcc05b1
feat(items): 用 TC 树替换 rollDrop 并彻底删除 demo 物品数据 (Issue #107 )
2026-09-18 12:54:50 +00:00
troytt
064e956de6
feat(items): 用 TC 树替换 rollDrop 并彻底删除 demo 物品数据 (Issue #107 )
2026-09-18 12:54:21 +00:00
troytt
27049a4dac
fix(scene): 优化 Live 模式请求非预设关卡时的诊断报错信息
...
当用户在 ?live=1 下请求旷野或迷宫关卡(如冰冷之原 Level 3)时,明确提示该关卡无单体 LvlPrest 预设,引导切换至资源包 (Pack) 模式浏览。
TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
2026-09-18 12:16:34 +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
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
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
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
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
5aba7487f3
Merge branch 'fix/issue-16' into main
2026-09-17 09:10:59 +00:00
troytt
6f01c8de89
Merge branch 'fix/issue-19' into main
2026-09-17 09:08:46 +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
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
c56b808d7a
feat(renderer/map): support animated tiles loop playback ( fixes #43 )
2026-09-17 06:34:02 +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
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
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
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
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
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
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
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
7d8629cd73
feat(scene): 衔接 NPC 数据至引擎层及交互面板验证 ( fixes #6 )
...
建立 NPC token 到独立显示名称的转译字典。将打包导出的 NPC 集合统一注水到 GameEngine 模型内以生效交互半径;同时增加 Canvas UI 外围的绝对定位 DOM 生成层来渲染 NPC 头顶铭牌名称,以及挂载对应的对话展示面板供 engine.dialog 同步刷新。更新 E2E 环境验证逻辑检测交互健壮性。
2026-09-14 13:14:34 +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