88 lines
10 KiB
Markdown
88 lines
10 KiB
Markdown
# Project: D2GS v1.13c Online Gameplay Loops, Server Authority & Spell Visual Effects (`diablo2-web`)
|
||
|
||
## Architecture
|
||
|
||
The Diablo II v1.13c Web Client (`diablo2-web`) is structured into strictly isolated modules enforcing server-authoritative online gameplay over the D2GS v1.13c protocol:
|
||
|
||
```text
|
||
diablo2-web/
|
||
├── src/
|
||
│ ├── common/
|
||
│ │ ├── items/ # 1.13c item tables (embedded-drop-tables.ts), stat formatter (item-bridge.ts), tooltip builder (item-tooltip.ts), ground-items.ts
|
||
│ │ ├── skills/ # 1.13c Skills.txt & Missiles.txt data (skills-data.ts, skills-meta.ts, missiles-data.ts)
|
||
│ │ └── units/ # All-Act Town & Quest NPC descriptors (npc-table.ts), 1.13c direction mapping (direction.ts: DIR64_TO_DCC)
|
||
│ ├── netproto/
|
||
│ │ ├── index.ts # Sole public barrel export for netproto (strict boundary for src/client/**)
|
||
│ │ ├── domain/ # Typed ClientCommand (client-command.ts), ServerEvent (server-event.ts), ItemData & UnitSnapshot (ids.ts)
|
||
│ │ └── d2gs/
|
||
│ │ ├── tables/ # Authoritative 1.13c packet size tables (s2c-sizes.ts: C2S_PACKET_SIZES, S2C_PACKET_SIZES)
|
||
│ │ ├── items/ # 0x9C/0x9D item bitstream decoder (item-bitstream.ts)
|
||
│ │ ├── c2s/ # C2S binary packet encoders (items.ts, npc.ts, party.ts, movement.ts, interact.ts, ui.ts)
|
||
│ │ ├── s2c/ # S2C binary packet decoders (units.ts, npc.ts, merc.ts, party.ts, trade.ts, waypoint.ts, skills.ts, misc.ts)
|
||
│ │ └── registry.ts # encodeClientCommand & decodeD2gsServerPacket dispatchers
|
||
│ └── client/
|
||
│ ├── world/ # Authoritative ClientWorld state reducer & 25Hz simulation (client-world.ts, client-unit.ts, inventory.ts, self.ts)
|
||
│ ├── view/ # Scene drawables & picking (unit-drawables.ts, scene-source.ts)
|
||
│ ├── render/ # Pre-baked 1.13c missile & overlay metadata (missiles-meta.ts, overlays-meta.ts)
|
||
│ ├── scene/ # WorldRenderer (world-renderer.ts, missile-overlay-renderer.ts)
|
||
│ ├── session/ # OnlineSession (online-session.ts)
|
||
│ ├── input/ # CommandMapper (command-mapper.ts)
|
||
│ ├── ui-model/ # HudModel (hud-model.ts): syncs ClientWorld -> HudManager/WorldPanelsHud & emits ClientCommand
|
||
│ └── ui/ # HudManager (hud-manager.ts), WorldPanelsHud (world-panels.ts), InventoryPanel (inventory.ts), Minimap (minimap.ts)
|
||
└── tests/
|
||
├── arch/ # Architectural boundary enforcement (boundaries.test.ts)
|
||
├── client/ # Unit & integration tests (client-world, hud-session-play, missile-id-map, unit-facing-motion, hud-server-authority, vendor-stash-ui)
|
||
└── e2e-d2gs-online/ # 4-tier opaque-box E2E test suite + Tier 5 adversarial coverage hardening
|
||
```
|
||
|
||
### Architectural Invariants
|
||
1. **Diablo II v1.13c Ground Truth**: Every C2S and S2C opcode, packet byte length (`C2S_PACKET_SIZES` and `S2C_PACKET_SIZES` in `src/netproto/d2gs/tables/s2c-sizes.ts`), item bitstream field (`src/netproto/d2gs/items/item-bitstream.ts`), and Excel/TBL data table (`src/common/items/`, `src/common/skills/`, `src/common/units/npc-table.ts`) must strictly conform to Diablo II v1.13c.
|
||
2. **Server Authority**: When `HudManager` has a `_commandSink` attached (`hasCommandSink === true`), user actions MUST emit typed `ClientCommand` events (`src/netproto/domain/client-command.ts`) encoded via `encodeClientCommand` (`src/netproto/d2gs/registry.ts`) without mutating local container/stat/skill state ahead of the server. `ServerEvent` updates applied to `ClientWorld` (`src/client/world/client-world.ts`) and synced via `HudModel.syncFromWorld()` (`src/client/ui-model/hud-model.ts`) drive all UI state.
|
||
3. **Strict Import Boundary**: Files in `src/client/**` MUST ONLY import protocol types/functions from `src/netproto/index.ts` (never deep-importing `src/netproto/d2gs/**` or `src/server/**`). Files in `src/common/**` MUST ONLY import from `src/common/**` and remain 100% deterministic.
|
||
|
||
---
|
||
|
||
## Feature Inventory (Issue #550 — Spell Visual Effects)
|
||
|
||
Every feature from the Phase 0 Survey (derived from `ORIGINAL_REQUEST.md` `2026-10-02T07:31:06Z` and Explorer Reports `r1_1`, `r1_2`, `r1_3`) is listed below with its assigned milestone. No feature is left unassigned.
|
||
|
||
| # | Feature | Description | Milestone | Source |
|
||
|---|---------|-------------|-----------|--------|
|
||
| F17 | Canonical 1.13c Cast Overlays & Caster State Lifecycle (R1) | Display canonical 1.13c `Skills.txt` `castoverlay` (`Overlay.txt`, e.g. `fire_cast_1`, `fire_cast_2`, `ice_cast_1`, `ice_cast_2`, `ice_cast_3`, `light_cast_1`, `light_cast_2`, `teleport`, `bonearmor_cast`, `bonecast`, `cursecast`) on any casting unit (from server `SkillCast` `0x4C`/`0x4D`/`0x6C`/`0x99`/`0x9A` or local `useSkillAt`/`useSkillOn`/`attackUnit`) as a one-shot non-looping animation with dynamic point lighting; deduplicate local player casts against server `SkillCast` echoes within 250ms; expire `unit.castState` back to neutral mode (`1`) when cast duration finishes. | M5 | R1, Survey `r1_1`–`r1_3` |
|
||
| F18 | Client-Side Flying Missiles, `0x73 CreateMissile`, & 1.13c Trajectory Simulation (R2) | Preserve `at: { x, y }`, `level`, `owner` in `decode0x73CreateMissile` and spawn missile units (`unitType = 3`, `kind = 'missile'`) on `0x73` and spell casts; step at 25Hz according to 1.13c `Skills.txt` & `Missiles.txt` (`Vel`, `Range`, `LevRange`) for single-bolt projectiles (`Fire Bolt` 36, `Ice Bolt` 39, `Ice Blast` 45, `Fire Ball` 47, `Glacial Spike` 55, `Lightning` 49, `Chain Lightning` 53, `Bone Spear` 84, `Bone Spirit` 93, `Holy Bolt` 101, bow/crossbow skills), multi-projectile fans (`Charged Bolt` 38 with orthogonal jitter, `Teeth` 67, `Multiple Shot` 12), radial rings (`Frost Nova` 44, `Nova` 48, `Poison Nova` 92), spiral trajectories (`Blessed Hammer` 112), sub-missile emitters (`Frozen Orb` 64 emitting radial `icebolt`s + final nova), and ground/skyfall spells (`Fire Wall` 51, `Blaze` 46, `Meteor` 56, `Blizzard` 59); map velocity vectors to authentic 1.13c DCC directions (`DIR64_TO_DCC` / `velocityToDccDirection`). | M5 | R2, Survey `r1_1`–`r1_3` |
|
||
| F19 | Collision Detection & Spell Hit / Impact Explosion Visuals (R3) | Detect missile collisions against blocked map wall/obstacle sub-tiles and alive hostile units (respecting `pierce`); terminate non-piercing missiles on impact (or range expiry for `alwaysExplode` / `ExplosionMissile` missiles) and spawn canonical 1.13c `ExplosionMissile` / hit effect (`fireexplode` for `Fire Bolt`, `explodingarrowexp` for `Fire Ball`, `iceexplode` for `Ice Bolt`, `freezeexplode` for `Ice Blast`/`Glacial Spike`, `frozenorbexplode` for `Frozen Orb`, `meteorexplode` for `Meteor`, `teethexplode` for `Teeth`/`Bone Spear`, `bonespiritexplode` for `Bone Spirit`, `lightninghit` for `Lightning`/`Chain Lightning`/`Static Field`/`Telekinesis`) as a one-shot non-looping visual (`loop: false`, `loopAnim: false`) with dynamic point lighting and frame clamping (`totalFrames - 1`) that is removed upon completion. | M5 | R3, Survey `r1_1`–`r1_3` |
|
||
| F20 | Scene Drawables, `WorldRenderer` One-Shot Clamping, Automated Tests & Gitea PR Merge (R4) | Output `SceneOverlayDrawable` and `SceneMissileDrawable` with `loop: false` / `loopAnim: false` one-shot frame clamping and dynamic point lights in `unit-drawables.ts` and `world-renderer.ts`; verify with automated tests (`npx vitest run`) and production build (`npm run build`); commit, push `fix/issue-550-spell-visual-effects`, open Gitea PR (`Closes #550`), and merge into `main`. | M5 | R4, Survey `r1_1`–`r1_3` |
|
||
|
||
---
|
||
|
||
## Milestones
|
||
|
||
| # | Name | Scope | Dependencies | Status |
|
||
|---|------|-------|-------------|--------|
|
||
| M1–M4, E2E | Prior D2GS v1.13c Online Gameplay Milestones | F1–F16 (Completed in prior cycle) | none | DONE |
|
||
| M5 | Issue #550: Restore 1.13c Spell Visual Effects (Cast Overlays, Flying Missiles, Hit Explosions, Verification & PR Merge) | F17, F18, F19, F20 across `src/netproto/`, `src/common/skills/`, `src/client/world/`, `src/client/session/`, `src/client/view/`, `src/client/scene/`, and `tests/client/` | M1–M4 | IN_PROGRESS |
|
||
|
||
---
|
||
|
||
## Interface Contracts (Issue #550)
|
||
|
||
### 1. `src/netproto/domain/server-event.ts` & `src/netproto/d2gs/s2c/misc.ts`
|
||
- `SkillCast` variant on `ServerEvent` includes optional `at?: Pt | undefined` and `owner?: UnitRef | undefined`.
|
||
- `decode0x73CreateMissile` preserves `at: { x, y }`, `level: level || 1`, `owner: { unitType: ownerType, id: ownerId }`, and `target: { x: targetX || x, y: targetY || y }`.
|
||
|
||
### 2. `ClientWorld` (`src/client/world/client-world.ts`) & `OnlineSession` (`src/client/session/online-session.ts`)
|
||
- `OnlineSession` exposes `useSkillAt(x, y, skillId?, hand?, shift?)`, `useSkillOn(unitId, unitType?, skillId?, hand?, shift?)`, and `attackUnit(unitId, unitType?, shift?)`.
|
||
- `OnlineSession.tick(dtMs, nowMs)` invokes `this.world.tick(dtMs, ...)` with map walkability/wall collision check when `levelView` is loaded (returning `false` / unblocked when `levelView === null`).
|
||
- `OnlineSession.buildRenderableUnits(nowMs)` includes `this.world.getUnitsByType(3)` (missiles) and active caster overlays.
|
||
- `ClientWorld` deduplicates local player skill casts against server `SkillCast` echoes within `250ms`, expires `unit.castState` back to `mode = 1`, steps missiles at 25Hz, checks wall and hostile unit collisions, respects `pierce`, spawns one-shot `ExplosionMissile` units, and removes completed one-shot explosions/overlays.
|
||
|
||
### 3. `unit-drawables.ts` & `world-renderer.ts`
|
||
- `SceneOverlayDrawable` and `SceneMissileDrawable` carry `loop: boolean`, `loopAnim: boolean`, `totalFrames: number`, and clamp `frame` to `[0, totalFrames - 1]` when `!loop` (`loop === false` / `loopAnim === false`).
|
||
- `MISSILE_ID_TO_KEY` preserves exact `Missiles.txt` row names (`tests/client/missile-id-map.test.ts`); `unit.token` (`missileKey`) is passed to `resolveMissileMetadata(unit.classId, unit.token)` for client aliases such as `freezeexplode`.
|
||
|
||
---
|
||
|
||
## Code Layout & Write Ownership
|
||
|
||
- **M5 Worker (`teamwork_preview_worker_r1_1`)**: Owns `src/netproto/domain/server-event.ts`, `src/netproto/d2gs/s2c/misc.ts`, `src/common/skills/**`, `src/client/world/**`, `src/client/session/online-session.ts`, `src/client/view/unit-drawables.ts`, `src/client/scene/world-renderer.ts`, `src/client/scene/missile-overlay-renderer.ts`, `tests/client/client-world.test.ts`, and `tests/client/hud-session-play.test.ts`.
|