73 lines
9.1 KiB
Markdown
73 lines
9.1 KiB
Markdown
# 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** |
|