diablo2-web/TEST_INFRA.md

73 lines
9.1 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.

# Test Infrastructure & Strategy — `diablo2-web` Module Split (`TEST_INFRA.md`)
## 1. Test Philosophy
- **Requirement-Driven & Opaque-Box Verification**: Every test case in `tests/e2e-module-split/` is derived directly from `ORIGINAL_REQUEST.md` (`R1`–`R4` and Acceptance Criteria), `PROJECT.md` (`F1`–`F10` and Interface Contracts 1–4), and `AGENTS.md` (Diablo II v1.13c Ground Truth Parity Rules).
- **1.13c Ground Truth & Anti-Silent Failure Enforcement**:
- Universal DT1 libraries (`data\global\tiles\Act1\Outdoors\Blank.dt1`, `data\global\tiles\Act1\Barracks\InvisWal.dt1` with `style=49 sequence=7` collision floor, `data\global\tiles\Act1\Barracks\Warp.dt1`) must be unconditionally loaded per `D2Common.dll` `DRLGROOM_LoadDt1Files` (`0x6fdb8400`).
- Sparse/void cells must be blocked (`blocked === 1`) and masked with `COLLIDE_MASK_INVALID` (`COLLIDE_BLANK | COLLIDE_WALL`), and `findIsoSpawn` must never spawn in void or default blindly to geometric center `(width/2, height/2)`.
- Tile variant selection (`pickVariant`) must use high-entropy SplitMix64 2D spatial coordinate hashing without 45-degree diagonal striping.
- All 66 `SuperUniques` must preserve exact 1.13c `Class` and minion identifiers (`scarab2`, `zombie5`, `fetishshaman4`, `nihlathakboss`, `minion1`..`minion11` -> token `'XX'`, etc.).
- Missing required parameters (e.g., `D2DataRegistry` / `getSharedDataRegistry` called without an `ArchiveSource`) must fail fast by throwing an explicit `Error`.
- **Dynamic Import & AST/Filesystem Inspection**: All module imports inside `tests/e2e-module-split/*.test.ts` use runtime dynamic `await import(...)`, `node:fs` inspection, and source/AST boundary scanning inside `it(...)` blocks so the test files remain 100% valid TypeScript at all times and execute against the post-split architecture (`src/common/`, `src/netproto/`, `src/server/`, `src/client/`, `src/baker/`, `tools/`, `play.html`).
---
## 2. Feature Inventory (`F1`–`F10`)
| Feature ID | Feature Name | Target Module / Path | Core Requirements & Contracts | Tier 1 | Tier 2 | Tier 3 | Tier 4 |
|---|---|---|---|---:|---:|---:|---:|
| **F1** | `src/common/` Pure Module & Data Registry Decoupling | `src/common/{mpq,formats,data,drlg,world,items,skills,units,i18n,rng,pack-contract}`, `tsconfig.common.json` | Pure ES2022 (`types: []`, zero DOM, zero `node:*`), `D2DataRegistry` & `getSharedDataRegistry` require injected `ArchiveSource` (no `/usr/local/google/home/taodao/d2-data`), `D2Rng`, SplitMix64 `pickVariant`, pure `lookupTbl`. | 6 | 6 | ✓ | ✓ |
| **F2** | Shared Pack Contract (`src/common/pack-contract/`) | `src/common/pack-contract/index.ts` | Exports `PackedDrlgBundleManifest`, `PackedTileCatalogIndex`, `PackIndex`, `PackSceneJson`, `EntityMeta`, `ClipMeta`, `validateUniversalDt1Libraries`, `validatePackManifest`. | 5 | 5 | ✓ | ✓ |
| **F3** | Elimination of All 23 Server-to-Client Reverse Dependencies | `src/common/{items,units,skills,i18n,world}`, `src/server/**` | Extract `inventory-model`, `belt-model`, `item-bridge`, `CharacterAttributes`, `resolvePlayerCharToken`, `HotkeySkillEntry`, `getSkillName`, `subTileToScene`; zero imports from `src/server/**` into `src/client/**`. | 5 | 5 | ✓ | ✓ |
| **F4** | `src/baker/` Offline Baker Module & Unified CLI | `src/baker/{cli.ts,io,packers,extract,audit}`, `tsconfig.baker.json` | Node-only offline baker (`types: ["node"]`, zero DOM, zero `client`/`server` imports), unified `src/baker/cli.ts` (`npm run bake -- <target>` & `--verify`). | 5 | 5 | ✓ | ✓ |
| **F5** | `tools/` CLI Consolidation & Legacy Relay Deletion | `tools/*.ts`, `package.json` | Migrate 93 CLI/bot/replay/verify tools to `tools/`; delete `scripts/net-relay.ts`, `scripts/net-server.ts`, and `scripts/` directory; update `package.json` scripts. | 5 | 5 | ✓ | ✓ |
| **F6** | `src/server/` Authoritative Simulation & `LocalLoopbackAdapter` | `src/server/{engine,ai,skills,items,world,save,scene,adapter}`, `tsconfig.server.json` | Zero DOM globals, zero `Math.random`/`Date.now`/`performance.now`, zero `node:*` in `skills/registry.ts`; `LocalLoopbackAdapter` implements `GameServerAdapter`. | 6 | 6 | ✓ | ✓ |
| **F7** | `src/client/` Pure Browser Module & `MapService` Decoupling | `src/client/{main.ts,render,ui,frontend,scene,audio,sim,i18n,map,session,world}`, `tsconfig.client.json` | Pure browser (`types: []`, zero `node:*`, zero `server`/`baker` imports); `MapService.fromLoader` / `fromPackedUrls` without `node:fs`/`node:path`. | 5 | 5 | ✓ | ✓ |
| **F8** | Single HTML Entry (`play.html`) & Legacy Entry Removal | `play.html`, `vite.config.ts` | `play.html` is sole `.html` in root and `vite.config.ts` (with `D2Exocet`/`D2Formal436` `@font-face`); `index.html`, `acts*.html`, `bnet.html`, `act-scene.ts`, `net-scene.ts`, `src/net/*` deleted. | 5 | 5 | ✓ | ✓ |
| **F9** | Compiler-Level Isolation & Boundary Gate (`tests/arch/boundaries.test.ts`) | `tsconfig.*.json`, `tests/arch/boundaries.test.ts` | All 5 `tsconfig.<module>.json` files wired into `npm run typecheck`; `src/` contains only `{common,netproto,server,client,baker}`; `tests/arch/boundaries.test.ts` passes with 0 whitelist exceptions. | 5 | 5 | ✓ | ✓ |
| **F10** | Full 1.13c Parity, Baker, Replay & DRLG Invariants | `src/common/drlg/`, `src/common/world/`, `src/common/units/`, `tools/netproto-replay.ts` | Universal DT1s (`Blank.dt1`, `InvisWal.dt1`, `Warp.dt1`), void blocking (`COLLIDE_MASK_INVALID`), non-void `findIsoSpawn`, SplitMix64 `pickVariant`, SuperUnique minion closure, `.d2cap` replay parity. | 5 | 5 | ✓ | ✓ |
---
## 3. Test Architecture
```text
tests/e2e-module-split/
├── tier1-feature-coverage.test.ts # >= 50 tests (>= 5 per feature F1–F10): happy-path module layout, exports, & contracts
├── tier2-boundary-corner.test.ts # >= 50 tests (>= 5 per feature F1–F10): fail-fast, negative, boundary & isolation invariants
├── tier3-cross-feature.test.ts # >= 15 tests: pairwise cross-module & cross-feature integration contracts
└── tier4-real-world-scenarios.test.ts # >= 6 tests: realistic end-to-end gameplay, DRLG, item/socket, .d2cap replay, & repo audit scenarios
```
- **Test Runner Command**: `npx vitest run tests/e2e-module-split/`
---
## 4. Real-World Application Scenarios (Tier 4)
1. **Scenario 1 — End-to-End Single-Player Game Loop (`LocalLoopbackAdapter` → `OnlineSession` → `ClientWorld`)**:
- Connect `LocalLoopbackAdapter` to `OnlineSession` and `ClientWorld`, verify initial handshake (`LoadAct`, `UnitAssign`, `MapSeed`), dispatch movement (`RunToLocation` / `WalkToLocation`) and skill cast (`CastLeftSkillAtLocation` / `CastRightSkillAtLocation`), step server ticks, verify authoritative `ClientWorld` state updates, and cleanly disconnect.
2. **Scenario 2 — End-to-End DRLG Level Generation, Void Collision & `findIsoSpawn` Across Act 1–5**:
- Verify universal DT1 inclusion (`Blank.dt1`, `InvisWal.dt1` style=49 seq=7, `Warp.dt1`), void cell collision masking (`COLLIDE_MASK_INVALID = COLLIDE_BLANK | COLLIDE_WALL`), largest walkable floor region spawn resolution (`findIsoSpawn`), and SplitMix64 `pickVariant` spatial decorrelation across Act 1–5 levels.
3. **Scenario 3 — End-to-End Item, Inventory, Belt & Socket Pipeline (`src/common/items/` ↔ `src/server/items/`)**:
- Generate and bridge items via `itemToUiInventoryItem`, place items into `InventoryGrid` (`findFreeGridSlot`), manage belt potions (`BeltHud` / `BeltModel` / `itemToBeltPotion` / column gravity shift), and socket gems/runes into host items (`socketItemIntoHost`, `checkRunewordMatch`).
4. **Scenario 4 — End-to-End `.d2cap` Binary Packet Capture Replay into `ClientWorld`**:
- Replay all 4 real `.d2cap` capture fixtures (`01-bncs-mcp-lobby.d2cap`, `02-d2gs-town-walk-chat.d2cap`, `03-d2gs-items-npc-skills.d2cap`, `04-d2gs-idle-60s.d2cap`) via `replayD2CapText(..., { strict: true })` and verify `unknownPackets === 0`, `opaquePackets === 0`, `protocolErrors === 0`, and populated `ClientWorld` state.
5. **Scenario 5 — End-to-End Offline Asset Pack Manifest & Entity/Tile Pack Integrity**:
- Load and validate `public/act-packs/tiles/{index.json, drlg-bundle.json, drlg-bundle.bin}` against `src/common/pack-contract/` validators (`validateUniversalDt1Libraries`, `validatePackManifest`), and initialize `MapService.fromLoader` with an injected loader to generate an Act 1 level view without `node:fs` in `src/client/`.
6. **Scenario 6 — End-to-End Repository Architectural & Module Boundary Audit**:
- Programmatically scan every `.ts` file in `src/**`, `tools/**`, root `*.html`, `vite.config.ts`, `package.json`, and `tsconfig*.json` to verify all R1–R4 architectural invariants with zero exceptions.
---
## 5. Coverage Thresholds
| Tier | File | Minimum Required Tests | Target Tests |
|---|---|---:|---:|
| **Tier 1** | `tests/e2e-module-split/tier1-feature-coverage.test.ts` | 50 (`>= 5` per F1–F10) | 52 |
| **Tier 2** | `tests/e2e-module-split/tier2-boundary-corner.test.ts` | 50 (`>= 5` per F1–F10) | 52 |
| **Tier 3** | `tests/e2e-module-split/tier3-cross-feature.test.ts` | 15 | 16 |
| **Tier 4** | `tests/e2e-module-split/tier4-real-world-scenarios.test.ts` | 6 | 6 |
| **Total** | `tests/e2e-module-split/` | **121** | **126** |