acdream/docs/ISSUES.md
Erik 567078803f docs(issues): #8/#9/#11 filed; #10 wired (KillerNotification)
Files four new issues created by the 2026-04-25 PDB-discovery sprint:
  #8  (DONE 2026-04-25) — pdb-extract tool, shipped 69d884a
  #9  (OPEN)            — function-map address-correction sweep
                          (Phase E will close)
  #10 (DONE 2026-04-25) — wire KillerNotification (0x01AD); orphan
                          parser at GameEvents.ParseKillerNotification
                          existed but was never registered. This commit
                          adds CombatState.OnKillerNotification +
                          KillLanded event, registers the dispatcher
                          handler, and adds a regression test.
  #11 (OPEN)            — spell metadata loader (spells.csv → SpellTable)
                          (Phase F will close)

Code change is minimal — three lines of dispatch + a 12-line
CombatState method with a typed event for future killfeed UI.

818 tests passing (+1 KillerNotification).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:39:47 +02:00

264 lines
18 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.

# acdream — known issues + small deferred features
Rolling tactical list. What goes here:
- **Bugs**: user-visible defects we've observed but haven't fixed yet.
- **Small deferred features**: work that fits in one or two commits.
Anything larger should be a named Phase in the [roadmap](plans/2026-04-11-roadmap.md).
What does NOT go here:
- Large multi-commit work → add a Phase to the roadmap instead.
- Ideas / wishlist → `docs/plans/`.
- Design questions → open a `docs/research/*.md` note.
## Conventions
- Sequential integer IDs (`#1`, `#2`, …). Commits that close an issue reference the ID in the message (e.g. `fix #3: periodic TimeSync parsing`).
- `Status` is `OPEN`, `IN-PROGRESS`, or `DONE`. DONE items move to the **Recently closed** section at the bottom with closed-date + commit SHA.
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
## Template
Copy this block when adding a new issue:
```
## #NN — Short title
**Status:** OPEN
**Severity:** HIGH | MEDIUM | LOW
**Filed:** YYYY-MM-DD
**Component:** e.g. sky, physics, net, ui
**Description:** One paragraph — what's wrong or what's missing.
**Root cause / status:** What we know so far. Empty if unknown.
**Files:** Path references with approximate line numbers.
**Research:** Links to `docs/research/*.md` if applicable.
**Acceptance:** How we'll know it's fixed.
```
---
# Active issues
## #1 — Rain falls only to horizon, not to the player's feet
**Status:** OPEN
**Severity:** MEDIUM
**Filed:** 2026-04-25
**Component:** weather / particles
**Description:** During Rainy DayGroups, rain particles are visible in the upper sky band but fade out before reaching the camera / ground level. Retail's rain falls all the way past the camera to the terrain.
**Root cause / status:** Unknown. Likely one of: (a) particle emitter volume too short in Z, (b) particle lifetime shorter than the time it takes to traverse emitter-top → ground, (c) emitter anchored in world-space so particles escape the player's reference frame as they fall, (d) camera-relative spawn origin is offset too high above the player.
**Files:**
- `src/AcDream.App/Rendering/GameWindow.cs``UpdateWeatherParticles` (~line 4591)
- `src/AcDream.Core/Vfx/ParticleSystem.cs` — emitter spawn config + lifetime integration
**Research:** `docs/research/deepdives/r12-weather-daynight.md` (rain mechanism — but does not pin volume / lifetime values).
**Acceptance:** Standing at 9,115 in Holtburg during a Rainy DayGroup, rain drops visibly fall all the way from the sky band past the camera to the ground level.
---
## #2 — Lightning visual not wired (dat-baked PES triggers)
**Status:** OPEN
**Severity:** MEDIUM
**Filed:** 2026-04-25
**Component:** weather / sky / vfx
**Description:** Retail's Rainy DayGroup in the Dereth Region dat contains 12+ `SkyObject` entries with non-zero `PesObjectId` and narrow visibility windows (570 ms at keyframe-boundary moments) that drive PhysicsScript-authored flash + thunder effects. We render the sky meshes but ignore the PES path, so no lightning flashes appear during storms. The fragment-shader flash bump on `uFogParams.z` is already wired in `sky.frag` — only the CPU-side PES→runner wire is missing.
**Root cause / status:** Research complete. Implementation is: in `SkyRenderer.Render`, detect visibility-window entry on any SkyObject with `obj.PesObjectId != 0`, call `PhysicsScriptRunner.Play(pesObjectId, ownerId: sky-owner, anchorPos: camera)`, and route any `SetFlash` / `Sound` hooks from the script into `uFogParams.z` + audio.
**Files:**
- `src/AcDream.App/Rendering/Sky/SkyRenderer.cs` — add per-SkyObject PES dispatch inside the visibility loop
- `src/AcDream.Core/Vfx/PhysicsScriptRunner.cs` — already shipped (Phase 6a); exposes `Play(scriptId, entityId, anchorWorldPos)`
- `src/AcDream.Core/Lighting/SceneLightingUbo.cs``FogParams.Z` is the flash slot; needs a sink that bumps it and decays
- `src/AcDream.App/Rendering/Shaders/sky.frag` — flash bump already wired (`rgb += flash * vec3(1.5, 1.5, 1.8)`)
**Research:**
- `docs/research/2026-04-23-lightning-real.md` (decompile trace + dat discovery)
- `docs/research/2026-04-23-physicsscript.md` (runtime semantics)
- `docs/research/2026-04-23-lightning-crossfade.md` (crossfade mechanism)
**Acceptance:** During a Rainy DayGroup's storm window, visible flashes appear in the sky at the dat-scripted moments, the fragment-shader flash bump briefly brightens the scene, and (later, once thunder audio is wired) a thunder clap plays with a short propagation delay.
---
## #3 — Client clock drifts from retail after ~10 minutes (periodic TimeSync missing)
**Status:** OPEN
**Severity:** MEDIUM
**Filed:** 2026-04-25
**Component:** net / sky
**Description:** Our `WorldTimeService.DayFraction` syncs with the server once at login via `ConnectRequest + TimeSync`, then advances from the local wall-clock. Retail receives periodic `TimeSync` refreshes (header flag `0x1000000`) carrying a fresh `PortalYearTicks double` and re-anchors its clock. Without those, acdream's keyframe state drifts from retail's over 10+ minutes — observed during the 2026-04-24 sky-color debug sessions where retail was at DayFraction 0.976 while acdream was at 0.634.
**Root cause / status:** Mechanism is well-understood (see research). `WorldTimeService.SyncFromServer(double)` already exists — we just need to detect the periodic flag in the packet header and call it whenever a fresh tick arrives.
**Files:**
- `src/AcDream.Core.Net/WorldSession.cs` — header-flag parsing; currently only the initial sync is consumed
- `src/AcDream.Core/World/WorldTimeService.cs``SyncFromServer(double ticks)` ready; needs caller wiring
**Research:** `docs/research/deepdives/r12-weather-daynight.md` §TimeSync (line ~563). References retail packet-header flag `0x1000000` carrying `PortalYearTicks double`.
**Acceptance:** Probe retail via `tools/RetailTimeProbe` and acdream's ACDREAM_DUMP_SKY log at the same wall-clock moment after a 20-minute session without re-login; `abs(acdream.DayFraction - retail.DayFraction) < 0.01`.
---
## #6 — Vital max ignores enchantment buffs + vitae
**Status:** OPEN
**Severity:** LOW (3-5% accuracy gap on a HUD bar)
**Filed:** 2026-04-25
**Component:** ui / player-state
**Description:** `LocalPlayerState.GetMaxApprox` computes the unenchanted base max for HP/Stam/Mana — `vital.(ranks+start) + attribute_contribution` with retail's hardcoded coefficients (Endurance/2, Endurance, Self). Live test shows bars at ~95% when buffs are presumably active (server character is `+Acdream`, GM-marker char with likely buff stack). Holtburger's `calculate_vital_current` adds `× multiplier + additive` from the active enchantment list — that's the missing 5%.
**Root cause / status:** Need to fold `Spellbook.ActiveEnchantments` into the max calc. Holtburger's `magic.rs` aggregates by `EnchantmentTypeFlags::SECOND_ATT` masked with the vital id. The same data already arrives via `MagicUpdateEnchantment` events that we wire into `Spellbook`.
**Files:**
- `src/AcDream.Core/Player/LocalPlayerState.cs``GetMaxApprox` returns the base; extend to also call `Spellbook` for vital-typed enchantment aggregation.
- `src/AcDream.Core/Spells/Spellbook.cs` — needs an aggregator helper similar to holtburger's `get_vital_multiplier` / `get_vital_additive`.
**Research:** holtburger `crates/holtburger-world/src/player/stats_calc.rs:91-111`, `magic.rs` get_enchantment_multiplier / additive.
**Acceptance:** A `+Acdream` login shows Stam/Mana percent within 1% of retail's reading once any active buff multipliers are applied. (HP already at 100% indicates the unbuffed Health formula is already correct on its own.)
---
## #7 — PlayerDescription parser stops after spells (options/inventory/equipped not extracted)
**Status:** OPEN
**Severity:** LOW (Issue #5 needed only the early sections; later panels will need the rest)
**Filed:** 2026-04-25
**Component:** net / player-state
**Description:** Current `PlayerDescriptionParser` walks through `Attribute / Skill / Spell` vector-flag blocks per holtburger but stops before the trailing options / shortcuts / hotbars / desired_comps / spellbook_filters / options2 / gameplay_options / inventory / equipped sections. Future panels (hotbar, inventory, character options) need that data; the parser will need extension. Holtburger's events.rs:462-625 has the full layout; the messy parts are `gameplay_options` (variable-length opaque blob requiring heuristic skip) and `desired_comps`.
**Root cause / status:** Just a scope decision — port-the-easy-bits-first approach. Reference shape lives in holtburger's full `unpack`; the only complex piece is `find_inventory_start_after_gameplay_options` (heuristic alignment search).
**Files:**
- `src/AcDream.Core.Net/Messages/PlayerDescriptionParser.cs` — extend `Parsed` record + walker.
- `tests/AcDream.Core.Net.Tests/PlayerDescriptionParserTests.cs` — add coverage with synthetic payloads of the trailing sections.
**Research:** holtburger `crates/holtburger-protocol/src/messages/player/events.rs:462-625` (full unpacker including the heuristic `find_inventory_start_after_gameplay_options`).
**Acceptance:** All sections of a real-world PlayerDescription parse to completion — verified via a packet capture or by feeding synthetic test fixtures covering every flag combination.
---
## #4 — Sky horizon-glow disabled (fog-mix skipped on sky meshes)
**Status:** OPEN
**Severity:** LOW (aesthetic feature-parity, not regression from pre-session state)
**Filed:** 2026-04-25
**Component:** sky
**Description:** Phase 8.1 (commit `593b76f`) disabled the fog-mix on sky meshes to fix the "entire dome swallowed by fog color" regression. Dereth's keyframe `FogEnd` values (02400 m) are calibrated for terrain; sky meshes are authored at radii 105014271 m so every sky pixel was past `FogEnd`, saturated to `uFogColor`, destroying stars / moon / dome texture. Disabling the mix restored visibility but we lost retail's horizon-glow effect (gradient from clear zenith to fog-tinted horizon band at dusk/dawn).
**Root cause / status:** Three competing hypotheses, none pinned down: (a) retail uses a **different** fog range for sky than terrain; (b) retail applies fog with an **elevation-angle** weighting rather than linear distance; (c) retail's sky meshes **don't participate** in the global fog and the "horizon glow" comes from a different atmospheric-scatter path. Need to identify retail's actual sky-fog behaviour before re-enabling with correct parameters.
**Files:**
- `src/AcDream.App/Rendering/Shaders/sky.frag` — line ~55, `rgb = mix(uFogColor.rgb, rgb, vFogFactor)` currently commented out
- `src/AcDream.App/Rendering/Shaders/sky.vert` — lines 109-114, `vFogFactor` computation
**Research:** `docs/research/2026-04-23-sky-fog.md`. Partial; doesn't pin the sky-specific fog path.
**Acceptance:** At dusk in Holtburg, the sky dome shows a clear zenith and a warm fog-tinted horizon band that matches retail's appearance, with stars / moon / sun / clouds all still visible at their correct brightnesses elsewhere in the frame.
---
## #9 — Address-correction sweep on `acclient_function_map.md`
**Status:** OPEN
**Severity:** LOW (per-developer convenience; gets us correct symbol→address mapping for ~71 hand-curated entries)
**Filed:** 2026-04-25
**Component:** docs / research
**Description:** The hand-curated function map at `docs/research/acclient_function_map.md` has ~71 entries with addresses derived from older Ghidra chunk inspection. The PDB-extracted `docs/research/named-retail/symbols.json` (Sept 2013 EoR build) is now the authoritative name-source. Several entries point at mid-function offsets rather than function starts. E.g. our `FUN_005111d0 = UpdatePhysicsInternal` — actual start in PDB is `0x510700`. Need a sweep that verifies + corrects all hand-curated rows.
**Root cause / status:** PDB built from a slightly different revision (~0xC00 byte delta on some functions); legacy Ghidra-derived addresses don't all line up. Match by name (PDB names are ground truth) and record the corrected address.
**Files:**
- `docs/research/acclient_function_map.md` — corrections in-place.
- `docs/research/named-retail/symbols.json` — name→address lookup source.
**Acceptance:** Spot-check 10 entries across all sections — each row's address matches `symbols.json` for the named function. Mismatches annotated `(corrected from FUN_xxx, was mid-body)`.
---
## #11 — Spell metadata loader (`spells.csv` → `SpellTable`)
**Status:** OPEN
**Severity:** LOW (unblocks issue #6's stacking aggregation; also adds tooltip / icon / school metadata for future panels)
**Filed:** 2026-04-25
**Component:** core / spells
**Description:** `docs/research/data/spells.csv` (3,956 spells × 35 cols) has all the per-spell metadata the existing `Spellbook` lacks: `Name`, `School`, `Family` (buff stacking bucket), `IconId`, `Mana`, `Duration`, `IsDebuff`, `IsFellowship`, `Description`. Need a `SpellTable` loader that hydrates a `Dictionary<uint, SpellMetadata>` at startup so `Spellbook.TryGetMetadata(spellId, out)` works.
**Root cause / status:** Issue #6 (vital max ignores enchantment buffs) needs `Family` to do correct stacking aggregation (only one buff per family wins; highest generation). That field comes only from `spells.csv`.
**Files:**
- `src/AcDream.Core/Spells/SpellMetadata.cs` (new record).
- `src/AcDream.Core/Spells/SpellTable.cs` (new loader).
- `src/AcDream.App/Rendering/GameWindow.cs` (load at OnLoad).
- `src/AcDream.App/AcDream.App.csproj` (`<None Update>` to copy CSV to bin output).
- `src/AcDream.Core/Spells/Spellbook.cs` (accept optional `SpellTable`, expose `TryGetMetadata`).
**Acceptance:** Launch with `ACDREAM_DEVTOOLS=1` shows console line `spells: loaded 3956 entries from spells.csv`. `Spellbook.TryGetMetadata(spellId, out)` returns valid record for active enchantment lookups.
---
---
# Recently closed
## #10 — [DONE 2026-04-25] Wire `KillerNotification (0x01AD)`
**Closed:** 2026-04-25
**Commit:** `docs(issues): #8/#9/#11 filed; #10 wired (KillerNotification)`
**Resolution:** Orphan parser at `GameEvents.ParseKillerNotification` existed but was never registered for dispatch in `GameEventWiring.cs`. Added a `combat.OnKillerNotification(victimName, victimGuid)` method on `CombatState` that fires a new `KillLanded` event, then registered the handler. One-line dispatch + 12-line CombatState method + one regression test fixture in `GameEventWiringTests`.
---
## #8 — [DONE 2026-04-25] pdb-extract tool: PDB → symbols.json + types.json
**Closed:** 2026-04-25
**Commit:** `tools(pdb-extract): #8 PDB -> symbols.json + types.json sidecar`
**Resolution:** Pure-Python (no deps) MSF 7.00 PDB parser at `tools/pdb-extract/pdb_extract.py`. Reads `refs/acclient.pdb` (Sept 2013 EoR build), extracts S_PUB32 records from the symbol stream + named class/struct types from TPI, and writes JSON sidecars to `docs/research/named-retail/`:
- `symbols.json` — 18,366 named functions (`address` + demangled `name` + raw `mangled`)
- `types.json` — 5,371 named class/struct records (`name` + `size` + `kind`)
Best-effort MSVC C++ demangler handles the common `?Method@Class@@<sig>` patterns + ctors (`??0`) + dtors (`??1`); operator overloads and vtables left mangled. Spot-check verified: `CEnchantmentRegistry::EnchantAttribute` resolves to `0x00594570` exactly as the discovery agent reported. Runtime <1s.
Regen workflow: `py tools/pdb-extract/pdb_extract.py refs/acclient.pdb`. The committed JSON outputs are stable + ~3 MB combined; ripgrep/jq on them is faster than re-parsing.
---
## #5 — [DONE 2026-04-25] VitalsPanel stamina/mana bars always null
**Closed:** 2026-04-25
**Commit:** `feat(player): #5 PlayerDescription parser — Stam/Mana via attribute block`
**Resolution:** First attempt (commit `d42bf57`) used `AppraiseInfoParser` for `PlayerDescription (0x0013)` wrong wire format. ACE source confirmed via `GameEventPlayerDescription.WriteEventBody`: PlayerDescription is hand-written (DescriptionPropertyFlag-driven property hashtables, vector flags, attribute block, skills, spells, options/inventory tail) distinct from `IdentifyObjectResponse (0x00C9)`'s `AppraiseInfo.Write`. Pivoted to a real port: new `PlayerDescriptionParser.cs` that walks property hashtables (Int32/Int64/Bool/Double/String/Did/Iid + Position) gated on the property flags, then reads vector flags + has_health + the attribute block where vitals 7/8/9 carry `ranks/start/xp/current`. Also redesigned `LocalPlayerState` to track per-vital snapshots (replacing the sentinel-API of attempt 1) plus per-attribute snapshots, with `GetMaxApprox` applying the retail formula `vital.(ranks+start) + attribute_contribution` (Endurance/2 for Health, Endurance for Stamina, Self for Mana). Live verified: `+Acdream` shows three bars; ~95% reading on Stam/Mana traced to active buff multipliers (filed as #6). Wire-port also added `PrivateUpdateVital (0x02E7)` + `PrivateUpdateVitalCurrent (0x02E9)` for delta updates per holtburger `UpdateVital`. ~700 LOC C#, 30+ new tests.
<!--
Example:
## #0 — [DONE 2026-04-24 · 593b76f] Sky cube edges visible as cross in daytime sky
**Closed:** 2026-04-24
**Commit:** `593b76f sky(phase-8.1): CLAUDE_TO_EDGE on static sky meshes`
**Resolution:** Switched to `GL_CLAMP_TO_EDGE` wrap mode for static sky
meshes; scrolling cloud layers kept `GL_REPEAT`. The 5 dome walls were
sampling opposite-edge pixels via UV wrap + LINEAR filtering, producing
visible seam lines that formed a cube outline across the view.
-->