acdream/docs/ISSUES.md
Erik 7da2a027d4 feat(player): #5 PlayerDescription parser — Stam/Mana via attribute block
Visual-verified — Vitals window now shows three bars (HP/Stam/Mana)
with live values. Closes ISSUES.md #5; ~95% reading on Stam/Mana
traced to active buff multipliers, filed as #6.

Why the rewrite

The first attempt (commit d42bf57) routed PlayerDescription (0x0013)
through AppraiseInfoParser, trusting a misleading xmldoc claim.
Live diagnostics proved the format is wrong — ACE source
(GameEventPlayerDescription.WriteEventBody) hand-writes a body
distinct from IdentifyObjectResponse's AppraiseInfo: property
hashtables gated on DescriptionPropertyFlag, vector-flag-gated
attribute / skill / spell blocks, then a long options + inventory
trailer. Vitals only arrive via the attribute block at login.
Holtburger's events.rs:220-625 has the canonical client-side
unpacker; this commit ports the early-section walker through spells.

What landed

  PlayerDescriptionParser.cs (new — 350 LOC):
    Walks propertyFlags + weenieType, then property hashtables
    (Int32/Int64/Bool/Double/String/Did/Iid) + Position table —
    each gated on a property flag bit, header is `u16 count, u16
    buckets`. Then vectorFlags + has_health + the attribute block
    (primary attrs 1..6 = 12 B each, vitals 7..9 = 16 B with
    `current`), then optional Skill + Spell tables. Stops cleanly
    before the options/shortcuts/hotbars/inventory trailer (filed
    as #7 — heuristic alignment search needed for gameplay_options).

  PrivateUpdateVital.cs (new — 95 LOC):
    Wire parsers for the GameMessage opcodes 0x02E7 (full snapshot)
    and 0x02E9 (current-only delta), per holtburger UpdateVital +
    UpdateVitalCurrent. WorldSession dispatches each to a session-
    level event the GameWindow forwards into LocalPlayerState.

  LocalPlayerState (full redesign):
    VitalKind (Health/Stamina/Mana) + AttributeKind (six primary).
    VitalSnapshot stores ranks/start/xp/current; AttributeSnapshot
    stores ranks/start/xp with `Current = ranks+start` per
    holtburger. GetMaxApprox computes the retail formula
        vital.(ranks+start) + attribute_contribution
    where the contribution is hardcoded from retail's
    SecondaryAttributeTable: Endurance/2 for Health, Endurance for
    Stamina, Self for Mana. Enchantment buffs not yet folded in
    (filed as #6). VitalIdToKind now accepts both ID systems
    (1..6 wire, 7..9 PD attribute block); AttributeIdToKind covers
    primary attrs 1..6.

  GameEventWiring:
    PlayerDescription handler. Walks parsed.Attributes, routes
    primary attrs (id 1..6) to OnAttributeUpdate and vitals
    (id 7..9) to OnVitalUpdate. Player's full learned spellbook
    also lands here. ACDREAM_DUMP_VITALS=1 traces every PD attribute
    + every PrivateUpdateVital(Current) opcode for diagnostics.

  WorldSession:
    Dispatch chain re-ordered — the diagnostic else-if for
    ACDREAM_DUMP_OPCODES=1 was originally placed before
    GameEventEnvelope.Opcode, which silently intercepted 0xF7B0 and
    broke UpdateHealth dispatch when the env var was set. Moved to
    the very end of the chain so it only fires for genuinely
    unhandled opcodes. (Diagnostic-only regression; production
    launches without the env var were unaffected.)

Test deltas

  Added:
    - PlayerDescriptionParserTests (6 — empty header, full attribute
      block, partial flags, post-property-table walk, spell table)
    - PrivateUpdateVitalTests (7 — fixture round-trip, vital ID
      coverage, opcode rejection, truncation)
    - LocalPlayerStateTests rewritten (20 — VitalIdToKind +
      AttributeIdToKind theories, Endurance/Self formula coverage,
      delta semantics, change events)
    - GameEventWiringTests for PlayerDescription dispatch (2 —
      end-to-end populate + spellbook feed)
  Updated:
    - VitalsVMTests rephrased onto the new OnVitalUpdate API.
  Total: 765 → 817 tests passing.

Diagnostics

  ACDREAM_DUMP_VITALS=1 — log every PD attribute extracted,
    every 0x02E7/0x02E9 dispatch.
  ACDREAM_DUMP_OPCODES=1 — log first occurrence of any unhandled
    GameMessage opcode (now correctly placed at end of chain).

Visual verify

  $env:ACDREAM_DEVTOOLS = "1"
  dotnet run --project src\AcDream.App\AcDream.App.csproj -c Debug

  Vitals window shows three bars; HP at 100%, Stam/Mana at ~95%
  (the gap is buff enchantments — filed as #6 with the holtburger
  multiplier+additive aggregator pattern as the reference for the
  fix).

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

13 KiB
Raw Blame History

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.

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.csUpdateWeatherParticles (~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.csFogParams.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.csSyncFromServer(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.csGetMaxApprox 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.



Recently closed

#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.