10 KiB
10 KiB
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:
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
- Diablo II v1.13c Ground Truth: Every C2S and S2C opcode, packet byte length (
C2S_PACKET_SIZESandS2C_PACKET_SIZESinsrc/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. - Server Authority: When
HudManagerhas a_commandSinkattached (hasCommandSink === true), user actions MUST emit typedClientCommandevents (src/netproto/domain/client-command.ts) encoded viaencodeClientCommand(src/netproto/d2gs/registry.ts) without mutating local container/stat/skill state ahead of the server.ServerEventupdates applied toClientWorld(src/client/world/client-world.ts) and synced viaHudModel.syncFromWorld()(src/client/ui-model/hud-model.ts) drive all UI state. - Strict Import Boundary: Files in
src/client/**MUST ONLY import protocol types/functions fromsrc/netproto/index.ts(never deep-importingsrc/netproto/d2gs/**orsrc/server/**). Files insrc/common/**MUST ONLY import fromsrc/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 icebolts + 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
SkillCastvariant onServerEventincludes optionalat?: Pt | undefinedandowner?: UnitRef | undefined.decode0x73CreateMissilepreservesat: { x, y },level: level || 1,owner: { unitType: ownerType, id: ownerId }, andtarget: { x: targetX || x, y: targetY || y }.
2. ClientWorld (src/client/world/client-world.ts) & OnlineSession (src/client/session/online-session.ts)
OnlineSessionexposesuseSkillAt(x, y, skillId?, hand?, shift?),useSkillOn(unitId, unitType?, skillId?, hand?, shift?), andattackUnit(unitId, unitType?, shift?).OnlineSession.tick(dtMs, nowMs)invokesthis.world.tick(dtMs, ...)with map walkability/wall collision check whenlevelViewis loaded (returningfalse/ unblocked whenlevelView === null).OnlineSession.buildRenderableUnits(nowMs)includesthis.world.getUnitsByType(3)(missiles) and active caster overlays.ClientWorlddeduplicates local player skill casts against serverSkillCastechoes within250ms, expiresunit.castStateback tomode = 1, steps missiles at 25Hz, checks wall and hostile unit collisions, respectspierce, spawns one-shotExplosionMissileunits, and removes completed one-shot explosions/overlays.
3. unit-drawables.ts & world-renderer.ts
SceneOverlayDrawableandSceneMissileDrawablecarryloop: boolean,loopAnim: boolean,totalFrames: number, and clampframeto[0, totalFrames - 1]when!loop(loop === false/loopAnim === false).MISSILE_ID_TO_KEYpreserves exactMissiles.txtrow names (tests/client/missile-id-map.test.ts);unit.token(missileKey) is passed toresolveMissileMetadata(unit.classId, unit.token)for client aliases such asfreezeexplode.
Code Layout & Write Ownership
- M5 Worker (
teamwork_preview_worker_r1_1): Ownssrc/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, andtests/client/hud-session-play.test.ts.