diablo2-web/PROJECT.md

18 KiB
Raw Blame History

Project: Diablo II v1.13c Authentic Isometric Sprite Automap & Minimap System (diablo2-web)

Architecture

  • 1.13c Ground Truth Lookup & Weight Engine (src/formats/automap-table.ts, re-exported at src/game/automap-table.ts):
    • Parses all 3,288 data rows of data\global\excel\AutoMap.txt by column position (0..4 and 6, 8, 10, 12, avoiding the duplicate 'Type2' header at indices 7 and 9) into a 36 × 20 bucket table indexed by AUTOMAP_LEVEL_NAMES (0..35, 1-to-1 with LvlTypes.txt IDs 1..35) and AUTOMAP_TILE_NAMES (0..19, "fl".."fi").
    • Implements getAutomapCellId matching (Style, StartSequence..EndSequence) (Style == -1 and StartSequence == -1 as wildcards) and selecting a valid frame from Cel1..Cel4 (!= -1) via SplitMix64 spatial hashing.
    • Enforces the D2Client.dll 0x6fba1528 cell priority weight table (49 (cellId, groupId) pairs across 18 priority groups 0..17) when deduplicating cells at identical (xPixel, yPixel) coordinates so doors, exits, stairs, and warps are never overwritten by ordinary walls.
    • Applies all 1.13c special tile, object, monster, and pre-baked town chunk overrides (0x6fd89360, 0x6fb11d00, 0x6fb10c40, 0x6fb12760).
  • Offline UI & Level Asset Packing Pipeline (scripts/pack-ui.ts, src/game/d2map.ts, scripts/pack-act-assets.ts):
    • scripts/pack-ui.ts decodes all 8 automap DC6 sprite sheets from data\global\ui\Automap\ (MaxiMap.dc6, MaxiMapS.dc6, Act2Map.dc6, Act2MapS.dc6, Act4Map.dc6, Act4MapS.dc6, ExTnMap.dc6, ExTnMapS.dc6) and packs them into public/ui/ and public/assets/ui/automap/ with exact per-frame dimensions and offsets, updating public/ui/manifest.json and src/ui/baked-ui-meta.ts.
    • Both live buildIsoMapScene (src/game/d2map.ts) and offline bakeDs1Variant (scripts/pack-act-assets.ts) generate the 4 ordered automap cell layers (floors, walls, objects, extras) with authentic coordinates (xPixel = (cellX - cellY) * 8, yPixel = (cellX + cellY) * 4 + (tileType >= 16 ? 24 : 0) for tiles; xPixel = trunc(xClient / 10) + 1, yPixel = trunc(yClient / 10) - 3 for objects/NPCs) across all 136 levels.
  • Runtime Isometric Automap & Minimap UI (src/ui/minimap.ts, src/ui/hud-manager.ts, src/scene/act-scene.ts):
    • Renders the 4 automap sprite layers in strict order (floors -> walls -> objects -> extras) using screenX = trunc((xPixel * 10) / nDivisor) - offsetX, screenY = trunc((yPixel * 10) / nDivisor) - offsetY.
    • Supports Corner Minimap mode (bMinimap = 1, nDivisor = 20, *MapS.dc6 sheets, 279×225 top-right/top-left viewport clipped via gl.scissor with no non-authentic background box) and Full-Screen Automap mode (bMinimap = 0, nDivisor = 10, *Map.dc6 sheets, arrow-key panning ±8px, Home reset, and automatic ±160px horizontal center shift when left/right HUD panels are open), toggled via Tab and V (plus F9 fade and F10 names).
    • Implements Room/chunk-based fog-of-war exploration reveal (0x6fb12af0 & 0x6fb12580), 12-segment closed isometric X crosshairs (0x6fb0f1c0, 0x6fb82de8), dynamic portal icons (306/307/300), entrance/warp destination labels (Font6), and top-right right-aligned gold Font16 HUD text (Level Name and Difficulty).

Feature Inventory

Every feature from the Survey phase is assigned to a milestone below. No feature is left unassigned.

