diablo2-web/TEST_INFRA.md

14 KiB
Raw Blame History

Test Infrastructure & 4-Tier E2E Test Plan: D2GS v1.13c Online Gameplay Loops & Server Authority

1. Test Philosophy & Ground Truth Invariants

The tests/e2e-d2gs-online/ suite is an opaque-box, requirement-driven 4-tier E2E test suite verifying all 16 features (F1–F16) defined in PROJECT.md and ORIGINAL_REQUEST.md (2026-10-01T13:08:09Z).

Core Verification Principles

  1. Diablo II v1.13c Binary & Data-Table Ground Truth:
    • Every C2S binary packet emitted by encodeD2gsClientCommand / clientCommandToC2sPackets is verified against C2S_PACKET_SIZES[opcode] (src/netproto/d2gs/tables/s2c-sizes.ts) and exact little-endian wire layout (u8, u16LE, u32LE).
    • Every online item tooltip (onlineItemDataToUiInventoryItem and HudModel.syncFromWorld()) is verified against canonical 1.13c item tables (getEmbeddedDropTables(), Armor.txt, Weapons.txt, Misc.txt, MagicPrefix.txt, MagicSuffix.txt, RarePrefix.txt, RareSuffix.txt, SetItems.txt, UniqueItems.txt, Runes.txt, ItemStatCost.txt) — never raw Stat #N: V fallbacks.
    • Every Town and Quest NPC across Acts 1–5 is verified against TOWN_NPC_DESCRIPTORS and NPC_HCIDX_TO_DESCRIPTOR_ID (src/common/units/npc-table.ts).
  2. Strict Server Authority (Zero Optimistic Client Mutation):
    • When HudManager / WorldPanelsHud has a HudCommandSink (HudModel) attached (hasCommandSink === true), UI interactions emit typed ClientCommand payloads and C2S wire bytes without mutating local inventory, equipment, belt, stash, cube, mercenary, party, or trade state ahead of the server.
    • Only authoritative ServerEvent updates applied to ClientWorld (src/client/world/client-world.ts) and synchronized via HudModel.syncFromWorld() (src/client/ui-model/hud-model.ts) mutate the visible UI state.
  3. Opaque-Box Contract Testing:
    • All tests interact through public module boundaries (src/netproto/index.ts, src/common/items/item-bridge.ts, src/common/items/ground-items.ts, src/common/units/npc-table.ts, src/client/world/client-world.ts, src/client/ui-model/hud-model.ts, src/client/ui/hud-manager.ts, src/client/ui/world-panels.ts, src/client/view/unit-drawables.ts, src/client/view/scene-source.ts, src/client/input/command-mapper.ts).

2. Complete 16-Feature Inventory (F1–F16)

