diablo2-web/PROJECT.md

18 KiB
Raw Blame History

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/)

  • ArchiveSource interface in src/common/data/data-registry.ts:
    • readText(mpqRelativePath: string): Promise<string | null> | string | null
    • readBytes(mpqRelativePath: string): Promise<Uint8Array | null> | Uint8Array | null
  • D2DataRegistry and getSharedDataRegistry(source: ArchiveSource) MUST require an explicit ArchiveSource (no default /usr/local/google/home/taodao/d2-data path, no node:fs, no window/fetch). Missing source throws an explicit Error immediately (fail-fast per AGENTS.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) (enforcing Blank.dt1, InvisWal.dt1 with style=49 sequence=7, Warp.dt1) and validatePackManifest(manifest) (enforcing missingFiles === 0 and missingTiles === 0).

3. src/client/map/map-service.ts ↔ tools/ & Browser

  • MapService.fromLoader(loader: PackedTileBundleLoader): Promise<MapService> and MapService.fromPackedUrls(baseUrl: string): Promise<MapService> live in src/client/map/map-service.ts with zero node:fs or node:path imports.
  • Node filesystem loading (createNodePackedTileLoader(dirPath) / loadMapServiceFromPackedDir(dirPath)) lives in tools/lib/node-map-loader.ts (or src/baker/io/).

4. src/netproto/index.ts ↔ src/server/adapter/LocalLoopbackAdapter.ts & src/client/

  • LocalLoopbackAdapter in src/server/adapter/LocalLoopbackAdapter.ts implements GameServerAdapter from src/netproto/index.ts:
    • connect(): Promise<void>
    • disconnect(): void
    • readonly isConnected: boolean
    • readonly tick: number
    • send(cmd: ClientCommand): void
    • onEvent(handler: (ev: ServerEvent) => void): () => void
    • onRawPacket?(handler: (direction: 'S2C' | 'C2S', opcode: number, bytes: Uint8Array, decoded?: ServerEvent | ClientCommand) => void): () => void
  • src/client/** never imports src/server/**; src/client/main.ts consumes any GameServerAdapter via OnlineSession.

Code Layout & Write Ownership

  • Canonical src/ directories (post-M3): ONLY src/common/, src/netproto/, src/server/, src/client/, src/baker/. All legacy top-level src/ 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, and M3 execute sequentially on the codebase so git mv and 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.