# Feature Description Milestone Source
F1 AutoMap.txt 36 Level Names & 20 Tile Names Exact AUTOMAP_LEVEL_NAMES (0..35, 1-to-1 with LvlTypes.txt 1..35 including "1 Monestary") and AUTOMAP_TILE_NAMES (0..19, "fl".."fi"). M1 Survey 1 & 2
F2 AutoMap.txt 3,288-Row Parser & getAutomapCellId Parse all 3,288 rows of data\global\excel\AutoMap.txt by column index, build 36 × 20 buckets, match (Style, StartSequence..EndSequence) (-1 wildcards), and pick valid frame from Cel1..Cel4 (!= -1) via SplitMix64 hash. M1 Survey 1 & 2
F3 0x6fba1528 Cell Priority Weight Table & Deduplication Extract all 49 (cellId, groupId) pairs across 18 priority groups (0..17), implement getAutomapCellWeight, and deduplicate identical (xPixel, yPixel) cells so high-priority doors, exits, stairs, and warps are never overwritten by ordinary walls. M1 Survey 1
F4 1.13c Special Tile, Object & Monster Overrides Apply hidden wall bit 0 skip (flags & 1), Rogue Campfire (Level 2, style=8, seq=0 -> 405), Act 2 Palace Cellar warp (Level 40, type=8, style=25, seq=0 -> 305), Objects.txt AutoMap column, Stash (267 -> 319), Cain (26 & monster IDs 146,254,265,266,473,532,540 -> 267), Portal (59/60 -> 306 or 300 in Level 4), Wirt (268 -> 309), Khalim Chests (405..407 -> 319), Compelling Orb (366), Arcane Waypoint (402 in levelId=74), and MonStats2.txt (!noMap && automapCel > 0). M1 Survey 1 & 2
F5 Pre-Baked Town Base Map Chunk Generation (0x6fb12760) Generate fSaved=1 Act 2 Lut Gholein (levelId=40, Act2Map 4×5 = 20 frames), fSaved=2 Act 4 Pandemonium Fortress (levelId=103, Act4Map 2×2 = 4 frames), and fSaved=3 Act 5 Harrogath (levelId=109, ExTnMap 2×4 = 8 frames) into extras layer with exact 1.13c coordinates. M1 Survey 1
F6 Offline Packing of All 8 Automap DC6 Sprite Sheets (scripts/pack-ui.ts) Pack MaxiMap.dc6, MaxiMapS.dc6, Act2Map.dc6, Act2MapS.dc6, Act4Map.dc6, Act4MapS.dc6, ExTnMap.dc6, ExTnMapS.dc6 into public/ui/ and public/assets/ui/automap/ with exact per-frame dimensions and offsets; update public/ui/manifest.json and src/ui/baked-ui-meta.ts. M1 Survey 1 & 2
F7 Live & Offline 4-Layer Automap Generation Across All 136 Levels Expose layerId (Levels.txt Layer) in src/game/acts.ts, generate { floors, walls, objects, extras } with xPixel = (cellX - cellY) * 8, yPixel = (cellX + cellY) * 4 + (tileType >= 16 ? 24 : 0) in src/game/d2map.ts and scripts/pack-act-assets.ts, fail fast on missing/invalid inputs, re-bake all 136 levels (npm run pack:data), and verify in scripts/verify-packs.ts. M2 Survey 1 & 2
F8 4-Layer Isometric Sprite Automap & Minimap Renderer (src/ui/minimap.ts) Load all 8 packed automap sprite sheets and render floors -> walls -> objects -> extras in strict order using screenX = trunc((xPixel * 10) / nDivisor) - offsetX, screenY = trunc((yPixel * 10) / nDivisor) - offsetY. M3 Survey 1 & 3
F9 Corner Minimap (bMinimap=1) vs Full Automap (bMinimap=0), Viewport, Panel Shift & Hotkeys Corner Minimap (nDivisor=20, *MapS sheets, 279×225 top-right/top-left viewport clipped via gl.scissor, no background box) and Full Automap (nDivisor=10, *Map sheets, arrow-key panning ±8px, Home reset, ±160px center shift when left/right HUD panels open), toggled via Tab and V (plus F9 fade, F10 names). M3 Survey 1 & 3
F10 Room/Chunk-Based Fog-of-War Exploration Reveal (0x6fb12af0 & 0x6fb12580) Reveal current room/chunk and adjacent same-level rooms when player movement octile distance satisfies `max( dx ,
F11 12-Segment Isometric X Crosshair (0x6fb0f1c0, 0x6fb82de8) & Dynamic Portals Render 12-segment closed polygon (0x6fb82de8 scaled by 2, shifted (-1, +5) when bMinimap == 1) for player (RGB(0,0,255) / palette 0x97), party/allies (RGB(0,255,0) + Font6 label), mercenaries/summons (RGB(72,160,52) / RGB(244,244,244)), bosses/superuniques (RGB(255,0,0)), and active town portals (MaxiMap(S) frame 306/307/300). M3 Survey 1 & 3
F12 Entrance/Warp Area Name Labels & Top-Right Gold Font16 HUD Text (0x6fb0f9f0) Render centered Font6 area name labels on revealed entrances/warps (and "Stash" on Stash), and top-right right-aligned gold Font16 HUD text (Level Name and Difficulty) at x = screenWidth - 16. M3 Survey 1 & 3
F13 Full-Spectrum 136-Level Verification & Browser Audit (scripts/verify-automap.ts) Verify all 136 level IDs (Acts 1–5 towns including 40, 103, 109, wildernesses, dungeons) produce non-empty valid automap cells and render cleanly in headless Chrome in both bMinimap=1 and bMinimap=0 modes with zero console/page/WebGL errors, plus Tiers 1–5 E2E tests. M4 Survey 1, 2 & 3

Milestones

# Name Scope Dependencies Status
M1 M1-R1-Automap-Table-And-UI-Sprites Implement src/formats/automap-table.ts (and src/game/automap-table.ts re-export), src/formats/automap-table.test.ts (testing all 3,288 rows, bucket lookup, 0x6fba1528 weight deduplication, 0x6fb82de8 crosshair geometry, and special tile/object/town overrides), and update scripts/pack-ui.ts to pack all 8 automap DC6 sprite sheets (MaxiMap(S), Act2Map(S), Act4Map(S), ExTnMap(S)) into public/ui/ and public/assets/ui/automap/. Sync .agents/teamwork/PROJECT.md to root PROJECT.md. none DONE (src/formats/automap-table.ts, src/game/automap-table.ts, src/formats/automap-table.test.ts 11/11 passed, scripts/pack-ui.ts 8 sheets packed)
M2 M2-R2-Map-Scene-Automap-Baking Update src/game/acts.ts (layerId), src/game/d2map.ts (buildIsoMapScene), scripts/pack-act-assets.ts (bakeDs1Variant), package.json (pack:data), and scripts/verify-packs.ts to generate and verify the 4 ordered automap cell layers (floors, walls, objects, extras) across all 136 levels in both live buildIsoMapScene and baked scene.json. M1 DONE (all 365 scenes across 136 levels baked in samples/d2-packs, npm run verify:packs 4061/4061 passed)
M3 M3-R3-Isometric-Automap-Minimap-UI Rewrite src/ui/minimap.ts and update src/ui/hud-manager.ts and src/scene/act-scene.ts to implement the authentic 1.13c 4-layer isometric sprite renderer, Corner Minimap (bMinimap=1, nDivisor=20, 279×225) & Full Automap (bMinimap=0, nDivisor=10, arrow-key panning, ±160px panel shift), Tab/V/F9/F10 hotkeys, room fog-of-war reveal, 12-segment crosshairs, portal icons, warp labels, top-right gold Font16 HUD text, and unit tests (tests/automap-renderer.test.ts). M1, M2 DONE (src/ui/minimap.ts, src/ui/hud-manager.ts, src/scene/act-scene.ts, tests/automap-renderer.test.ts 13/13 passed, scripts/verify-automap.ts 1556/1556 passed)
E2E E2E-Testing-Track Design and implement TEST_INFRA.md, 4-tier requirement-driven opaque-box E2E test suite (tests/e2e-automap-113c/tier1..tier4.test.ts), and 136-level + headless browser verification script (scripts/verify-automap.ts); publish TEST_READY.md. M1, M2, M3 DONE (tests/e2e-automap-113c/tier1..4.test.ts 160/160 passed, TEST_INFRA.md, TEST_READY.md)
M4 M4-Final-E2E-And-Tier5-Hardening Phase 1: Pass 100% of E2E tests (Tiers 1–4), npm run typecheck, npm test, npm run verify:packs, npm run verify:acts, npm run verify:all, and scripts/verify-automap.ts across all 136 levels. Phase 2: Tier 5 white-box adversarial coverage hardening (tests/e2e-automap-113c/tier5-adversarial-coverage.test.ts). M1, M2, M3, E2E IN_PROGRESS

Interface Contracts

1. src/formats/automap-table.ts (re-exported by src/game/automap-table.ts)

export type AutomapAtlasSlot = 0 | 1 | 2 | 3 // 0 = MaxiMap(S), 1 = Act2Map(S), 2 = Act4Map(S), 3 = ExTnMap(S)

export interface AutomapRow {
  readonly rowIndex: number
  readonly levelName: string
  readonly tileName: string
  readonly levelTypeId: number // 0..35
  readonly tileType: number    // 0..19
  readonly style: number       // -1 or 0..N
  readonly startSequence: number // -1 or 0..N
  readonly endSequence: number   // -1 or 0..N
  readonly cels: readonly number[]
  readonly celCount: number
}

export interface AutomapTable {
  readonly rows: readonly AutomapRow[]
  readonly buckets: ReadonlyArray<ReadonlyArray<readonly AutomapRow[]>> // [36][20]
  readonly levelRanges: ReadonlyArray<{ readonly start: number; readonly end: number }> // [36]
}

export interface AutomapCell {
  readonly cellNo: number
  readonly xPixel: number
  readonly yPixel: number
  readonly cellX: number
  readonly cellY: number
  readonly fSaved?: AutomapAtlasSlot
  readonly weight?: number
}

export interface AutomapLayerData {
  readonly layerId: number
  readonly levelTypeId: number
  readonly floors: readonly AutomapCell[]
  readonly walls: readonly AutomapCell[]
  readonly objects: readonly AutomapCell[]
  readonly extras: readonly AutomapCell[]
}

export const AUTOMAP_LEVEL_NAMES: readonly string[] // length 36 (0..35)
export const AUTOMAP_TILE_NAMES: readonly string[]  // length 20 (0..19)
export const AUTOMAP_CELL_WEIGHT_PAIRS: ReadonlyArray<readonly [number, number]> // 49 pairs across groups 0..17
export const AUTOMAP_CROSSHAIR_VERTICES: ReadonlyArray<readonly [number, number]> // 13 pairs from 0x6fb82de8
export const AUTOMAP_CROSSHAIR_SCALED_VERTICES: ReadonlyArray<readonly [number, number]> // 13 pairs scaled by 2

export function parseAutomapTable(raw: Uint8Array | string): AutomapTable
export function getAutomapLevelTypeId(levelNameOrLvlTypeName: string | number): number
export function getAutomapTileType(tileName: string): number
export function getAutomapCellId(
  table: AutomapTable,
  levelTypeId: number,
  tileType: number,
  style: number,
  sequence: number,
  cellX?: number,
  cellY?: number,
  seed?: number,
): number
export function getAutomapCellWeight(cellNo: number): number
export function computeAutomapTilePixelCoords(cellX: number, cellY: number, tileType: number): { xPixel: number; yPixel: number }
export function computeAutomapObjectPixelCoords(subX: number, subY: number): { xPixel: number; yPixel: number }
export function resolveTileAutomapCellId(
  table: AutomapTable,
  params: {
    levelId: number
    levelTypeId: number
    tileType: number
    style: number
    sequence: number
    flags?: number
    cellX?: number
    cellY?: number
    seed?: number
  },
): number
export function resolveObjectAutomapCellId(params: {
  objectClassId: number
  autoMapCol: number
  levelId?: number
  mode?: number
}): number
export function resolveMonsterAutomapCellId(params: {
  monsterClassId: number
  automapCel: number
  noMap?: boolean
  isAlive?: boolean
}): number
export function deduplicateAutomapCells(cells: readonly AutomapCell[]): AutomapCell[]
export function generateTownSavedExtras(
  levelId: number,
  roomOriginSubX?: number,
  roomOriginSubY?: number,
  roomSubWidth?: number,
): AutomapCell[]
export function getCrosshairSegments(
  cx: number,
  cy: number,
  bMinimap: boolean,
): ReadonlyArray<{ readonly x0: number; readonly y0: number; readonly x1: number; readonly y1: number }>

2. Packed UI Atlases (scripts/pack-ui.ts ↔ src/ui/baked-ui-meta.ts & src/ui/minimap.ts)

  • scripts/pack-ui.ts outputs all 8 automap DC6 sprite sheets (MaxiMap.dc6, MaxiMapS.dc6, Act2Map.dc6, Act2MapS.dc6, Act4Map.dc6, Act4MapS.dc6, ExTnMap.dc6, ExTnMapS.dc6) to:
    • public/ui/automap-maxi.png, public/ui/automap-maxis.png, public/ui/automap-act2.png, public/ui/automap-act2s.png, public/ui/automap-act4.png, public/ui/automap-act4s.png, public/ui/automap-extn.png, public/ui/automap-extns.png (and recorded in public/ui/manifest.json + src/ui/baked-ui-meta.ts), AND
    • public/assets/ui/automap/MaxiMap.png, MaxiMapS.png, Act2Map.png, Act2MapS.png, Act4Map.png, Act4MapS.png, ExTnMap.png, ExTnMapS.png + public/assets/ui/automap/manifest.json with exact per-frame { width, height, offsetX, offsetY, x, y } metadata.

3. Scene & Runtime Automap Data (src/game/d2map.ts & scripts/pack-act-assets.ts ↔ src/scene/act-scene.ts & src/ui/minimap.ts)

  • Both IsoMapScene (src/game/d2map.ts) and scene.json (PackSceneJson in scripts/pack-act-assets.ts and src/scene/act-scene.ts) include:
    • automap: AutomapLayerData ({ layerId, levelTypeId, floors, walls, objects, extras }).
  • src/ui/minimap.ts (Minimap) exposes:
    • visible: boolean (toggled by Tab)
    • bMinimap: boolean (or isMinimap: boolean, default true = Corner Minimap nDivisor = 20; false = Full-Screen Automap nDivisor = 10, toggled by V)
    • fadeMode: 0 | 1 | 2 (toggled by F9)
    • showNames: boolean (toggled by F10)
    • panX: number, panY: number (stepped ±8px by Arrow keys when Full-Screen Automap is active; reset by Home)
    • reveal(level: MinimapLevel, subXOrCellX: number, subYOrCellY: number): void (implementing 1.13c room/chunk fog-of-war reveal with octile movement threshold max(|dx|,|dy|)*2 + min(|dx|,|dy|) >= 160 sub-units / room transition)
    • draw(...) & drawHudOverlay(ctx: CanvasRenderingContext2D, font: D2FontRenderer, ...) (rendering 4 sprite layers, 12-segment crosshairs, dynamic portals, warp/stash labels, and top-right gold Font16 Level Name + Difficulty)

Code Layout

  • src/formats/automap-table.ts & src/game/automap-table.ts: 1.13c AutoMap.txt parser, 36 × 20 bucket lookup, 0x6fba1528 weight table & deduplication, special tile/object/monster/town overrides, and 0x6fb82de8 crosshair geometry.
  • src/formats/automap-table.test.ts: Comprehensive unit test suite for automap-table.ts.
  • scripts/pack-ui.ts, public/ui/, public/assets/ui/automap/, src/ui/baked-ui-meta.ts: Offline UI packer and packed atlas metadata for all 8 automap DC6 sprite sheets.
  • src/game/acts.ts, src/game/d2map.ts, scripts/pack-act-assets.ts, scripts/verify-packs.ts: Live and offline 4-layer automap cell generation and 136-level scene.json baking.
  • src/ui/minimap.ts, src/ui/hud-manager.ts, src/scene/act-scene.ts: Authentic 1.13c isometric sprite Automap & Minimap renderer, input bindings (Tab, V, F9, F10, Arrow keys, Home), room reveal, 12-segment crosshairs, warp labels, and top-right HUD text.
  • tests/automap-renderer.test.ts: Unit tests for src/ui/minimap.ts renderer, room reveal, crosshairs, projection, and HUD/input integration.
  • tests/e2e-automap-113c/ & scripts/verify-automap.ts: 4-tier requirement-driven E2E test suite, Tier 5 adversarial coverage tests, and 136-level + headless Chrome CDP verification script.