Phase K final commit. Settings panel with click-to-rebind UX on top of the K.1+K.2 input architecture, plus the roadmap / ISSUES / memory updates that retire Phase K. InputDispatcher gains BeginCapture / CancelCapture / IsCapturing / SetBindings — modal capture suppresses normal action firing for the next chord. Esc cancels (returns sentinel default chord); modifier-only keys don't complete capture; non-modifier key down with current modifier mask completes. IPanelRenderer + ImGuiPanelRenderer + FakePanelRenderer gain BeginMainMenuBar / EndMainMenuBar / BeginMenu / EndMenu / MenuItem primitives. SettingsVM owns a draft copy of KeyBindings with explicit Save / Cancel / Reset semantics. Click-to-rebind enters dispatcher capture mode; on chord captured, conflict-detect against draft (excluding the action being rebound itself); surface a ConflictPrompt when the chord collides; ResolveConflict(replace=true|false) commits or reverts. ResetActionToDefault restores a single action to RetailDefaults(); ResetAllToDefaults rebuilds the entire draft. Save invokes the onSave callback (which writes JSON + swaps the live dispatcher's bindings). SettingsPanel renders 8 retail-keymap-categorized CollapsingHeader sections (Movement, Postures, Camera, Combat, UI panels, Chat, Hotbar, Emotes). Per action: name + current binding(s) summary + "Rebind"/"Reset" buttons. Conflict prompt at the top when pending. Save / Cancel / "Reset all to retail defaults" at the top. GameWindow registers SettingsPanel + wires F11 → ToggleOptionsPanel → IsVisible toggle, plus a top-of-frame ImGui MainMenuBar with View → Settings/Vitals/Chat/Debug entries (calls ImGui directly — the abstraction methods exist for backend portability but the host doesn't own a menu-bar surface). Tests: +37 across InputDispatcherCaptureTests (7), IPanelRendererMainMenuBarTests (9), SettingsVMTests (13), SettingsPanelTests (8). Solution total 1220 green. Roadmap (docs/plans/2026-04-11-roadmap.md) appends Phase K shipped section after Phase J with K.1a–K.3 commit SHAs. ISSUES.md files Phase L deferred work as #L.1–#L.8 (hotbar UI, spellbook favorites, combat-mode dispatch, F-key panels, floating chat windows, UI layout save/load, joystick bindings, plugin input subscription) and adds #21–#25 to Recently closed. project_input_pipeline.md updated to shipped state. CLAUDE.md gets an input-pipeline reference. Closes Phase K. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
497 lines
28 KiB
Markdown
497 lines
28 KiB
Markdown
# 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
|
||
|
||
## #L.1 — Hotbar UI panel
|
||
|
||
**Status:** OPEN
|
||
**Severity:** MEDIUM
|
||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||
**Component:** ui / hotbar
|
||
|
||
**Description:** Number keys 1-9 are bound to `UseQuickSlot_1..9`
|
||
actions but no panel exists. Actions fire (visible via the `[input]`
|
||
console log) but produce no visible result. Phase L feature: drag-drop
|
||
hotbar with up to 5 bars × 9 slots, drag spell/skill icons to slots,
|
||
key activates the slot's contents. Server-side: `CreateShortcutToSelected`
|
||
(action 0x0A9 in retail motion table) sends a `UseSelected` on slot
|
||
fire.
|
||
|
||
**Files:** `src/AcDream.UI.Abstractions/Panels/Hotbar/` (TBC).
|
||
|
||
**Acceptance:** Drag an item or spell into slot 1, press `1`, server
|
||
responds as if the user clicked the item.
|
||
|
||
---
|
||
|
||
## #L.2 — Spellbook favorites panel
|
||
|
||
**Status:** OPEN
|
||
**Severity:** MEDIUM
|
||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||
**Component:** ui / magic
|
||
|
||
**Description:** In `MagicCombat` scope, 1-9 should fire
|
||
`UseSpellSlot_1..9` (distinct from hotbar). Requires a small UI to
|
||
pin favorite spells + a spellbook tab nav. Cross-references issue
|
||
#L.3 (combat-mode dispatch).
|
||
|
||
---
|
||
|
||
## #L.3 — Combat-mode tracking + scope-aware Insert/PgUp/Delete/End/PgDn dispatch
|
||
|
||
**Status:** OPEN
|
||
**Severity:** MEDIUM
|
||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||
**Component:** input / combat
|
||
|
||
**Description:** Insert/PgUp/Delete/End/PgDn mean different things in
|
||
melee / missile / magic combat modes (per retail keymap MeleeCombat /
|
||
MissileCombat / MagicCombat blocks). Phase K has the bindings and the
|
||
scope stack; what's missing: `CombatState.CurrentMode` field +
|
||
listener for the server-side `SetCombatMode` packet (likely 0x0053 or
|
||
similar — confirm against ACE source). When mode arrives, push the
|
||
appropriate scope; when leaving combat, pop.
|
||
|
||
---
|
||
|
||
## #L.4 — F-key panels: Allegiance / Fellowship / Skills / Attributes / World / SpellComponents
|
||
|
||
**Status:** OPEN
|
||
**Severity:** LOW
|
||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||
**Component:** ui
|
||
|
||
**Description:** Retail F3-F6, F8-F12 toggle UI panels for various
|
||
character data. Phase K has the bindings (`ToggleAllegiancePanel`,
|
||
`ToggleFellowshipPanel`, `ToggleSpellbookPanel`,
|
||
`ToggleSpellComponentsPanel`, `ToggleAttributesPanel`,
|
||
`ToggleSkillsPanel`, `ToggleWorldPanel`, `ToggleInventoryPanel`); the
|
||
panels themselves don't exist. Each is its own design feature.
|
||
Inventory (F12) is the most-requested.
|
||
|
||
---
|
||
|
||
## #L.5 — Floating chat windows (Alt+1-4)
|
||
|
||
**Status:** OPEN
|
||
**Severity:** LOW
|
||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||
**Component:** ui / chat
|
||
|
||
**Description:** Alt+1..4 toggle four floating chat windows in retail.
|
||
Phase K binds the actions; `ChatPanel` currently is a single window.
|
||
Floating windows would need filtered-by-channel-type chat tail
|
||
rendering.
|
||
|
||
---
|
||
|
||
## #L.6 — UI layout save/load (saveui / loadui / lockui)
|
||
|
||
**Status:** OPEN
|
||
**Severity:** LOW
|
||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||
**Component:** ui
|
||
|
||
**Description:** Retail had `@saveui <name>`, `@loadui <name>`,
|
||
`@lockui` commands for persisting ImGui-style window layouts. ImGui
|
||
has built-in `LoadIniSettingsFromMemory` /
|
||
`SaveIniSettingsToMemory` — wire these to per-named-layout files,
|
||
plus chat-command parsing for the `@` prefixes.
|
||
|
||
---
|
||
|
||
## #L.7 — Joystick / gamepad bindings
|
||
|
||
**Status:** OPEN
|
||
**Severity:** LOW
|
||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||
**Component:** input
|
||
|
||
**Description:** Retail keymap declares 11 Joystick devices in the
|
||
`Devices` block but no actions are bound by default. acdream uses
|
||
Silk.NET keyboard+mouse only. Adding Silk.NET joystick support + a
|
||
`JoystickInputSource` adapter would unlock controller play.
|
||
`KeyChord.Device` byte already supports values >1, so the binding
|
||
side is ready.
|
||
|
||
---
|
||
|
||
## #L.8 — Plugin / scripting / macro input subscription
|
||
|
||
**Status:** OPEN
|
||
**Severity:** MEDIUM
|
||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||
**Component:** plugin / input
|
||
|
||
**Description:** CLAUDE.md goal: "Build acdream's plugin API to
|
||
support scripting/macros for player automation." Plugins should be
|
||
able to register custom actions (with namespaced IDs like
|
||
`mymacro.heal-rotation`) and subscribe to `InputAction` events. Phase K
|
||
foundation supports this via the multicast `InputDispatcher`; what's
|
||
missing is the plugin-API surface.
|
||
|
||
---
|
||
|
||
## #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`.
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## #13 — PlayerDescription trailer past enchantments (options / shortcuts / hotbars / desired_comps / spellbook_filters / options2 / gameplay_options / inventory / equipped)
|
||
|
||
**Status:** OPEN
|
||
**Severity:** LOW (no current user-visible bug; future panels will need the data)
|
||
**Filed:** 2026-04-25
|
||
**Component:** net / player-state
|
||
|
||
**Description:** `PlayerDescriptionParser` walks through enchantments (Phase H, 2026-04-25). The trailer beyond that — Options1 / Shortcuts / HotbarSpells (8 lists) / DesiredComps / SpellbookFilters / Options2 / GameplayOptions blob / Inventory / Equipped — is not yet parsed. Required for future Spellbook UI panel, hotbar UI, inventory UI, character options panel.
|
||
|
||
**Root cause / status:** Holtburger `events.rs:462-625` has the full layout. The trickiest piece is `gameplay_options` — a variable-length opaque blob; holtburger uses a heuristic forward search (`find_inventory_start_after_gameplay_options`) for plausibly-aligned inventory-count + GUID pairs to find the inventory start. Other sections are well-formed.
|
||
|
||
**Files:**
|
||
- `src/AcDream.Core.Net/Messages/PlayerDescriptionParser.cs` — extend `Parsed` record + walker.
|
||
- `tests/AcDream.Core.Net.Tests/PlayerDescriptionParserTests.cs` — add fixtures per section.
|
||
- `src/AcDream.Core.Net/GameEventWiring.cs` — route `parsed.Inventory` + `Equipped` to ItemRepository.
|
||
|
||
**Research:** holtburger `events.rs:462-625`; `references/actestclient/TestClient/messages.xml`.
|
||
|
||
**Acceptance:** All sections of a real-world PlayerDescription parse to completion (no truncation). New tests cover synthetic fixtures per section. `ItemRepository.Count` after login > 0.
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## #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.
|
||
|
||
---
|
||
|
||
---
|
||
|
||
# Recently closed
|
||
|
||
## #25 — [DONE 2026-04-26] Phase K.3 — Settings panel + click-to-rebind UI
|
||
|
||
**Closed:** 2026-04-26
|
||
**Commit:** `(this commit)`
|
||
**Resolution:** `SettingsPanel` with click-to-rebind UX (modal capture
|
||
via `InputDispatcher.BeginCapture`, Esc cancels, conflict prompt with
|
||
Yes/No, draft / Save / Cancel semantics), F11 toggle + ImGui
|
||
MainMenuBar entry, per-action / per-section / reset-all-defaults
|
||
buttons. Roadmap + ISSUES + memory crib + CLAUDE.md updated.
|
||
|
||
---
|
||
|
||
## #24 — [DONE 2026-04-26] Phase K.2 — auto-enter player mode + MMB mouse-look
|
||
|
||
**Closed:** 2026-04-26
|
||
**Commit:** `af74eac`
|
||
**Resolution:** Auto-enter player mode at login (one-shot guard
|
||
reusing the existing Tab handler logic); MMB-hold mouse-look
|
||
(`CameraInstantMouseLook` — cursor-locked camera + character yaw
|
||
drive together); `Tab → ChatPanel.FocusInput()`; `DebugPanel`
|
||
"Toggle Free-Fly Mode" button.
|
||
|
||
---
|
||
|
||
## #23 — [DONE 2026-04-26] Phase K.1c — retail-default keymap + JSON persistence
|
||
|
||
**Closed:** 2026-04-26
|
||
**Commit:** `da18910`
|
||
**Resolution:** ~149 retail-faithful bindings byte-precise to
|
||
`docs/research/named-retail/retail-default.keymap.txt`;
|
||
`%LOCALAPPDATA%\acdream\keybinds.json` with merge-over-defaults
|
||
migration; acdream debug F-keys relocated to `Ctrl+F*`.
|
||
|
||
---
|
||
|
||
## #22 — [DONE 2026-04-26] Phase K.1b — cut handlers over to dispatcher
|
||
|
||
**Closed:** 2026-04-26
|
||
**Commit:** `256e962`
|
||
**Resolution:** Drop the legacy mouse-X-character-yaw path; fix
|
||
`WantCaptureMouse` gating; single input path via the multicast
|
||
`InputDispatcher`.
|
||
|
||
---
|
||
|
||
## #21 — [DONE 2026-04-26] Phase K.1a — input architecture skeleton
|
||
|
||
**Closed:** 2026-04-26
|
||
**Commit:** `84512d3`
|
||
**Resolution:** Action enum, multicast `InputDispatcher` with scope
|
||
stack, `KeyChord` / `Binding` / `KeyBindings`, Silk.NET adapters;
|
||
parallel to existing handlers (no behavior change).
|
||
|
||
---
|
||
|
||
## #20 — [DONE 2026-04-25] CombatChatTranslator — retail-faithful combat-text formatters
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `3d26c8e`
|
||
**Resolution:** Retail-faithful combat-text formatters into `ChatLog` ("You hit drudge for 50 slashing damage"). Subscribes to `CombatState`'s `DamageTaken` / `DamageDealtAccepted` / `EvadedIncoming` / `MissedOutgoing` / `AttackDone` / `KillLanded` events; templates ported verbatim from holtburger `panels/chat.rs:221-308`.
|
||
|
||
---
|
||
|
||
## #19 — [DONE 2026-04-25] TurbineChat codec (0xF7DE) + ChatChannelInfo
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `ca968fc`
|
||
**Resolution:** Full `0xF7DE` codec with three payload variants (`EventSendToRoom`, `RequestSendToRoomById`, `Response`), UTF-16LE strings with variable-length prefix, `SetTurbineChatChannels (0x0295)` parser, unified `ChatChannelInfo` (Legacy + Turbine variants), `TurbineChatState`. **Note: ACE doesn't run a TurbineChat server — codec is ready for retail-server-emulating setups.**
|
||
|
||
---
|
||
|
||
## #18 — [DONE 2026-04-25] Holtburger inbound chat parity + Windows-1252 codec
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `ff5ed9e`
|
||
**Resolution:** `EmoteText (0x01E0)` / `SoulEmote (0x01E2)` / `ServerMessage (0xF7E0)` / `PlayerKilled (0x019E)` parsers + `WeenieError` routing through `GameEventWiring`. Global codec switch from `Encoding.ASCII` to `Encoding.GetEncoding(1252)`; matches retail + holtburger; accented names round-trip correctly.
|
||
|
||
---
|
||
|
||
## #17 — [DONE 2026-04-25] ChatPanel input field + slash commands
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `f14296c`
|
||
**Resolution:** `ChatPanel` gains Enter-to-submit input field; `ChatInputParser` recognises `/say` `/t` `/tell` `/r` `/g` `/f` `/a` `/m` `/p` `/v` `/cv` `/lfg` `/trade` `/role` `/society` `/olthoi`; `ChatVM` tracks `LastIncomingTellSender` for `/r` reply.
|
||
|
||
---
|
||
|
||
## #16 — [DONE 2026-04-25] LiveCommandBus + WorldSession chat senders
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `8e6e5a0`
|
||
**Resolution:** Real `ICommandBus` impl + `WorldSession.SendTalk` / `SendTell` / `SendChannel` wrappers + `SendChatCmd` record + `ChannelResolver` legacy-id mapping per holtburger.
|
||
|
||
---
|
||
|
||
## #15 — [DONE 2026-04-25] DebugPanel migration
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `56037a4`
|
||
**Resolution:** Migrates the 473-LOC StbTrueTypeSharp `DebugOverlay` to an ImGui `DebugPanel` with collapsing-headers + checkbox diagnostics + combat-event tail. Deletes `DebugOverlay.cs`; `TextRenderer` + `BitmapFont` kept for future HUD-in-world (D.6 damage floaters, name plates).
|
||
|
||
---
|
||
|
||
## #14 — [DONE 2026-04-25] IPanelRenderer widget extension
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `b131514`
|
||
**Resolution:** Adds 14 widget signatures (`TextColored` / `Checkbox` / `Combo` / `InputTextSubmit` / `BeginTable` / etc.) to `IPanelRenderer` + `ImGuiPanelRenderer` impl. Foundation for I.2 DebugPanel and I.4 ChatPanel input.
|
||
|
||
---
|
||
|
||
## #7 — [DONE 2026-04-25] PlayerDescription parser stops after spells (enchantment block parsed)
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `feat(net): #7 PlayerDescriptionParser — enchantment block walker + StatMod flow`
|
||
**Resolution:** Extended `PlayerDescriptionParser` past the spell block to parse the Enchantment trailer per holtburger `events.rs:462-501`. Added `EnchantmentEntry` record with full wire payload (16 fields including the `StatMod` triad — type/key/val) + `EnchantmentBucket` (Multiplicative / Additive / Cooldown / Vitae per `EnchantmentMask`). `Parsed` now exposes `IReadOnlyList<EnchantmentEntry> Enchantments`. `GameEventWiring` routes each entry through the new `Spellbook.OnEnchantmentAdded(ActiveEnchantmentRecord)` overload with `StatModType` / `StatModKey` / `StatModValue` / `Bucket` populated. 2 new parser tests cover the enchantment block schema + Vitae singleton.
|
||
|
||
The remaining trailer sections (options / shortcuts / hotbars / inventory / equipped) are not yet parsed; filed as #13. Stopping after enchantments is intentional — it covers the highest-value section (issue #6 lights up) and avoids the heuristic `gameplay_options` walker that #13 needs.
|
||
|
||
---
|
||
|
||
## #12 — [DONE 2026-04-25] Capture full Enchantment wire payload (StatMod) on ActiveEnchantmentRecord
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `feat(net): #7 PlayerDescriptionParser — enchantment block walker + StatMod flow`
|
||
**Resolution:** Closed alongside #7 in the same commit. `ActiveEnchantmentRecord` extended with optional `StatModType`, `StatModKey`, `StatModValue`, `Bucket` fields. `Spellbook` got an `OnEnchantmentAdded(ActiveEnchantmentRecord)` overload that accepts the full record. `EnchantmentMath.GetMod` aggregator now consumes the StatMod data: multiplicative bucket (1) → multiplier ×= val; additive bucket (2) → additive += val; vitae bucket (8) → multiplier ×= val (applied last, matching retail `CEnchantmentRegistry::EnchantAttribute` semantics). 5 new EnchantmentMath StatMod-aware tests cover: multiplicative buffs aggregate, additive buffs sum, stat-key mismatch is filtered out, vitae applies multiplicatively, family-stacking picks the higher spell-id buff.
|
||
|
||
`ParseMagicUpdateEnchantment` (the live-update opcode 0x02C2) is **not** yet extended — it still uses the 4-field summary. That's a separate refactor; PlayerDescription's enchantment block is the load-bearing path for issue #6, and that's now flowing.
|
||
|
||
---
|
||
|
||
## #6 — [DONE 2026-04-25 architecture; data flowing as of #12] Vital max ignores enchantment buffs + vitae
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `feat(player): #6 fold enchantment buffs into vital max via EnchantmentMath`
|
||
**Resolution:** Ported `CEnchantmentRegistry::EnchantAttribute` (PDB `0x00594570`) as `EnchantmentMath.GetMod(IEnumerable<ActiveEnchantmentRecord>, SpellTable, statKey)` returning `(Multiplier, Additive)`. Family-stacking dedup via `SpellTable.Family` (only one buff per family bucket wins, by highest spell-id as a generation proxy). `Spellbook.GetVitalMod(statKey)` delegates. `LocalPlayerState.GetMaxApprox` reworked to apply `(unbuffed × mult) + add` with retail's min-vital clamp (`>= 5` if base ≥ 5 else `>= 1`, matches `CreatureVital::GetMaxValue` at PDB `0x0058F2DD`). Stat-key constants (`MaxHealth=1`, `MaxStamina=3`, `MaxMana=5`) verified against `docs/research/named-retail/acclient.h` line 37287-37301.
|
||
|
||
**Architecture in place; data still flat.** Until ISSUES.md #12 lands the wire-format extension that captures `StatMod (type/key/val)` on `ActiveEnchantmentRecord`, the per-enchantment modifier value isn't aggregated yet — `EnchantmentMath.GetMod` returns `Identity (1.0, 0.0)` for every stat key. Once #12 wires the data, the existing aggregator + formula light up automatically. Live `+Acdream` Stam/Mana percent will continue to read ~95% until #12 lands.
|
||
|
||
6 new EnchantmentMathTests cover: empty list returns Identity, no-table-entries returns Identity, stat-key constants match ACE enum, Identity is `(1, 0)`, family-stacking dedup, family=0 (no-bucket) treated as separate.
|
||
|
||
---
|
||
|
||
## #11 — [DONE 2026-04-25] Spell metadata loader (spells.csv → SpellTable)
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `feat(spells): #11 SpellTable — hydrate metadata from spells.csv at startup`
|
||
**Resolution:** Added `SpellMetadata` record + `SpellTable` CSV loader (hand-rolled RFC 4180-ish parser for the quoted Description column with embedded commas). Wired into `Spellbook` constructor as optional metadata source; `Spellbook.TryGetMetadata(spellId, out)` returns the static record when found. `GameWindow` loads `data/spells.csv` from bin output at construction (file copied via `<None Include>` in `AcDream.App.csproj` from `docs/research/data/spells.csv`). Falls back to `SpellTable.Empty` + console warning if the file is missing (e.g. tooling contexts). 10 new tests covering: empty table, header-only, simple row, quoted description with commas, blank lines skipped, bad spell-id rows skipped, lookup hit/miss, RFC 4180 escaped-quote parsing.
|
||
|
||
---
|
||
|
||
## #9 — [DONE 2026-04-25] Address-correction sweep on `acclient_function_map.md`
|
||
|
||
**Closed:** 2026-04-25
|
||
**Commit:** `docs(research): #9 sweep acclient_function_map.md against PDB symbols`
|
||
**Resolution:** Wrote `tools/pdb-extract/check_function_map.py` that cross-checks 63 hand-curated entries against `docs/research/named-retail/symbols.json`. Findings: **zero entries matched address-and-name exactly** (confirms ~0x800-0xC10 byte delta vs the binary that produced our Ghidra chunks — different build revision). 38 entries corrected by PDB name lookup; 25 entries either lack PDB symbol records (inlined / non-public) or had wrong class assignments (e.g. `0x5387C0` claimed as `CTransition::find_collisions` was actually `CPolygon::polygon_hits_sphere`). Updated `acclient_function_map.md` with corrected addresses, kept legacy addresses in a "Was" column for traceability, added a top-of-file sweep summary.
|
||
|
||
---
|
||
|
||
## #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.
|
||
-->
|