ID Feature Requirement Primary Public Contracts Under Test
F1 Canonical 1.13c Online Item Tooltip Bridge R1.1 onlineItemDataToUiInventoryItem, HudModel.syncFromWorld(): CHI/ENG base names, Magic/Rare/Set/Unique/Runeword names, 1H/2H damage, defense, durability, reqLevel/reqStr/reqDex/reqClass, sorted ItemStatCost lines (stats/statsEn), sockets/usedSockets/socketedItems.
F2 C2S Container, Belt, NPC, Merc, Warp, Party & Trade Packet Encoders R1.2, R2–R5 encodeD2gsClientCommand & clientCommandToC2sPackets matching C2S_PACKET_SIZES: 0x09 WarpTo (9B), 0x1D SwapCursorBodyItem (ItemSwap, 9B), 0x1F SwapContainerItem (17B), 0x23 BeltPut (9B), 0x24 BeltRemove (5B), 0x25 BeltSwap (9B), 0x27 IdentifyItem (9B), 0x28 SocketItem (9B), 0x29 ScrollToBook (9B), 0x31 QuestMessage (9B), 0x36 HireMerc (MercHire, 9B), 0x38 EntityAction (NpcEntityAction, 13B), 0x41 Resurrect (1B), 0x4F Trade (ClickButton, 7B), 0x5D Hostile (7B, button=1/2), 0x5E Party (PartyAction, 6B), 0x61 MercItem (3B), 0x62 NpcResurrectMerc (MercResurrect, 5B).
F3 Server-Authoritative Container, Belt, Identify, Socket & Tome UI Wiring R1.2 HudModel & HudManager: equip slot swap (0x1D) vs socket (0x28); container grid scroll-to-book (0x29), socket (0x28), swap (0x1F); belt put (0x23), remove (0x24), swap (0x25), shift-use on merc (0x26 useOnMerc=1); right-click isc/ibk identify targeting (0x27).
F4 Weapon Set II (bodyLoc 11 & 12) Sync R1.3 HudModel.syncFromWorld(): bodyLoc 11 (rarm2) -> hud.inventory.swapWeapons.weapon1, bodyLoc 12 (larm2) -> hud.inventory.swapWeapons.weapon2, preserved across 0x60/0x97 weapon switch.
F5 Act 1–5 Town & Quest NPC Descriptors R2.1 TOWN_NPC_DESCRIPTORS, NPC_HCIDX_TO_DESCRIPTOR_ID, getTownNpcDescriptorByHcIdx: Cain (146, 244, 245, 246, 520, 265), Warriv (155, 175), Meshif (210, 258), Kaelan (203), Tyrael (251, 367), Hadriel (259), Qual-Kehk (515), Kashya (150), Greiz (198), Asheara (252), Charsi (154), Larzuk (511), Anya (512).
F6 NPC Info (0x27), SetNPCTalk (0x91), Talk Submenu & 0x31 QuestMessage R2.2 ClientWorld.apply({ type: 'Npc', kind: 'info' | 'menu' | 'heal' }), synchronous NpcInit menu opening, Talk submenu (Introduction, Gossip, Quest speech), .tbl speech dialog box, and 0x31 QuestMessage.
F7 Act Travel Menu Options & C2S 0x38 EntityAction R2.3 buildNpcMenuOptions & HudModel.handleNpcMenuAction: travel_east (Warriv 155, Meshif 210), travel_west (Warriv 175, Meshif 258), travel_harrogath (Tyrael 367) emitting NpcEntityAction (0x38, 13B).
F8 Mercenary World State (0x4E, 0x4F, 0x9B & lifePct) R3.1 ClientWorld.apply({ type: 'Merc' }): hireListStart (0x4F), forHire (0x4E { nameId, seed }), reviveCost (0x9B { reviveCost, deadNameId }), lifePct updates (0x0C, 0xAB, 0x69, 0x81, 0x9E..0xA0), and world.containers.merc unequip-to-cursor (0x08).
F9 Mercenary Hire (0x36) & Resurrect (0x62) UI R3.2 hire option on mercenary captains (150, 198, 252, 515) -> 0x38 (action=3) -> Hire panel populated from world.mercHireList -> MercHire (0x36, 9B); dead merc (reviveCost > 0) exposes resurrect -> MercResurrect (0x62, 5B).
F10 Mercenary HUD Portrait & Paperdoll (O Hotkey, 0x61 MercItem) R3.3 Top-left mercenary portrait + life bar in HudManager; 'O' hotkey toggles 'merc' panel; world.containers.merc syncs to merc equipment slots (helm, armor, weapon1, weapon2); clicking slot emits MercItem (0x61, 3B).
F11 Dungeon/Stair Warp Clicking (0x09 AssignWarp → C2S 0x09 WarpTo) R4.1 0x09 AssignWarp preserves warpId on UnitSnapshot & ClientUnit; buildSceneDrawables & ClientWorldSceneSource.pickUnitAtScreen expose 'warp'/'tile' units with localized destination labels; clicking emits MoveToUnit (0x04) + TakeWarp/WarpTo (0x09, 9B).
F12 Portal Ownership Hover (0x60/0x82) & Ground Item Labels (0x16) R4.2 Town Portal (classId 59/60) enriched with world.portals (<ownerName>'s Town Portal, destination areaId); ground gold piles propagate goldAmount (<N> Gold / <N> 金币, never hardcoded '1 Gold'); Alt ground labels layout without overlap and click-to-pickup emits ItemPickup (0x16).
F13 SkillCast (0x4C/0x4D/0x6C/0x99/0x9A) Animation R4.3 ClientWorld.apply({ type: 'SkillCast' }) updates caster unit castState, facing direction toward target, and attack/cast animation mode ('A1'/'SC', mode 7/10 for player, 4/7 for monster).
F14 Player Death Overlay & C2S 0x41 Resurrect R4.4 world.self.life <= 0 (when maxLife > 0) or death mode sets isDead / isSelfDead, renders classic death overlay ("You Have Died. Press ESC to continue." / "你挂了,请按 ESC 键继续"), and pressing Escape or clicking overlay emits { type: 'Resurrect' } (0x41, 1B).
F15 Multiplayer Party State & Party Panel (P Hotkey, 0x5D/0x5E) R5.1 ClientWorld.apply({ type: 'Party' }) handles roster (0x5B/0x5C), relation, memberState, memberPos; 'P' hotkey toggles 'party' panel; Invite/Cancel/Accept/Leave emit PartyAction (0x5E, 6B); Hostile (button=1) and Loot (button=2) emit Hostile (0x5D, 7B); party portraits & automap markers.
F16 Player-to-Player Trade UI (0x77–0x79 & C2S 0x4F) & Quest Reward Item Panels R5.2, R5.3 0x77 ButtonActions, 0x78 TradeAccepted (partnerName), 0x79 GoldInTrade (selfGold, partnerGold), tradePartner & tradeSelf 10×4 grids, Trade Accept/Cancel (0x4F ClickButton, 7B); Quest Reward panels (imbue on Charsi 154, add_sockets on Larzuk 511, personalize on Anya 512).

3. 4-Tier Suite Architecture & Coverage Thresholds

tests/e2e-d2gs-online/
├── helpers.ts                        # Shared fixtures, mock Canvas/DOM harness, packet/item builders, runtime wrappers
├── tier1-feature-coverage.test.ts    # Tier 1: Core feature happy-path coverage (80 tests: 5 per F1–F16)
├── tier2-boundary-corner.test.ts     # Tier 2: Boundary, corner, negative & anti-regression coverage (80 tests: 5 per F1–F16)
├── tier3-cross-feature.test.ts       # Tier 3: Pairwise cross-feature integration coverage (16 tests across F1–F16 pairs)
└── tier4-real-world-scenarios.test.ts # Tier 4: End-to-end multi-step online gameplay session scenarios (8 scenarios)
Tier File Minimum Required Implemented Scope
Tier 1 tests/e2e-d2gs-online/tier1-feature-coverage.test.ts >= 80 (>= 5 / feature) 80 (5 × 16) Core happy-path behavior for F1–F16
Tier 2 tests/e2e-d2gs-online/tier2-boundary-corner.test.ts >= 80 (>= 5 / feature) 80 (5 × 16) Boundary values, malformed/stale packets, server rejection, zero optimistic mutation
Tier 3 tests/e2e-d2gs-online/tier3-cross-feature.test.ts >= 16 16 Pairwise cross-feature workflows (F1×F3, F2×F3, F1×F4, F5×F6, F5×F7, F8×F9, F8×F10, F1×F10, F11×F12, F12×F1, F13×F14, F14×F8, F15×F12, F15×F16, F5×F16, F1×F16)
Tier 4 tests/e2e-d2gs-online/tier4-real-world-scenarios.test.ts >= 8 8 Full multi-step online gameplay session loops
Total tests/e2e-d2gs-online/*.test.ts >= 184 184 All 16 features (F1–F16)

4. Tier 4 Real-World Online Gameplay Session Scenarios

  1. Scenario 1 — Town Identification, Tome Packing, Rune Socketing & Weapon Swap Loop (F1 + F2 + F3 + F4):
    • Player picks up an unidentified Unique item, right-clicks Tome of Identify (ibk) to arm identify targeting, left-clicks the item (0x27 IdentifyItem), receives server 0x9C identified update with canonical 1.13c stats, packs loose Scroll of Identify (isc) into ibk (0x29 ScrollToBook), sockets a rune into a 2-socket helm (0x28 SocketItem), and verifies Weapon Set II (bodyLoc 11/12) persistence across weapon switch (0x97).
  2. Scenario 2 — Belt Potion Management & Shift-Click Mercenary Feeding (F2 + F3 + F8 + F10):
    • Player places potions into belt (0x23 BeltPut), swaps a belt slot with a Super Healing Potion on cursor (0x25 BeltSwap), removes a belt potion (0x24 BeltRemove), and Shift-uses a belt slot (0x26 UseBeltItem with useOnMerc = 1) to heal a damaged Act 2 mercenary whose lifePct subsequently updates via S2C 0x0C/0x9E.
  3. Scenario 3 — Multi-Act Town NPC Dialogue, Cain Identification & Act Travel (F2 + F5 + F6 + F7):
    • Player interacts with Deckard Cain (hcIdx 244), opens Talk submenu (Introduction/Gossip/Quest speech -> 0x31 QuestMessage), triggers Cain Identify Items (0x38 action=4), then interacts with Warriv (155 -> travel_east to Act 2), Meshif (210 -> travel_east to Act 3), and Tyrael (367 -> travel_harrogath to Act 5).
  4. Scenario 4 — Mercenary Captain Hiring, Equipment Paperdoll (O) & Post-Combat Resurrection (F1 + F2 + F8 + F9 + F10):
    • Player opens Kashya (150) / Greiz (198) menu, clicks Hire (0x38 action=3), receives 0x4F + 0x4E hire candidates, hires a mercenary (0x36 HireMerc), opens 'O' Mercenary Paperdoll, equips/swaps merc gear (0x61 MercItem), takes lethal merc damage (0x9B MercReviveCost), and resurrects the mercenary (0x62 NpcResurrectMerc).
  5. Scenario 5 — Dungeon Warp Traversal, Town Portal Ownership Hover & Ground Loot Pickup (F2 + F11 + F12):
    • Server assigns a dungeon warp tile (0x09 AssignWarp), player hovers/clicks warp (0x04 MoveToUnit + 0x09 WarpTo), enters dungeon, hovers an allied Town Portal (0x60/0x82 -> "Sorceress's Town Portal"), holds Alt to inspect non-overlapping ground labels (750 Gold + Magic item), and clicks to pick up (0x16 ItemPickup).
  6. Scenario 6 — Combat SkillCast Animation, Player Death Overlay & Town Corpse Resurrection (F2 + F13 + F14):
    • Monster and player exchange SkillCast packets (0x4C/0x4D/0x6C) updating facing octants and 'A1'/'SC' animation modes; lethal damage reduces world.self.life to 0, activating isDead and rendering "You Have Died. Press ESC to continue."; pressing Escape dispatches 0x41 Resurrect (1B), and server respawn restores town state.
  7. Scenario 7 — Multiplayer Party Roster, Invite/Accept, Hostile & Corpse Loot Permissions (F2 + F15):
    • Second player joins (0x5B with partyId=0xffff and non-empty charName), player opens 'P' Party panel, invites (0x5E action=0x06) and accepts (0x5E action=0x08), receives party memberState (0x7F) and memberPos (0x90) for top-left party portrait and automap marker, toggles Corpse Loot (0x5D button=2) and Hostile (0x5D button=1), and cleans up when peer leaves (0x5C).
  8. Scenario 8 — Player-to-Player Trade Session & Act 1/5 Quest Reward Item Upgrading (F1 + F2 + F5 + F16):
    • Player initiates trade (0x77 action=0x01), receives tradePartner item (container: 'tradePartner') and 0x79 GoldInTrade, places tradeSelf item (container: 'trade'), receives 0x78 TradeAccepted with partnerName, clicks Trade Accept (0x4F buttonId=0x04), completes trade (0x77 action=0x06), and uses Charsi imbue (154), Larzuk add_sockets (511), and Anya personalize (512) quest reward panels.