diablo2-web/tools/browser
troytt 820ca12221 refactor: split into isolated common/netproto/server/client/baker modules
Split the mixed src/ and scripts/ trees into five compiler-enforced modules
aligned with the 1.13c DLL architecture, plus tools/:

- src/common/   (D2Common/Storm/D2Lang): formats, MPQ, data tables, DRLG,
                items/skills/units metadata, RNG, pack contracts. No DOM/Node.
- src/netproto/ (D2Net/Bnclient/D2MCPClient): BNCS/MCP/D2GS protocol; external
                code imports only src/netproto/index.ts.
- src/server/   (D2Game): authoritative simulation + LocalLoopbackAdapter.
                All 23 reverse deps into client removed; no Math.random /
                Date.now / performance.now.
- src/client/   (D2Client/D2Gfx/D2Win/D2Launch/D2Sound): render, UI, frontend,
                audio, session, toolbar and the play.html entry.
- src/baker/    Node-only offline packers/extractors + unified `npm run bake`.
- tools/        bot, replay, unlock, verify/audit/benchmark CLIs.

play.html is now the only HTML entry; index.html, acts*.html and bnet.html
plus legacy entry glue (src/main.ts, act-scene, net-scene, lockstep src/net)
are removed.

Adds tsconfig.{common,netproto,server,client,baker}.json wired into
`npm run typecheck`, and tests/arch/boundaries.test.ts enforcing the module
boundaries with zero whitelist exceptions.

Verification: typecheck 6/6 clean; vitest 556 files / 6640 tests pass;
npm run build clean; bake --verify (136/136 levels, universal DT1s);
netproto-replay --strict 0 unknown/opaque/errors; d2-bot walk hardSnaps=0;
audit-levels 136/136; verify-play 9/9 gates at 800x600 and 1024x768.
2026-10-01 00:10:18 +00:00
..
checks refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00
README.md refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00
inspect-page.mjs refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00
many-pages.mjs refactor: split into isolated common/netproto/server/client/baker modules 2026-10-01 00:10:18 +00:00

README.md

浏览器验证工具(零依赖)

这些脚本用 Node 内置的 WebSocket 直接讲 CDP(Chrome DevTools Protocol), 不需要 puppeteer/playwright 这类依赖。目的只有一个:给"页面里到底发生了什么"留下证据, 因为像素和截图的判断在这套环境里不可靠(模型读不了图,readPixels 在合成后会返回清空缓冲)。

inspect-page.mjs —— 单页检查

CHROME=/root/.cache/ms-playwright/chromium-1223/chrome-linux64/chrome
tsx scripts/browser/inspect-page.mjs "$CHROME" \
  "http://127.0.0.1:5173/acts.html" \
  scripts/browser/checks/act-state.js --screenshot /tmp/act.png

它会启动一个 headless Chromium(SwiftShader 软件 WebGL2),打开 URL,等待页面加载, 把 check 脚本的返回值打印成 JSON,可选截图。

many-pages.mjs —— N 页同时跑(联机用)

tsx scripts/browser/many-pages.mjs "$CHROME" \
  "http://127.0.0.1:5173/net.html?data=samples/fixtures&ws=ws://127.0.0.1:8787&peers=3&peer=0" \
  "http://127.0.0.1:5173/net.html?data=samples/fixtures&ws=ws://127.0.0.1:8787&peers=3&peer=1" \
  "http://127.0.0.1:5173/net.html?data=samples/fixtures&ws=ws://127.0.0.1:8787&peers=3&peer=2" \
  --seconds 20 --shot /tmp/coop

每一页是一个独立的 Chromium 实例(标签页级复用在这套 CDP 流程里会把两次导航混在一起, 曾经导致"两页都跑成了 peer 0"这种假象)。它按页分发不同的方向键,让每个角色只受本页按键驱动, 结束时打印每页的 window.__d2web* 状态。判定靠状态字段,不靠看图。

checks/ —— 页面内脚本

文件 目标页面 做什么
walk-state.js walk.html 读真实 MPQ(暗黑1 spawn.mpq)后的就绪状态、tick、drawCalls、glError
net-state.js net.html 单机模式的状态:世界 tick、角色数、怪物数、tps
act-state.js acts.html(?live=1) 读归档路径:归档是否读入、DS1 象限、格数、瓦片引用缺失数、图集尺寸,以及按键后的位移与朝向
act-pack-state.js acts.html(默认) 资源包路径:是否走 pack、首帧装了几页(证明懒加载)、出画面毫秒数、按键位移与朝向

已知限制:沙盒里 Chrome 没有网络

在某些受限环境(例如代理后的自动化沙盒)里,headless Chrome 无法访问任何 HTTP 地址, 包括 127.0.0.1。症状很有迷惑性:

  • CDP 的 /json 目标列表会显示你要的 URL(Chrome 乐观地先报告待定导航)
  • 但页面里 location.href 仍是 about:blank、document.readyState 是 complete
  • 页面内 fetch() 报 TypeError: Failed to fetch
  • Page.navigate 直接挂住不返回
  • 同一台机器上 curl 一切正常

这和「场景启动失败」长得一模一样,很容易让人去应用代码里找根本不存在的 bug。 判定方法:如果 location.href === 'about:blank' 而目标列表里的 URL 是对的, 那就是环境问题,不是页面问题。

此时请改用纯逻辑测试(tests/ 下的 vitest),它们覆盖了同样的断言且不需要浏览器。 tests/engine-save-load.test.ts 就是这样从退役的 map-save-load.js 迁移过来的。