# 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 (5–70 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 (0–2400 m) are calibrated for terrain; sky meshes are authored at radii 1050–14271 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` 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` (`` 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@@` 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.