104 lines
23 KiB
Markdown
104 lines
23 KiB
Markdown
# 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.
|