18 KiB
18 KiB
Project: diablo2-web Module Split & Single Entry Consolidation
Architecture
diablo2-web is refactored in-repo on /usr/local/google/home/taodao/diablo2-web/.worktrees/module-split into five strictly isolated, compiler-enforced TypeScript modules under src/ plus developer/verification CLI scripts under tools/ and a single Web HTML entry (play.html):
diablo2-web/
├── play.html # Sole HTML entry point (loads /src/client/main.ts)
├── src/
│ ├── common/ # D2Common.dll + Storm.dll + D2Lang.dll (ES2022, types: [], zero DOM/Node)
│ │ ├── mpq/ # Pure MPQ archive & decompression (no file-source.ts)
│ │ ├── formats/ # DC6, DCC, DT1, DS1, COF, PL2, PAL, CEL, PCX, TBL, AnimData, Bitstream
│ │ ├── data/ # Excel .txt data-registry (injected ArchiveSource, no hardcoded d2-data) & canonical tables
│ │ ├── drlg/ # 1:1 1.13c DRLG map generation, collision masks, preset units, warps, light-grid
│ │ ├── world/ # acts, act-variants, levels-meta, level-links, level-names-zh, world-graph, map, d2map, warp-tiles, automap-table, animated-tiles timing
│ │ ├── items/ # Static item/affix/set/unique/runeword/gem tables, drop-tables, inventory-model, belt-model, item-bridge, item-cost, item-upgrade, item-tooltip, ground-item-physics
│ │ ├── skills/ # Static Skills.txt/Missiles.txt/Overlay.txt tables, skill-tree, skill-calc-engine, formulas, calc-ast, skills-meta
│ │ ├── units/ # classes, character, monster-mapping, monsters-data, npc-table, object-lookup, objects, plr-mode, pure monster-art compositor
│ │ ├── i18n/ # Pure .tbl lookup (lookupTbl, TblLang, GameLang) & tooltip-i18n decoupled from UI lang state
│ │ ├── rng/ # d2-rng, SplitMix64 pickVariant (variants.ts)
│ │ └── pack-contract/ # Shared tile/scene/entity pack manifest schemas & fail-fast universal DT1 / missingFiles===0 validators
│ ├── netproto/ # D2Net.dll + Bnclient.dll + D2MCPClient.dll (strictly isolated; external code imports only src/netproto/index.ts)
│ ├── server/ # D2Game.dll (ES2022 + WebWorker, types: [], zero DOM, zero client/baker imports, zero Math.random/Date.now/performance.now)
│ │ ├── engine/ # GameEngine, world-arena, combat-pipeline, missile-engine, state-bus, aura-scanner, summon-manager, anim-dispatcher, environment, stat-list, state-hash
│ │ ├── ai/ # Monster AI, spawning, monster-rooms, monster-streaming
│ │ ├── skills/ # Skill registry (static imports, zero node:*), skills simulation, impl/{ama,ass,bar,dru,nec,pal,sor,uni}
│ │ ├── items/ # drop-pipeline, treasure-class, treasure-engine, auto-tc, affix-generator, automagic, ground-items, runeword-socket, gems, vendor-generator, vendor-service
│ │ ├── world/ # objects interaction, quests, npc, portal, class-starter-profiles
│ │ ├── save/ # .d2s character save read/write
│ │ ├── scene/ # chat-commands, scene-links-nav, scene-population, skill-caster, weapon-combat
│ │ └── adapter/ # LocalLoopbackAdapter implementing GameServerAdapter from src/netproto/index.ts
│ ├── client/ # D2Client.dll + D2Gfx.dll + D2Win.dll + D2Launch.dll + D2Sound.dll (ES2022 + DOM + DOM.Iterable, types: [], zero Node/server/baker imports)
│ │ ├── main.ts # Sole browser entry point for play.html
│ │ ├── render/ # WebGL2/Canvas renderer, atlas, cull, roofs, unit-sprites, missile-renderer, missiles-meta, overlays-meta, monster-art atlas loader, char-appearance
│ │ ├── ui/ # HUD, inventory UI, belt UI, character-sheet, control-bar, cursor, font, globes, ground-labels, hotkeys, minimap, skill-tree-panel, world-panels, baked-ui-meta
│ │ ├── frontend/ # Title, login, character-select, lobby state & renderer (flow-state.ts, frontend-renderer.ts)
│ │ ├── scene/ # actor-animator, animation, entity-art-loader, ground-item-renderer, lighting-presets, missile-overlay-renderer, mouse-controller, object-animation, overhead-labels, pack-loader, scene-state, toolbar-chat-ui, transition, world-renderer
│ │ ├── audio/ # AudioManager & sound-service
│ │ ├── sim/ # Camera, Input, Loop
│ │ ├── i18n/ # Browser URL/DOM/localStorage language state & TOOLBAR_I18N
│ │ └── {assets,automap,input,inspector,map,motion,session,settings,toolbar,ui-model,view,world}/
│ └── baker/ # Node-only offline asset & table baker (ES2022, types: ["node"], zero DOM, imports only src/baker/** and src/common/**)
│ ├── cli.ts # Unified CLI (npm run bake -- <target> | --verify)
│ ├── io/ # file-source.ts, png.ts, drlg-mpq-source.ts, tbl-writer.ts, publish-packs.ts
│ ├── packers/ # 16 pack-*.ts scripts + rebuild-pack-index-and-monsters.ts (incl. SuperUnique minion DCC/DC6 closure)
│ ├── extract/ # 11 table/font/code extractors (extract-d2net-tables, extract-d2moo-tables, gen-drlg-ids, port-object-lookup, build-d2-ttf, etc.)
│ └── audit/ # verify-packs.ts, verify-entity-packs.ts, verify-tiles.ts, verify-tile-alignment.ts
├── tools/ # Developer, Bot, Replay & Verification CLI Tools (d2-bot.ts, netproto-replay.ts, d2s-unlock.ts, verify-play.ts, audit-*.ts, etc.)
└── tests/ # Unit, architecture boundary, integration, and E2E test suites
Strict Dependency DAG
| From ↓ Imports → | src/common/** |
src/netproto/index.ts |
src/server/** |
src/client/** |
src/baker/** |
node:* |
DOM Globals |
|---|---|---|---|---|---|---|---|
src/common/** |
✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
src/netproto/** |
✗ | ✓ (internal) | ✗ | ✗ | ✗ | Only transport/node-tcp-stream.ts (excluded from browser) |
✗ |
src/server/** |
✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ |
src/client/** |
✓ | ✓ (index.ts only) |
✗ | ✓ | ✗ | ✗ | ✓ |
src/baker/** |
✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ |
tools/** |
✓ | ✓ | ✓ | ✓ (audit/bot) | ✓ | ✓ | ✓ (Playwright) |
Feature Inventory
Every feature discovered in the Phase 0 Survey (from ORIGINAL_REQUEST.md, module_split_plan.md, AGENTS.md, and Survey Reports 1, 2, 3) is inventoried below with its assigned milestone. Zero features are unassigned.
| # | Feature | Description | Milestone | Source |
|---|---|---|---|---|
| F1 | src/common/ Pure Module & Data Registry Decoupling |
Extract pure MPQ (mpq/ without file-source.ts), binary decoders (formats/), Excel .txt data-registry (with injected ArchiveSource and zero hardcoded /usr/local/google/home/taodao/d2-data), 1:1 DRLG (drlg/), world topology (world/), static items/skills/units (items/, skills/, units/), pure .tbl lookup (i18n/), deterministic RNG & SplitMix64 pickVariant (rng/), compiling cleanly under tsconfig.common.json (lib: ["ES2022"], types: []). |
M1 | R1.1, Survey 1 & 2 |
| F2 | Shared Pack Contract (src/common/pack-contract/) |
Extract PackedDrlgBundleManifest, PackedTileCatalogIndex, PackIndex, PackSceneJson, EntityMeta, ClipMeta, and fail-fast universal DT1 (Blank.dt1, InvisWal.dt1 style=49 seq=7, Warp.dt1) + missingFiles === 0 validators into src/common/pack-contract/ to break client <-> baker coupling. |
M1 | R1.1, R1.5, Survey 2 & 3 |
| F3 | Elimination of All 23 Server-to-Client Reverse Dependencies | Extract pure inventory/belt/socket container models (inventory-model.ts, belt-model.ts, item-bridge.ts), CharacterAttributes, resolvePlayerCharToken, HotkeySkillEntry, getSkillName, subTileToScene, and explicit lang: TblLang parameters into src/common/, and split mixed files (monsters.ts, skills.ts, engine.ts, world-arena.ts, skill-caster.ts, monster-art.ts, animated-tiles.ts, i18n/lang.ts) so server simulation has 0 imports from UI/render/client. |
M1 | R1.3, Survey 1 & 2 |
| F4 | src/baker/ Offline Baker Module & Unified CLI (src/baker/cli.ts) |
Migrate src/mpq/file-source.ts, png.ts, drlg-mpq-source.ts, tbl-writer.ts, publish-packs.ts, 16 pack-*.ts packers + rebuild-pack-index-and-monsters.ts, 11 extractors, and 4 offline verifiers into src/baker/; implement src/baker/cli.ts (npm run bake -- <target> and npm run bake -- --verify); enforce tsconfig.baker.json (lib: ["ES2022"], types: ["node"], zero imports from src/client/** or src/server/**). |
M2 | R1.5, R3, Survey 3 |
| F5 | tools/ CLI Consolidation & Legacy Relay Deletion |
Delete legacy P2P lockstep server scripts (scripts/net-relay.ts, scripts/net-server.ts, npm run net); migrate all 93 developer, bot, replay, unlock, benchmark, and verification tools (d2-bot.ts, netproto-replay.ts, d2s-unlock.ts, verify-play.ts, audit-levels-browser.ts, lib/drlg-oracle.ts, etc.) from scripts/ to tools/; remove scripts/ directory and update package.json scripts & test imports. |
M2 | R1.6, R2, Survey 3 |
| F6 | src/server/ Authoritative Simulation Module & LocalLoopbackAdapter |
Consolidate all server simulation files (engine/, ai/, skills/, items/, world/, save/, scene/) into src/server/; remove dynamic node:* in skills/registry.ts; verify 0 occurrences of Math.random, Date.now, performance.now, or DOM globals under tsconfig.server.json (lib: ["ES2022", "WebWorker"], types: []); implement src/server/adapter/LocalLoopbackAdapter.ts implementing GameServerAdapter from src/netproto/index.ts. |
M3 | R1.3, R3, Survey 1 & 2 |
| F7 | src/client/ Pure Browser Module & MapService Node Decoupling |
Consolidate render/, ui/, frontend/, audio/, sim/, scene/frontend/, transition.ts, client i18n/lang.ts, and client art/animation modules into src/client/; remove node:fs/node:path from src/client/map/map-service.ts via injected PackedTileBundleLoader; decouple HudManager and WorldRenderer from GameEngine; enforce tsconfig.client.json (lib: ["ES2022", "DOM", "DOM.Iterable"], types: [], zero imports from src/server/**, src/baker/**, or node:*). |
M3 | R1.4, R3, Survey 2 |
| F8 | Single HTML Entry (play.html) Consolidation & Legacy Entry Removal |
Delete index.html, acts.html, acts-zh.html, acts-en.html, bnet.html, src/main.ts, src/walk.ts, src/frontend/bnet-main.ts, src/scene/act-scene.ts, src/scene/net-scene.ts, src/scene/backend/net-simulation.ts, and src/net/*; preserve computeLockstepHash in src/server/engine/; add D2Exocet/D2Formal436 @font-face CSS to play.html; configure vite.config.ts with play.html as sole input; update all act-scene.ts and HTML-reading tests. |
M3 | R2, Survey 2 & 3 |
| F9 | Compiler-Level Isolation (tsconfig.*.json) & Zero-Exception Boundary Gate (tests/arch/boundaries.test.ts) |
Wire tsconfig.common.json, tsconfig.netproto.json, tsconfig.server.json, tsconfig.client.json, tsconfig.baker.json, and root tsconfig.json into npm run typecheck; implement tests/arch/boundaries.test.ts enforcing all 7 R3 boundary rules with 0 whitelist exceptions; ensure src/ contains only {common,netproto,server,client,baker} and npm run build succeeds with 0 Node-builtin externalization warnings. |
M3 | R3, Survey 1, 2 & 3 |
| F10 | Full 1.13c Parity, Replay, Baker, Walk, 136-Level DRLG & Headless play.html E2E Verification |
Pass 100% of npx vitest run, npm run bake -- --verify, tools/netproto-replay.ts --strict (unknownPackets === 0, opaquePackets === 0, protocolErrors === 0), tools/d2-bot.ts walk test (hardSnaps === 0) & 136-level DRLG / 5-Act 20-WP audit-levels, tools/verify-play.ts in 800×600 & 1024×768, and Tier 1–5 E2E test suites. |
M4 | R4, Acceptance Criteria, Survey 3 |
Milestones
| # | Name | Scope | Dependencies | Status |
|---|---|---|---|---|
| M1 | src/common/, pack-contract & Shared Model Extraction |
F1, F2, F3: Build AST move/rewrite tool (tools/refactor/move.ts); populate src/common/ (mpq, formats, data, drlg, world, items, skills, units, i18n, rng, pack-contract); split mixed files and extract shared models to eliminate all 23 server -> client reverse dependencies; pass tsc -p tsconfig.common.json --noEmit and npx vitest run. |
none | DONE (src/common/*, pack-contract, 0 errors in tsc -p tsconfig.common.json --noEmit) |
| M2 | src/baker/ & tools/ Migration + Unified Baker CLI |
F4, F5: Migrate file-source.ts + 31 baking/extraction/audit scripts to src/baker/; implement src/baker/cli.ts; delete net-relay.ts & net-server.ts; migrate 93 CLI/bot/replay/verify scripts to tools/ and remove scripts/; update package.json scripts and test imports; pass tsc -p tsconfig.baker.json --noEmit and npm run bake -- --verify. |
M1 | DONE (src/baker/*, src/baker/cli.ts, tools/*, scripts/ removed, tsc -p tsconfig.baker.json & bake -- --verify exit 0) |
| M3 | src/server/, src/client/, LocalLoopbackAdapter, play.html Consolidation & Boundary Gate |
F6, F7, F8, F9: Consolidate src/server/ + LocalLoopbackAdapter; consolidate src/client/ + decouple MapService from node:fs/node:path; codemod act-scene.ts test imports and delete index.html, acts*.html, bnet.html, act-scene.ts, net-scene.ts, src/net/*, and legacy top-level src/ dirs; wire all 5 tsconfig.<module>.json configs; implement tests/arch/boundaries.test.ts with 0 exceptions; pass all tsc checks, npm run build, and npx vitest run. |
M2 | DONE (src/ contains only {baker,client,common,netproto,server}, 6 tsconfigs 0 errors, boundaries.test.ts 0 exceptions, dist/play.html built cleanly) |
| M4 | Final Milestone: E2E Test Suite Pass (Tiers 1–4), Live Parity Gates & Tier 5 Adversarial Hardening | F10: Phase 1 — Pass 100% of E2E test suite (TEST_READY.md Tiers 1–4) and all live/offline parity gates (vitest, bake -- --verify, netproto-replay --strict, d2-bot walk & 136-level audit-levels, verify-play at 800×600 & 1024×768). Phase 2 — Tier 5 white-box adversarial coverage hardening & 2B Gate (2 Reviewers, 2 Challengers, 1 Forensic Auditor). |
M3, E2E Track | DONE (Phase 1 & Phase 2 passed: 556 Vitest files / 6,640 tests, 138/138 Tiers 1–5 E2E tests, 9/9 offline & live gates, Reviewers APPROVE, Challengers APPROVE, Forensic Auditors CLEAN) |
| E2E | E2E Testing Track (TEST_INFRA.md, Tiers 1–5 Test Suite, TEST_READY.md) |
Opaque-box, requirement-driven 4-tier test suite + Tier 5 adversarial regression suite covering F1–F10 in tests/e2e-module-split/** + TEST_INFRA.md + TEST_READY.md. |
none | DONE (TEST_INFRA.md, TEST_READY.md, 138/138 tests passing in tests/e2e-module-split/tier{1..5}-*.test.ts) |
Interface Contracts
1. src/common/data/data-registry.ts ↔ Callers (src/server/, src/client/, src/baker/)
ArchiveSourceinterface insrc/common/data/data-registry.ts:readText(mpqRelativePath: string): Promise<string | null> | string | nullreadBytes(mpqRelativePath: string): Promise<Uint8Array | null> | Uint8Array | null
D2DataRegistryandgetSharedDataRegistry(source: ArchiveSource)MUST require an explicitArchiveSource(no default/usr/local/google/home/taodao/d2-datapath, nonode:fs, nowindow/fetch). Missingsourcethrows an explicitErrorimmediately (fail-fast perAGENTS.md§3.1).
2. src/common/pack-contract/ ↔ src/baker/ & src/client/
- Exports
PackedDrlgBundleManifest,PackedTileCatalogIndex,PackedTilePlacement,PackedDt1TileEntry,PackedDt1LibraryEntry,PackIndex,PackIndexEntry,PackObject,PackObjectFrame,PackSceneJson,EntityMeta,ClipMeta. - Exports fail-fast validators
validateUniversalDt1Libraries(libraries)(enforcingBlank.dt1,InvisWal.dt1with style=49 sequence=7,Warp.dt1) andvalidatePackManifest(manifest)(enforcingmissingFiles === 0andmissingTiles === 0).
3. src/client/map/map-service.ts ↔ tools/ & Browser
MapService.fromLoader(loader: PackedTileBundleLoader): Promise<MapService>andMapService.fromPackedUrls(baseUrl: string): Promise<MapService>live insrc/client/map/map-service.tswith zeronode:fsornode:pathimports.- Node filesystem loading (
createNodePackedTileLoader(dirPath)/loadMapServiceFromPackedDir(dirPath)) lives intools/lib/node-map-loader.ts(orsrc/baker/io/).
4. src/netproto/index.ts ↔ src/server/adapter/LocalLoopbackAdapter.ts & src/client/
LocalLoopbackAdapterinsrc/server/adapter/LocalLoopbackAdapter.tsimplementsGameServerAdapterfromsrc/netproto/index.ts:connect(): Promise<void>disconnect(): voidreadonly isConnected: booleanreadonly tick: numbersend(cmd: ClientCommand): voidonEvent(handler: (ev: ServerEvent) => void): () => voidonRawPacket?(handler: (direction: 'S2C' | 'C2S', opcode: number, bytes: Uint8Array, decoded?: ServerEvent | ClientCommand) => void): () => void
src/client/**never importssrc/server/**;src/client/main.tsconsumes anyGameServerAdapterviaOnlineSession.
Code Layout & Write Ownership
- Canonical
src/directories (post-M3): ONLYsrc/common/,src/netproto/,src/server/,src/client/,src/baker/. All legacy top-levelsrc/directories (src/game/,src/scene/,src/mpq/,src/formats/,src/data/,src/render/,src/ui/,src/frontend/,src/audio/,src/sim/,src/i18n/,src/net/) must be completely removed by the end of M3. - Concurrent Write Ownership:
M1,M2, andM3execute sequentially on the codebase sogit mvand import codemods never collide.- The parallel E2E Testing Orchestrator owns EXCLUSIVELY
/usr/local/google/home/taodao/diablo2-web/.worktrees/module-split/TEST_INFRA.md,/usr/local/google/home/taodao/diablo2-web/.worktrees/module-split/TEST_READY.md, and/usr/local/google/home/taodao/diablo2-web/.worktrees/module-split/tests/e2e-module-split/**. It MUST NOT edit any file outside those paths.