diablo2-web/PROJECT.md

104 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Project: Diablo II: Lord of Destruction (v1.13c) Pure-Browser Online Client (`diablo2-web` `feat/netproto`)
## Architecture
- **Pluggable, Zero-Dependency Network & Protocol Layer (`src/netproto/`)**:
- Strictly isolated from DOM, Node.js, rendering, and game simulation (`tsconfig.netproto.json` sets `lib: ["ES2022"], types: []` for core `src/netproto/**`, excluding only `transport/ws-stream.ts` and `transport/node-tcp-stream.ts`).
- **L0 Transport (`src/netproto/transport/`)**: `ByteStream` interface implemented by `MemoryStream` (testing/offline replay), `WsStream` (browser WebSocket), and `NodeTcpStream` (Node CLI bot), plus `EndpointResolver` (`wsBridgeResolver` mapping `6112 -> /d2net/bnet`, `6113 -> /d2net/realm`, `4000 -> /d2net/game`; `tcpResolver`; and `mixedResolver` using direct TCP `101.37.117.183:6112` for BNCS and WSS for MCP/D2GS so Node bots do not consume the 5-per-IP `127.0.0.1` WSS bridge quota).
- **L1–L3 Protocol Engines (`src/netproto/{core,crypto,bncs,mcp,d2gs}/`)**:
- `crypto/`: Battle.net broken SHA-1 (`xsha1`), doubleHash, 1.13c CheckRevision constants (`exeVersion=0x01000d00`, `exeHash=0x7686beca`, `exeInfo="Game.exe 03/09/10 04:10:51 61440"`), and 16-char D2DV/D2XP CD-Key decode & hash.
- `bncs/`: `0x01` greeting + `0xFF | id | u16 len` framing, OLS auth (`0x50`, `0x25`, `0x51`, `0x3D`, `0x3A`), chat (`0x0A..0x0F`), and realm handoff (`0x40`, `0x3E`).
- `mcp/`: `0x01` greeting + `u16 len | u8 id` framing (`D2MCPClient.dll`), `0x01` STARTUP, `0x19` CHARLIST2 (with 33-byte `charStatString` parser), `0x02` CHARCREATE, `0x0A` CHARDELETE, `0x18` CHARUPGRADE, `0x07` CHARLOGON, `0x03` CREATEGAME, `0x04` JOINGAME, `0x05` GAMELIST, `0x06` GAMEINFO, `0x12` MOTD.
- `d2gs/`: Generated 1.13c S→C packet size table (`D2Net.dll` `0x6fbfa900` 181 entries + 14 variable-length formulas at `0x6fbf6750` and C→S `0x6fbfabd8` 113 entries) and canonical Huffman bit-length table (`Fog.dll` `0x6ff7f210` + `Fog.10219`/`Fog.10224` MSB-first decompressor); initial uncompressed `[0xAF, 0x01]` greeting followed by 1/2-byte `totalFrameSize` compressed blocks (`compressedPayloadLen = totalFrameSize - headerSize`); fail-fast `ProtocolError` on unknown packet IDs or size mismatches; complete S→C (`0x00..0xB4`) and C→S (`0x01..0x70`) codecs, `0x9C`/`0x9D` LSB-first item bitstream decoder (`ItemDataTables` injection), and `D2gsSession` (`0x68` 37-byte GameLogon, `0x6B` EnterGame, 5s 13-byte `0x6D` Ping).
- **L4 Neutral Domain & Flow (`src/netproto/domain/`, `src/netproto/flow/`, `src/netproto/index.ts`)**:
- `src/netproto/index.ts` exports ONLY protocol-neutral domain types (`ServerEvent`, `ClientCommand`, `GameServerAdapter`, `D2OnlineFlow`, `EndpointResolver`, `TextCodec`, `PacketTap`, lobby models). External code (`src/client/**`, `src/ui/**`, `src/scene/**`, `src/render/**`, `src/frontend/**`) never imports `src/netproto/{bncs,mcp,d2gs,crypto}/**`.
- **Runtime Seed-Driven DRLG & Tile Atlas Pipeline (`scripts/pack-tiles.ts`, `src/game/drlg/`, `src/client/map/`)**:
- `scripts/pack-tiles.ts` packs all `LvlTypes.txt` DT1s across Acts 1–5 plus the 3 mandatory universal DT1s (`Blank.dt1`, `InvisWal.dt1` with `style=49 sequence=7` collision floor, `Warp.dt1`) and compact DRLG `.bin`/`.ds1`/DT1-header metadata into `public/act-packs/tiles/`.
- `map-worker.ts` & `map-service.ts` run `createDrlgActSession` on `(act, mapSeed, difficulty, areaId)` from `LoadAct` and `RoomReveal` to produce exact pinned tile references, 16-bit sub-tile collision grids (`COLLIDE_MASK_INVALID` on void cells), warps, waypoints, and preset units matching D2GS cell-for-cell.
- **Packet-Driven Browser Client, Dual Viewport, Toolbar, Lobby, Audio & Inspector (`play.html`, `src/client/`, `src/ui/`, `src/frontend/`)**:
- `ClientWorld` maintains the authoritative server mirror (`units`, `modes`, `directions`, `stats`, `states`, `skills`, `inventory/equipment/belt/stash/cube/cursor/trade/merc`).
- `ViewportProfile` supports `800×600` (1.13c native) and `1024×768` (HUD anchored at native pixel dimensions with `(barOffsetX=112, barOffsetY=168)`, accurate screen-to-world mouse picking).
- Top DOM `Toolbar` (`src/client/toolbar/`) & `ClientSettings` (`localStorage['d2web.settings.v1']`): connection status/RTT + 5-per-IP/TLS diagnostics, Server Host editor (rejecting bare IP over `https:`), live `中`/`EN` toggle, lighting level selector (`auto`/1.13c, `LIGHTING_PRESETS`, `fullbright`), automap reveal (`off`/`level`/`act`), viewport toggle (`800×600`/`1024×768`), and Packet Inspector toggle.
## Feature Inventory
Every feature from the Survey phase and `ORIGINAL_REQUEST.md` appears here with its assigned milestone. No feature is left unassigned.
| # | Feature | Description | Milestone | Source |
|---|---------|-------------|-----------|--------|
| F1 | Module Isolation & TypeScript Boundary Enforcement | `tsconfig.netproto.json` (`lib: ["ES2022"], types: []`), `tests/netproto/boundary.test.ts`, and `src/netproto/index.ts` exporting only protocol-neutral interfaces (`ServerEvent`, `ClientCommand`, `GameServerAdapter`, `D2OnlineFlow`, `EndpointResolver`). | M1 | R1, Survey 1 |
| F2 | Core Primitives, Framing, `TextCodec` & Transport Streams | `ByteReader`, `ByteWriter`, `BitReader`, `BitWriter`, `ProtocolError`, `TextCodec` (defaulting to `gbk` via `TextDecoder`/`TextEncoder`), `Clock`, `PacketTap`, `ByteStream`, `MemoryStream`, `WsStream`, `NodeTcpStream`, `EndpointResolver` (`wsBridgeResolver`, `tcpResolver`, `mixedResolver`). | M1 | R1, R6, Survey 1 & 3 |
| F3 | Deterministic 1.13c `D2Net.dll` / `Fog.dll` Table Extraction | `scripts/extract-d2net-tables.ts` generating `src/netproto/d2gs/tables/s2c-sizes.ts` (`0x6fbfa900` 181 S→C sizes + `0x6fbf6750` 14 variable-length formulas + `0x6fbfabd8` 113 C→S sizes) and `huffman.ts` (`Fog.dll` `0x6ff7f210` 256-byte bit-lengths + `Fog.10219` canonical builder) with SHA-1s and offsets. | M1 | R1, Survey 2 |
| F4 | Battle.net Crypto & BNCS Session State Machine | `xsha1` broken SHA-1, `doubleHash`, CheckRevision 1.13c constants (`0x01000d00`, `0x7686beca`, `"Game.exe 03/09/10 04:10:51 61440"`), 16-char D2DV/D2XP CD-Key decode & hash, BNCS `0xFF | id | u16 len` framing and packets (`0x50`, `0x25`, `0x51`, `0x3D`, `0x3A`, `0x0A..0x0F`, `0x40`, `0x3E`). | M1 | R1, Survey 2 & 3 |
| F5 | MCP Realm Session & `D2OnlineFlow` Orchestration | MCP `0x01` greeting + `u16 len | u8 id` framing (`0x01` STARTUP, `0x19` CHARLIST2 with 33B stat string, `0x02` CHARCREATE, `0x0A` CHARDELETE, `0x18` CHARUPGRADE, `0x07` CHARLOGON, `0x03` CREATEGAME, `0x04` JOINGAME, `0x05` GAMELIST, `0x06` GAMEINFO, `0x12` MOTD), `D2OnlineFlow`, and initial `scripts/d2-bot.ts` account registration (`d2webbot1..3` in `scratch/bot-accounts.json`). | M1 | R1, R2, Survey 2 & 3 |
| F6 | D2GS Block Framing, Huffman Decompression & Session Handshake | Initial uncompressed `[0xAF, 0x01]` greeting, 1/2-byte `totalFrameSize` block header (`compressedPayloadLen = totalFrameSize - headerSize`), `Fog.10224` MSB-first Huffman decompressor (+ encoder for tests), `D2NET_10033` packet splitter with fail-fast `ProtocolError`, 37-byte `0x68` GameLogon, `0x6B` EnterGame, and 5s 13-byte `0x6D` Ping loop. | M2 | R1, R2, Survey 2 |
| F7 | Complete D2GS S→C (`0x00..0xB4`) & C→S (`0x01..0x70`) Codecs & `D2gsAdapter` | Full table-driven S→C and C→S packet definitions (`load`, `units`, `movement`, `stats`, `skills`, `states`, `items`, `npc`, `quest`, `waypoint`, `party`, `trade`, `chat`, `merc`, `misc`), `registry.ts` with `D2Client.dll` `0x6fb8de60` handler VAs, and `D2gsAdapter implements GameServerAdapter`. | M2 | R1, R2, Survey 2 |
| F8 | 1.13c `0x9C` / `0x9D` Item Bitstream Decoder | `src/netproto/d2gs/items/item-bitstream.ts` with injected `ItemDataTables` (`ItemStatCost`, `Armor`, `Weapons`, `Misc`), decoding 8B `0x9C` and 13B `0x9D` headers, ground `16+16`b coords vs container `4+4+3`b coords, `"gld "` gold piles, compact vs extended items, sockets, runewords, and `0x1FF`-terminated stat lists. | M2 | R1, R2, Survey 2 |
| F9 | Headless `ClientWorld` Authoritative Mirror | `src/client/world/{client-world,client-unit,stat-list,states,inventory,self,item-tables-provider}.ts` maintaining units, modes, directions, stats, states, skills, and all item containers (`inventory`, `equipment`, `belt`, `stash`, `cube`, `cursor`, `trade`, `merc`, `shop`, `ground`) from `ServerEvent`s. | M2 | R2, R4, Survey 1 & 2 |
| F10 | Live `.d2cap` Capture Corpus & Strict Offline Replay (`scripts/netproto-replay.ts`) | Complete `scripts/d2-bot.ts` (`register`, `login`, `chars`, `create-char`, `games`, `create-game`, `join`, `idle`, `record`, `--world`) + `scripts/netproto-replay.ts --strict` verifying `tests/fixtures/netproto/*.d2cap` with `unknownPackets === 0`, `opaquePackets === 0`, `protocolErrors === 0`, and >=60s live D2GS stability. | M2 | R2, Survey 2 & 3 |
| F11 | Seed-Independent Tile Library Asset Pack (`scripts/pack-tiles.ts`) | Offline packer generating per-act DT1 tile atlases and compact DRLG `.bin`/`.ds1`/DT1-header bundles covering all `LvlTypes.txt` DT1s across Acts 1–5 plus the 3 mandatory universal DT1s (`Blank.dt1`, `InvisWal.dt1` with `style=49 sequence=7`, `Warp.dt1`). | M3 | R3, Survey 1 & 3 |
| F12 | Runtime DRLG Web Worker, `LevelView`, `TileAtlas` & Motion/Pathfinding | `createDrlgActSession` (`drlg-dump.ts`, `drlg-map.ts`), `src/client/map/{level-view,tile-atlas,map-worker,map-service}.ts`, and `src/client/motion/{pathfind,predictor,interpolate}.ts` generating exact pinned tile references, `COLLIDE_MASK_INVALID` void cell blocking, warps, and preset units on `LoadAct` / `RoomReveal`. | M3 | R3, Survey 1 & 3 |
| F13 | Offline `.d2s` Waypoint/Quest Unlock (`scripts/d2s-unlock.ts`) & 136-Level Parity Audit | `scripts/d2s-unlock.ts` (backing up offline `d2webbot*` save on `101.37.117.183` `/usr/local/var/pvpgn/charsave/` before unlocking all 39 waypoints & act-transition quests and recomputing checksum) + bot continuous walk test (`0` `UnitReassign` pullbacks) + automated level audit verifying 100% coordinate parity across all reachable levels and all 136 level IDs. | M3 | R3, R6, Survey 1 & 3 |
| F14 | Persistent `ClientSettings` & Top DOM Toolbar (`src/client/toolbar/`) | `src/client/settings/{client-settings,settings-store}.ts` (`localStorage['d2web.settings.v1']` strict validation) and `src/client/toolbar/{toolbar.ts,toolbar.css}`: connection status/RTT + 5-per-IP & TLS diagnostics + Packet Inspector toggle, Server Host editor (rejecting bare IP over `https:` with explicit error, prompting mid-game), live `中`/`EN` toggle, lighting selector, automap reveal (`off`/`level`/`act`), and viewport toggle (`800×600`/`1024×768`). | M4 | R5, Survey 1 & 3 |
| F15 | Dual Viewport Profiles (`800×600` & `1024×768`), `SceneSource` & `LightingInputs` | `src/client/view/{viewport-profile,scene-source,unit-drawables}.ts` and non-breaking refactoring of `src/scene/frontend/{lighting-presets,world-renderer,pack-loader,mouse-controller}.ts` (adding `fullbright` preset and keeping `acts.html` 100% working). | M4 | R4, R5, Survey 1 |
| F16 | `play.html` Entry, Client Movement Prediction, Combat, Skills & Native Missile/Overlay FX | `play.html`, `src/client/{main,session/online-session,input/command-mapper}.ts` rendering terrain, units, client-predicted movement with smooth `UnitReassign` reconciliation, left/right-hand skill casting, and native DCC/DC6 missile & overlay FX (`Missiles.txt`, additive blending `Trans: 1` with zero black fringes). | M4 | R4, Survey 1 & 3 |
| F17 | Full HUD/Panel Rebinding (`HudModel`), `1024×768` Layout & Automap Reveal Modes | `src/client/ui-model/hud-model.ts`, `src/client/automap/automap-view.ts`, and `src/ui/**` updates (anchoring panels at native pixel dimensions in `800×600` and `1024×768` without stretching art; wiring inventory, equipment, belt, stash, Horadric Cube, char sheet, skill tree, quest log, waypoint menu, NPC dialog/shop/repair/gamble/imbue, merc, party, trade, chat, Esc menu, death overlay, 1.13c hotkeys, and `off`/`level`/`act` automap reveal). | M5 | R4, R5, Survey 1 |
| F18 | Live Battle.net Lobby Flow (`bnet.html` & `play.html` via `D2OnlineFlow`) | Connect `src/frontend/{flow-state,bnet-main}.ts` and `play.html` / `bnet.html` to real `D2OnlineFlow` sessions for login/register, channel chat, realm selection, char list/create/delete/convert, and game list/create/join, replacing mocks and transitioning into the in-game view. | M5 | R4, Survey 1 & 3 |
| F19 | Web Audio Engine (`Sounds.txt`, `SoundEnviron.txt`, HTTP Range & OPFS MPQ Caching) | `src/client/audio/sound-service.ts` and `src/client/assets/opfs-mpq.ts` wiring server `PlaySound` events, skill/combat/UI actions, footsteps, per-level background music, and NPC speech audio to `src/audio/manager.ts` via HTTP 206 Range MPQ streaming (`/diablo2/data/*.mpq`) and optional browser OPFS MPQ caching. | M5 | R4, Survey 1, 2 & 3 |
| F20 | Hardening, Rate Limiting, Packet Inspector & `.d2cap` Replay Panel, and `scripts/verify-play.ts` | Graceful disconnect handling to lobby with reason, C→S outbound packet rate limiter, in-browser Packet Inspector & `.d2cap` Replay Panel (`src/client/inspector/packet-inspector.ts`), and headless Chrome CDP verification script `scripts/verify-play.ts` testing the complete lobby, gameplay, audio, dual viewport, and Toolbar matrix. | M5 | R4, R5, Survey 1 & 3 |
## Milestones
| # | Name | Scope | Dependencies | Status |
|---|------|-------|-------------|--------|
| M1 | `M1-Netproto-Core-BNCS-MCP-Bot` | Phases N0–N1: `tsconfig.netproto.json`, `scripts/extract-d2net-tables.ts` (`s2c-sizes.ts`, `huffman.ts`), `src/netproto/{core,transport,crypto,bncs,mcp,domain,flow,ts,index.ts}`, `tests/netproto/boundary.test.ts`, unit tests, and initial `scripts/d2-bot.ts` (`register`, `login`, `chars`, `create-char`, `games`, `create-game`, `join` via `mixed` and `wss` resolvers, registering `d2webbot1..3`). | none | DONE (`worker_m1_m2_1` `3d592731-8e41-4232-8b8a-3c8ce484ed25`) |
| M2 | `M2-D2GS-Protocol-Items-ClientWorld-Replay` | Phases N2–N3: `src/netproto/d2gs/{compression,framing-s2c,framing-c2s,s2c/*,c2s/*,items/*,registry,session,adapter}.ts`, headless `src/client/world/**`, `scripts/d2-bot.ts` (`idle`, `record`, `--world` >=60s live session), `tests/fixtures/netproto/*.d2cap`, and `scripts/netproto-replay.ts --strict` (0 unknown/opaque/error packets). | M1 | DONE (`worker_m1_m2_1` `3d592731-8e41-4232-8b8a-3c8ce484ed25`, 949 records replayed with 0 unknown/opaque/errors) |
| M3 | `M3-Runtime-DRLG-TileAtlas-And-136Level-Parity` | Phase N4: `createDrlgActSession` (`src/game/drlg/{drlg-dump,drlg-map}.ts`), `scripts/pack-tiles.ts` (all `LvlTypes.txt` DT1s + 3 universal DT1s), `src/client/map/{level-view,tile-atlas,map-worker,map-service}.ts`, `src/client/motion/{pathfind,predictor,interpolate}.ts`, `scripts/d2s-unlock.ts`, and bot walk (`0` `UnitReassign`) + 136-level coordinate parity audit. | M1, M2 | DONE (core DRLG/TileAtlas/motion/d2s-unlock/136-level parity by `worker_m3_1` `7ce7de0c-9422-4066-963f-d8a4fbbf4604`; live bot walk/level audit in M5) |
| M4 | `M4-Browser-Client-Viewport-Toolbar-Combat-Lighting` | Phases N5–N6 + D1 & D5: `src/client/settings/**`, `src/client/toolbar/**`, `src/client/view/{viewport-profile,scene-source,unit-drawables}.ts`, `src/scene/frontend/{lighting-presets,world-renderer,pack-loader,mouse-controller}.ts` decoupling, `src/client/{automap,audio,assets,inspector,session/rate-limiter}/**`, `src/i18n/lang.ts`, and `src/ui/**` `1024×768` anchoring. | M1, M2, M3 | DONE (`worker_m4_1` `f4b38de0-c797-4090-adef-79975dd9303b`, 61/61 tests passing) |
| M5 | `M5-UI-Panels-Automap-Lobby-Audio-Inspector` | Phases N7–N10: `src/client/ui-model/hud-model.ts`, `src/client/input/command-mapper.ts`, `src/client/session/online-session.ts`, `src/client/main.ts`, `play.html`, `src/frontend/**` live `D2OnlineFlow` lobby integration, `scripts/d2-bot.ts` live walk & level audit, and `scripts/verify-play.ts`. | M4 | DONE (`worker_m5_1` `9abb87be-ab83-4c74-a11a-6e7744dc79db`, walk/audit-levels/verify-play 9/9 passed) |
| E2E | `E2E-Testing-Track` | Opaque-box requirement-driven E2E test infrastructure (`TEST_INFRA.md`) and 4-tier test suite (`tests/e2e-netproto/tier1..4.test.ts` covering all 20 features F1–F20 with >=100 Tier 1, >=100 Tier 2, >=20 Tier 3, >=10 Tier 4 test cases), publishing `TEST_READY.md`. | M1–M5 | DONE (`test_writer_e2e_1` `03a98a04-9653-4c1d-84e3-681be87b6f29`, 236/236 E2E tests passing) |
| M6 | `M6-Final-E2E-Pass-And-Tier5-Hardening` | Phase 1: Pass 100% of E2E tests (Tiers 1–4) and all Acceptance Criteria (`tsc`, `vitest`, `npm run build`, `boundary.test.ts`, `extract-d2net-tables.ts`, `d2-bot.ts`, `netproto-replay.ts --strict`, `verify-play.ts`). Phase 2: Tier 5 white-box adversarial coverage hardening (`tests/e2e-netproto/tier5-adversarial-coverage.test.ts` 32/32 passed, 268/268 Tiers 1–5 passed) + clean git worktree check. | M1–M5, E2E | DONE (`reviewer_m6_1` APPROVE, `reviewer_m6_2` APPROVE, `challenger_m6_1_gen2` APPROVE, `challenger_m6_2` APPROVE, `auditor_m6_1` CLEAN) |
| M7 | `M7-Round2-Victory-Audit-Remediation` | Remediate all 4 Round 1 Victory Auditor violations (`B1`: live `FrontendAsyncHandlers` lobby in `play.html` & `bnet.html`, deleting `enterInGameDemo`; `B2`: WebGL2 `SpriteRenderer` + `TileAtlasGpuCache` + `LightGrid` + `Minimap` on `#view`, deleting `renderWorldAndAutomapOn2dCanvas`; `B3`: canonical 167-opcode `decodeD2gsS2cPacket` + `D2gsS2cFramer` in `OnlineSession` with `0` `UnknownS2c` across all 4 `.d2cap` files + 9-Gate live `scripts/verify-play.ts`; `B4`: SSH `ControlMaster` `.d2s` unlock + live 5-Act 20-waypoint `scripts/d2-bot.ts audit-levels` tour across Acts 1–5 + `map-service.ts` Vite fix). | M1–M6 | DONE (`worker_r2_1`, `worker_r2_2`, `worker_r2_3` DONE; `reviewer_r2_1_gen2` APPROVE, `reviewer_r2_2` APPROVE, `challenger_r2_1_gen2` APPROVE, `challenger_r2_2` APPROVE, `auditor_r2_1` CLEAN, `auditor_r2_2` CLEAN) |
## Interface Contracts
### 1. `src/netproto/index.ts` ↔ `src/client/**` & `src/frontend/**`
- `src/netproto/index.ts` is the ONLY allowed entry point into `src/netproto/` from outside `src/netproto/` (except `src/netproto/transport/*` where needed by Node scripts).
- Exports:
- `ByteStream`, `CloseReason`, `EndpointKind`, `EndpointResolver`, `createWsBridgeResolver`, `createTcpResolver`, `createMixedResolver`, ` createMemoryStreamPair`
- `TextCodec`, `createBrowserTextCodec`, `PacketTap`, `PacketTapEntry`, `ProtocolError`, `ClosedError`, `TimeoutError`, `Clock`, `systemClock`
- `UnitType`, `UnitRef`, `Pt`, `Hand`, `ItemContainer`, `ItemData`, `ItemStat`, `ItemDataTables`, `UnitSnapshot`, `SkillLevel`, `CharSummary`, `GameSummary`, `RealmInfo`, `ChatEvent`
- `ServerEvent` (discriminated union: `GameLoading`, `GameFlags`, `LoadAct`, `LoadComplete`, `UnloadComplete`, `GameLogout`, `RoomReveal`, `RoomHide`, `UnitAssign`, `UnitRemove`, `UnitMove`, `UnitReassign`, `UnitMode`, `SkillCast`, `StatSet`, `LifeMana`, `Experience`, `StateSet`, `StateEnd`, `ItemEvent`, `SkillList`, `ActiveSkill`, `Npc`, `WaypointMenu`, `QuestState`, `Party`, `Trade`, `Chat`, `PlaySound`, `Merc`, `Portal`, `Pong`, `Warden`, `SaveChunk`, `Opaque`)
- `ClientCommand` (discriminated union: `Move`, `MoveToUnit`, `SkillAt`, `SkillOn`, `SelectSkill`, `Interact`, `TakeWarp`, `ItemPickup`, `ItemDrop`, `ItemToCursor`, `ItemPlace`, `ItemEquip`, `ItemUnequip`, `ItemSwap`, `BeltPut`, `BeltRemove`, `BeltUse`, `ItemUse`, `SocketItem`, `IdentifyItem`, `NpcInit`, `NpcCancel`, `NpcBuy`, `NpcSell`, `NpcRepair`, `NpcHeal`, `NpcGamble`, `NpcIdentifyAll`, `NpcResurrectMerc`, `HireMerc`, `AddStatPoint`, `AddSkillPoint`, `WaypointGo`, `TownPortal`, `ClickButton`, `Party`, `Hostile`, `Trade`, `Chat`, `OverheadChat`, `Resurrect`, `SwapWeapons`, `Ping`, `LeaveGame`)
- `GameServerAdapter` (`state`, `send`, `onEvent`, `onClose`, `leave`, `stats`)
- `OnlineConfig`, `D2OnlineFlow` (`connect`, `createAccount`, `login`, `enterChat`, `joinChannel`, `sendChat`, `onChatEvent`, `listRealms`, `realmLogon`, `listChars`, `createChar`, `deleteChar`, `upgradeChar`, `charLogon`, `listGames`, `getGameInfo`, `createGame`, `joinGame`, `close`)
### 2. `src/client/world/client-world.ts` ↔ `src/client/{map,motion,view,ui-model}/**`
- `ClientWorld` consumes `ServerEvent` via `apply(ev: ServerEvent): void` and exposes read-only snapshots of:
- `act`, `mapSeed`, `difficulty`, `hardcore`, `expansion`, `ladder`, `areaId`, `loading`, `selfId`
- `self: SelfPlayerState` (`unitId`, `name`, `charClass`, `level`, `x`, `y`, `life`, `maxLife`, `mana`, `maxMana`, `stamina`, `maxStamina`, `gold`, `stashGold`, `experience`, `stats`, `skills`, `leftSkill`, `rightSkill`, `runMode`, `weaponSet`, `mercId`)
- `units: ReadonlyMap<string, ClientUnit>` (keyed by `${unitType}:${id}`)
- `containers: ClientItemContainers` (`inventory`, `equipment`, `belt`, `stash`, `cube`, `cursor`, `tradeSelf`, `tradePartner`, `merc`, `shop`, `ground`)
- `revealedRooms: ReadonlySet<string>` (`${areaId}:${tileX}:${tileY}`)
- `waypoints: ReadonlySet<number>`, `quests: ReadonlyUint16Array`, `party`, `trade`, `chatLog`, `soundQueue`
### 3. `src/client/map/map-service.ts` ↔ `src/game/drlg/**` & `src/client/view/scene-source.ts`
- `MapService.loadAct(act: number, mapSeed: number, difficulty: number, initialAreaId: number): Promise<LevelView>`
- `MapService.revealRoom(areaId: number, roomTileX: number, roomTileY: number): Promise<LevelView>`
- `LevelView`: `{ act, mapSeed, difficulty, areaId, originX, originY, width, height, collision: Uint16Array, blocked: Uint8Array, floors, shadows, walls, roofs, warps, presetUnits, automap, rooms }`
### 4. `src/client/settings/client-settings.ts` & `ViewportProfile` ↔ `Toolbar`, `WorldRenderer`, `HudManager`
- `ClientSettings` stored under `localStorage['d2web.settings.v1']` with strict schema validation.
- `ViewportProfile`: `{ id: '800x600' | '1024x768', width: 800 | 1024, height: 600 | 768, barOffsetX: 0 | 112, barOffsetY: 0 | 168, panelOffsetY: 0 | 84 }`.
- `LightingInputs` decouples `src/scene/frontend/lighting-presets.ts` from `GameEngine`.
- `SceneSource` decouples `src/scene/frontend/world-renderer.ts` from `GameEngine`.
## Code Layout
- `src/netproto/`: Isolated, zero-dependency network & protocol package (`core/`, `transport/`, `crypto/`, `bncs/`, `mcp/`, `d2gs/`, `domain/`, `flow/`, `ts/`, `index.ts`).
- `src/client/`: Packet-driven online client (`world/`, `map/`, `motion/`, `input/`, `view/`, `ui-model/`, `settings/`, `toolbar/`, `automap/`, `audio/`, `assets/`, `inspector/`, `session/`, `main.ts`).
- `play.html`: Online client HTML entry point with top DOM Toolbar and dual-viewport WebGL2 + 2D HUD canvas stage.
- `scripts/extract-d2net-tables.ts`: Deterministic 1.13c `D2Net.dll` & `Fog.dll` table generator.
- `scripts/d2-bot.ts`: Headless Node CLI bot (`register`, `login`, `chars`, `create-char`, `games`, `create-game`, `join`, `idle`, `record`, `walk`, `audit-levels`).
- `scripts/netproto-replay.ts`: Offline `.d2cap` JSONL capture replay & strict coverage verifier.
- `scripts/pack-tiles.ts`: Seed-independent DT1 tile library & DRLG metadata packer.
- `scripts/d2s-unlock.ts`: Safe offline `d2webbot*` `.d2s` waypoint & act-progression unlocker with timestamped backup.
- `scripts/verify-play.ts`: Headless Chrome CDP E2E verifier for `play.html` across `800×600` & `1024×768` viewports and all Toolbar controls.
- `tests/netproto/`, `tests/client/`, `tests/e2e-netproto/`: Unit, boundary, golden replay, and 5-tier E2E test suites.