16 KiB
16 KiB
E2E Test Infrastructure & Philosophy (feat/netproto)
Test Philosophy
The feat/netproto E2E test suite validates the complete Diablo II: Lord of Destruction v1.13c online protocol stack (src/netproto/), client world/map/motion state engine (src/client/), and browser UI/asset integration (src/ui/, src/i18n/, scripts/) under strict deterministic, hermetic execution guarantees:
- 1.13c Binary Ground-Truth Parity: Every protocol frame, bitfield, hash algorithm, compression table, and DRLG collision mask is verified against the canonical 1.13c binary tables (
D2Common.dll,D2Client.dll,D2Net.dll,D2MCPClient.dll,Bnclient.dll,Fog.dll, and MPQ data tablesItemStatCost.txt,Armor.txt,Weapons.txt,Misc.txt,Levels.txt,LvlPrest.txt,LvlTypes.txt,LvlWarp.txt,MonStats.txt,Objects.txt,SuperUniques.txt,Sounds.txt,SoundEnviron.txt). - Zero Live Network Dependency in Automated Runs: All Vitest suites (
tests/e2e-netproto/tier1..4.test.ts) execute 100% hermetically in-process usingcreateMemoryStreamPair,FakeClock,InMemoryPacketTap, and.d2capJSONL wire captures without opening any TCP or WebSocket connections to101.37.117.183orwww.laiseek.xyz. - Fail-Fast Protocol & Asset Validation: Malformed packet headers, unknown opcodes, payload length mismatches, bitstream overruns, invalid item qualities/stat IDs, and unwalkable spawn coordinates throw structured
ProtocolErroror explicit validation errors rather than silently succeeding or returning permissive fallbacks. - Protocol-Neutral Architectural Boundary: Client and UI modules (
src/client/**,src/ui/**) consume only the public domain boundary (src/netproto/index.ts:ServerEvent,ClientCommand,GameServerAdapter,D2OnlineFlow) and never import wire-level protocol internals (src/netproto/{bncs,mcp,d2gs,crypto}/**).
Feature Inventory (F1–F20)
| ID | Feature Name | Primary Modules | 1.13c Ground Truth Reference |
|---|---|---|---|
| F1 | Core Binary & Bit Primitives (ByteReader, ByteWriter, BitReader, BitWriter, Clock, TextCodec, PacketTap) |
src/netproto/core/{byte-reader,byte-writer,bit-reader,bit-writer,clock,text-codec,packet-tap,errors}.ts |
LSB-first bitstreams (D2Common.dll 0x6fd7c470), LE/BE integer framing, GBK/UTF-8/Latin1 codecs, .d2cap JSONL format |
| F2 | Transport & Endpoint Resolution (ByteStream, EndpointResolver, MemoryStream, WsBridge) |
src/netproto/transport/{byte-stream,endpoint,memory-stream,ws-stream,node-tcp-stream}.ts |
bnet:6112, realm:6113, game:4000, ts:4002, maxConcurrentBnetWss = 2 rate limit |
| F3 | Cryptographic & Auth Primitives (XSHA-1, BnetHash, CheckRevision, CDKey) |
src/netproto/crypto/{xsha1,bnet-hash,check-revision,cdkey}.ts |
Broken SHA-1 (leftRotate(1 << n) without word rotation), hashBnetPassword, doubleHash, hashRealmPassword, 16-char D2DV/D2XP CD-key shuffle & lookup table, 1.13c 0x7686beca |
| F4 | BNCS (bnetd Port 6112) Framing, Packets & Session |
src/netproto/bncs/{framing,packets,session}.ts |
0xFF magic header, SID_AUTH_INFO (0x50), SID_AUTH_CHECK (0x51), SID_LOGONRESPONSE2 (0x3A), SID_CREATEACCOUNT2 (0x3D), SID_QUERYREALMS2 (0x40), SID_LOGONREALMEX (0x3E), SID_ENTERCHAT (0x0A), SID_CHATEVENT (0x0F), SID_PING (0x25) |
| F5 | MCP (d2cs Port 6113) Framing, Packets & Session |
src/netproto/mcp/{framing,packets,session}.ts |
[u16LE len][u8 id] framing, MCP_STARTUP (0x01), MCP_CHARCREATE (0x02), MCP_CREATEGAME (0x03), MCP_JOINGAME (0x04), MCP_GAMELIST (0x05), MCP_GAMEINFO (0x06), MCP_CHARLOGON (0x07), MCP_CHARDELETE (0x0A), MCP_CHARUPGRADE (0x18), MCP_CHARLIST2 (0x19), 33B statString |
| F6 | D2GS (d2gs Port 4000) Huffman Compression & Block Framing |
src/netproto/d2gs/{compression,framing-s2c,framing-c2s}.ts |
Fog.10229 canonical 256-symbol Huffman tree, 1/2-byte block header (D2Net.dll 0x6fbf6b60), 0xAF compression negotiation ([0xAF, 0x01] & 130B custom Huffman table [0xAF, 0x81, ...128B]), 179-entry S->C size table (0x6fbfb090), 111-entry C->S size table (0x6fbfabd8) |
| F7 | D2GS S->C Packet Decoder Registry (0x00..0xB4) & C->S Command Encoders (0x01..0x6D) |
src/netproto/d2gs/registry.ts, src/netproto/d2gs/s2c/*.ts, src/netproto/d2gs/c2s/*.ts |
D2Client.dll handler table (0x6fb8de60 & 0x6fb5d450), 0 unknown/opaque packets across all 1.13c opcodes |
| F8 | D2GS 0x9C / 0x9D Item Bitstream Decoder & Data Table Pipeline |
src/netproto/d2gs/items/{item-bitstream,item-types}.ts, scripts/extract-d2net-tables.ts |
D2Common.11145 (0x6fd7c470), compact (0x00200000) vs extended items (0x6fd7c0c0 / 0x6fd7a600), qualities 1..9, chained stats (17/18, 48/49, 50/51, 52/53, 54..56, 57..59), 0x1FF terminator |
| F9 | D2GS Session State Machine & D2gsAdapter (GameServerAdapter) |
src/netproto/d2gs/{session,adapter}.ts |
connecting -> logonSent -> entering -> ingame -> closed, 0x68 (37B) -> 0x01/0x02 -> 0x6B (1B), 5000ms 0x6D Ping heartbeat, 0x8F Pong RTT tracking, early event buffering |
| F10 | Full Online Flow (D2OnlineFlow: BNCS -> MCP -> D2GS -> Leave -> Rejoin) |
src/netproto/flow/{online-flow,config}.ts |
End-to-end state machine (idle -> connecting_bnet -> authenticated -> connecting_realm -> realm_ready -> connecting_game -> ingame -> closed), loginOrRegister, enterRealm, createCharacter, selectCharacter, createGame, joinGame, leaveToLobby |
| F11 | Client World State Store (ClientWorld) |
src/client/world/{client-world,index}.ts |
Applies all 22 ServerEvent variants into reactive world state (act, mapSeed, areaId, self, units, containers, revealedRooms, waypoints, quests, party, trade, merc, chatLog, soundQueue, portals, lastRttMs) |
| F12 | DRLG Map Generation, Collision Grid, Tile Atlas & MapService |
src/client/map/{map-service,level-view,tile-atlas}.ts, scripts/pack-tiles.ts |
136 levels across Acts 1–5, DRLGROOM_LoadDt1Files (0x6fdb8400) universal DT1s (Blank.dt1, InvisWal.dt1, Warp.dt1), SplitMix64 pickVariant, COLLIDE_WALL (0x0001), COLLIDE_BLOCK_PLAYER (0x0004), COLLIDE_BLANK (0x8000) |
| F13 | Sub-Tile A* Pathfinding, Bresenham LOS, Movement Predictor & Interpolator | src/client/motion/{pathfind,predictor,interpolate}.ts |
8-directional octile A* (10 cardinal / 14 diagonal) with corner-cutting prevention, Bresenham raycast LOS, LocalMovementPredictor (0x96 WalkVerify + 0x15 ReassignPlayer snap), RemoteEntityInterpolator |
| F14 | Offline .d2s Waypoint & Difficulty Unlock Tool (d2s-unlock) |
scripts/d2s-unlock.ts |
v96 (0x60) .d2s header (0xAA55AA55), WS waypoint block (0x02, 0x01, 0x00 + 5B 0x7f ff ff ff 7f = 39 waypoints), Woo! quest block, rotate-left-1 + byte-add checksum (0x0C) |
| F15 | Client Settings Store (SettingsStore, ClientSettings, Realm WS Derivation) |
src/client/settings/{client-settings,settings-store}.ts |
localStorage persistence (d2lod_client_settings_v1), host validation (validateServerHost), deriveRealmWsEndpoints (wss://<host>/ws/{bnet,realm,game} vs ws://<host>:8081..8083) |
| F16 | Unified Top Toolbar (Toolbar, Language Switcher, Settings Modal) |
src/client/toolbar/{toolbar,index}.ts, src/i18n/lang.ts |
offline-local vs online-realm mode selector, zh / en instant locale toggle without reload, 800x600 / 1024x768 viewport toggle, Automap & Packet Inspector toggles, Settings modal |
| F17 | Dual Viewport (800x600 & 1024x768) & Scene Drawables (ClientWorldSceneSource) |
src/client/view/{viewport-profile,scene-source,unit-drawables}.ts, src/ui/hud-manager.ts |
VIEWPORT_800x600 (barOffsetX: 0, rightDockX: 400) vs VIEWPORT_1024x768 (barOffsetX: 112, rightDockX: 624, 224px central corridor), isometric worldToScreen / screenToWorld, ClientWorldSceneSource |
| F18 | Automap Overlay (AutomapView) |
src/client/automap/{automap-view,index}.ts |
Toggle (Tab), mode (overlay/minimap), zoom (0.5..4.0), pan (panBy, resetPan), fog-of-war (RoomReveal/RoomHide), cross-shape player marker, party/monster/merc/portal/waypoint markers |
| F19 | Spatial Audio (SoundService) & OPFS/HTTP-Range MPQ Asset Streaming (OpfsMpqManager) |
src/client/audio/{sound-service,index}.ts, src/client/assets/{opfs-mpq,index}.ts |
Euclidean sub-tile distance attenuation & stereo pan (computeSpatialAudioMetrics), resolveSoundEnvironmentForLevel, HttpRangeMpqSource (206 Partial Content), InMemoryChunkCache LRU, OpfsMpqManager |
| F20 | Outbound Rate Limiter (OutboundRateLimiter) & Live Packet Inspector (PacketInspector) |
src/client/session/{rate-limiter,index}.ts, src/client/inspector/{packet-inspector,index}.ts |
Per-category cooldowns (move 80ms, skill 120ms, interact 150ms, chat 500ms, item 120ms, npc 200ms) + token bucket (20 tokens/s, burst 8), PacketInspector ring buffer, filtering, hex dump, .d2cap export/import |
Test Architecture
All E2E tests reside under tests/e2e-netproto/ and are organized into 4 progressive tiers:
tests/e2e-netproto/tier1-feature-coverage.test.ts:- Scope: Positive happy-path verification for all 20 features (
F1–F20). - Density:
\ge 5distinctit(...)test cases per feature (\ge 100test cases total).
- Scope: Positive happy-path verification for all 20 features (
tests/e2e-netproto/tier2-boundary-corner.test.ts:- Scope: Negative, boundary, malformed, truncated, overflow, and adversarial input verification for all 20 features (
F1–F20). - Density:
\ge 5distinctit(...)test cases per feature (\ge 100test cases total).
- Scope: Negative, boundary, malformed, truncated, overflow, and adversarial input verification for all 20 features (
tests/e2e-netproto/tier3-cross-feature.test.ts:- Scope: Pairwise and multi-module integration tests verifying contracts across module boundaries (e.g.,
D2gsS2cFramer+decodeD2gsS2cPacket+ClientWorld,MapService+findPathWorld+LocalMovementPredictor,SettingsStore+Toolbar+ViewportProfile,PacketInspector+InMemoryPacketTap+D2gsSession). - Density:
\ge 20distinctit(...)test cases.
- Scope: Pairwise and multi-module integration tests verifying contracts across module boundaries (e.g.,
tests/e2e-netproto/tier4-real-world-scenarios.test.ts:- Scope: Full end-to-end multi-step user and bot workflows simulating real gameplay sessions from connection handshake through town navigation, combat, item loot/identification, party/trade, act transitions, and
.d2capcapture replay. - Density:
\ge 10distinctit(...)end-to-end workload scenarios.
- Scope: Full end-to-end multi-step user and bot workflows simulating real gameplay sessions from connection handshake through town navigation, combat, item loot/identification, party/trade, act transitions, and
Real-World Application Scenarios (Tier 4)
- Scenario 1 — New Account Registration, Realm Logon, Character Creation & First Game Entry: Full
D2OnlineFlowlifecycle withloginOrRegister(..., { register: true }),SID_QUERYREALMS2,SID_LOGONREALMEX,MCP_STARTUP,MCP_CHARCREATE,MCP_CHARLOGON,MCP_CREATEGAME,MCP_JOINGAME, andD2GS0xAF -> 0x68 -> 0x01/0x02 -> 0x6B -> 0x03/0x04transition intoingame. - Scenario 2 — Act 1 Rogue Encampment Spawn, Room Reveal, Town Navigation & Akara Vendor Transaction: Loading Act 1 Level 1 via
MapService, applyingLoadAct,RoomReveal,UnitAssign(self + Akara + Stash + Waypoint), computing an A* path around obstacles, walking withLocalMovementPredictor+WalkVerify (0x96), opening Akara's shop (NpcInit->0x9Cshop items), and buying a tome (NpcBuy->0x2Atransaction +0x9Dinventory item). - Scenario 3 — Blood Moor Combat, Skill Casting, Monster Kill, Item Drop, Pickup, Identification & Equip: Spawning a monster pack (
0xACAssignNPC with boss/champion flags), casting Charged Bolt (CastRightSkillOnTarget->0x4CSkillCast +0x95mana update), monster taking damage (0x0CNPCGetHit) and dying (0x11ReportKill +0x1AExpByte), dropping an unidentified Magic Ring (0x9CAddToGround), picking it up (ItemPickup), identifying with a scroll (IdentifyItem-> identified0x9Dwith prefix/suffix/stats), and equipping it (ItemEquip). - Scenario 4 — Waypoint Teleportation & Act Transition (Act 1 Rogue Encampment -> Act 2 Lut Gholein): Interacting with the Rogue Encampment waypoint (
Interact->0x63WaypointMenu bitmask), selecting Lut Gholein (WaypointGo), receivingUnloadComplete (0x05),LoadAct (0x03)for Act 2 (areaId=40),RoomReveal (0x07), andLoadComplete (0x04), and verifyingMapService+AutomapView+SoundServicetransition cleanly to Act 2 town. - Scenario 5 — Multiplayer Party Formation, Remote Entity Interpolation, Hostility & Corpse Recovery: Receiving
0x5BPlayerInGame for a second player, exchanging party invites (Party->0x8DAssignPlayerToParty), tracking party member HP/area (0x7FPartyMemberState) and map coordinates (0x90PartyMemberMapPos) onAutomapView, interpolating remote movement (0x0FPlayerMove ->RemoteEntityInterpolator), toggling hostility (Hostile->0x8C), and handling player corpse assignment (0x74PlayerCorpseAssign +Resurrect). - Scenario 6 — Player-to-Player Trade Session & Horadric Cube / Stash Container Management: Initiating trade (
0x77ButtonActions), placing gold (0x79GoldInTrade) and items into the trade window (container='trade'), accepting trade (0x78TradeAccepted), and moving items between Inventory (storePage=1), Horadric Cube (storePage=4), and Stash (storePage=5). - Scenario 7 — Mercenary Hiring, Stat/Experience Progression, Equipment & Resurrection: Opening Kashya's hire list (
0x4FMercForHireListStart +0x4EMercForHire), hiring an Act 1 Rogue scout (HireMerc->0x81AssignMerc), applying merc attributes and experience (0x9E..0xA2), equipping a bow on the mercenary (0x9DwithownerType=1->container='merc'), and resurrecting the merc (NpcResurrectMerc+0x9BMercReviveCost). - Scenario 8 — Leave Game to Lobby, Character Switch, Game List Discovery & Rejoin: Leaving an active D2GS session via
flow.leaveToLobby()(0x69LeaveGame), verifying BNCS/MCP remain alive inrealm_ready, switching characters (selectCharacter), querying active games (listGames+getGameInfo), and joining a second game without reconnecting BNCS. - Scenario 9 — Full UI & Client Integration Workflow (Settings, Toolbar, Viewport Toggle, Automap, Audio, OPFS Cache & Rate Limiter): Configuring
SettingsStore(online-realm,1024x768,zh/en), drivingToolbarcallbacks, switchingViewportProfilebetween800x600and1024x768, renderingClientWorldSceneSourcedrawables andAutomapViewmarkers, computingSoundServicespatial metrics, caching MPQ chunks viaOpfsMpqManager+HttpRangeMpqSource, and throttling rapid clicks throughOutboundRateLimiter. - Scenario 10 —
.d2capWire Capture Recording, Export, Import, Inspection & Deterministic Replay: Recording a multi-connection (bnet,realm,game) session throughInMemoryPacketTapandPacketInspector, exporting to.d2capJSONL (d2cap-v1), re-importing into a freshPacketInspector+D2gsS2cFramer+ClientWorld, and verifying 100% state equivalence and formatted hex dumps.
Coverage Thresholds
For N = 20 features (F1–F20):
| Tier | File | Minimum Formula | Required Minimum |
|---|---|---|---|
| Tier 1 | tests/e2e-netproto/tier1-feature-coverage.test.ts |
5 \times N |
\ge 100 tests (\ge 5 per feature) |
| Tier 2 | tests/e2e-netproto/tier2-boundary-corner.test.ts |
5 \times N |
\ge 100 tests (\ge 5 per feature) |
| Tier 3 | tests/e2e-netproto/tier3-cross-feature.test.ts |
1 \times N |
\ge 20 pairwise interaction tests |
| Tier 4 | tests/e2e-netproto/tier4-real-world-scenarios.test.ts |
\ge 10 |
\ge 10 end-to-end scenario tests |
| Total | tests/e2e-netproto/tier*.test.ts |
11N + 10 |
\ge 230 tests |