diablo2-web/TEST_INFRA.md

16 KiB
Raw Blame History

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. 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 tables ItemStatCost.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).
  2. Zero Live Network Dependency in Automated Runs: All Vitest suites (tests/e2e-netproto/tier1..4.test.ts) execute 100% hermetically in-process using createMemoryStreamPair, FakeClock, InMemoryPacketTap, and .d2cap JSONL wire captures without opening any TCP or WebSocket connections to 101.37.117.183 or www.laiseek.xyz.
  3. 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 ProtocolError or explicit validation errors rather than silently succeeding or returning permissive fallbacks.
  4. 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:

  1. tests/e2e-netproto/tier1-feature-coverage.test.ts:
    • Scope: Positive happy-path verification for all 20 features (F1–F20).
    • Density: \ge 5 distinct it(...) test cases per feature (\ge 100 test cases total).
  2. 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 5 distinct it(...) test cases per feature (\ge 100 test cases total).
  3. 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 20 distinct it(...) test cases.
  4. 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 .d2cap capture replay.
    • Density: \ge 10 distinct it(...) end-to-end workload scenarios.

Real-World Application Scenarios (Tier 4)

  1. Scenario 1 — New Account Registration, Realm Logon, Character Creation & First Game Entry: Full D2OnlineFlow lifecycle with loginOrRegister(..., { register: true }), SID_QUERYREALMS2, SID_LOGONREALMEX, MCP_STARTUP, MCP_CHARCREATE, MCP_CHARLOGON, MCP_CREATEGAME, MCP_JOINGAME, and D2GS 0xAF -> 0x68 -> 0x01/0x02 -> 0x6B -> 0x03/0x04 transition into ingame.
  2. Scenario 2 — Act 1 Rogue Encampment Spawn, Room Reveal, Town Navigation & Akara Vendor Transaction: Loading Act 1 Level 1 via MapService, applying LoadAct, RoomReveal, UnitAssign (self + Akara + Stash + Waypoint), computing an A* path around obstacles, walking with LocalMovementPredictor + WalkVerify (0x96), opening Akara's shop (NpcInit -> 0x9C shop items), and buying a tome (NpcBuy -> 0x2A transaction + 0x9D inventory item).
  3. Scenario 3 — Blood Moor Combat, Skill Casting, Monster Kill, Item Drop, Pickup, Identification & Equip: Spawning a monster pack (0xAC AssignNPC with boss/champion flags), casting Charged Bolt (CastRightSkillOnTarget -> 0x4C SkillCast + 0x95 mana update), monster taking damage (0x0C NPCGetHit) and dying (0x11 ReportKill + 0x1A ExpByte), dropping an unidentified Magic Ring (0x9C AddToGround), picking it up (ItemPickup), identifying with a scroll (IdentifyItem -> identified 0x9D with prefix/suffix/stats), and equipping it (ItemEquip).
  4. Scenario 4 — Waypoint Teleportation & Act Transition (Act 1 Rogue Encampment -> Act 2 Lut Gholein): Interacting with the Rogue Encampment waypoint (Interact -> 0x63 WaypointMenu bitmask), selecting Lut Gholein (WaypointGo), receiving UnloadComplete (0x05), LoadAct (0x03) for Act 2 (areaId=40), RoomReveal (0x07), and LoadComplete (0x04), and verifying MapService + AutomapView + SoundService transition cleanly to Act 2 town.
  5. Scenario 5 — Multiplayer Party Formation, Remote Entity Interpolation, Hostility & Corpse Recovery: Receiving 0x5B PlayerInGame for a second player, exchanging party invites (Party -> 0x8D AssignPlayerToParty), tracking party member HP/area (0x7F PartyMemberState) and map coordinates (0x90 PartyMemberMapPos) on AutomapView, interpolating remote movement (0x0F PlayerMove -> RemoteEntityInterpolator), toggling hostility (Hostile -> 0x8C), and handling player corpse assignment (0x74 PlayerCorpseAssign + Resurrect).
  6. Scenario 6 — Player-to-Player Trade Session & Horadric Cube / Stash Container Management: Initiating trade (0x77 ButtonActions), placing gold (0x79 GoldInTrade) and items into the trade window (container='trade'), accepting trade (0x78 TradeAccepted), and moving items between Inventory (storePage=1), Horadric Cube (storePage=4), and Stash (storePage=5).
  7. Scenario 7 — Mercenary Hiring, Stat/Experience Progression, Equipment & Resurrection: Opening Kashya's hire list (0x4F MercForHireListStart + 0x4E MercForHire), hiring an Act 1 Rogue scout (HireMerc -> 0x81 AssignMerc), applying merc attributes and experience (0x9E..0xA2), equipping a bow on the mercenary (0x9D with ownerType=1 -> container='merc'), and resurrecting the merc (NpcResurrectMerc + 0x9B MercReviveCost).
  8. Scenario 8 — Leave Game to Lobby, Character Switch, Game List Discovery & Rejoin: Leaving an active D2GS session via flow.leaveToLobby() (0x69 LeaveGame), verifying BNCS/MCP remain alive in realm_ready, switching characters (selectCharacter), querying active games (listGames + getGameInfo), and joining a second game without reconnecting BNCS.
  9. Scenario 9 — Full UI & Client Integration Workflow (Settings, Toolbar, Viewport Toggle, Automap, Audio, OPFS Cache & Rate Limiter): Configuring SettingsStore (online-realm, 1024x768, zh/en), driving Toolbar callbacks, switching ViewportProfile between 800x600 and 1024x768, rendering ClientWorldSceneSource drawables and AutomapView markers, computing SoundService spatial metrics, caching MPQ chunks via OpfsMpqManager + HttpRangeMpqSource, and throttling rapid clicks through OutboundRateLimiter.
  10. Scenario 10 — .d2cap Wire Capture Recording, Export, Import, Inspection & Deterministic Replay: Recording a multi-connection (bnet, realm, game) session through InMemoryPacketTap and PacketInspector, exporting to .d2cap JSONL (d2cap-v1), re-importing into a fresh PacketInspector + 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