Commit graph

615 commits

Author SHA1 Message Date
Erik
e8562fc4e2 docs(D.5.3a): spec + plan — selected-object meter (Stream A)
Brainstormed design for the action bar's bottom strip: name + Health meter
on selection (mana deferred #140). Decisions: SelectionChanged via property
setter; send QueryHealth(0x01BF) on select. Grounded in retail
gmToolbarUI::HandleSelectionChanged (acclient_2013_pseudo_c.txt:198635) —
clear-then-populate, overlay state 0x1000000b, health gate
IsPlayer||pet||attackable. Render-bug fix is BuildMeter-only (single-image
back+fill meter; UiMeter already renders it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:19:14 +02:00
Erik
d400bc6105 docs: handoff — finish the action bar (selected-object meter + shortcut drag) + start the inventory/paperdoll window
Next D.2b-UI work after D.5.4. 3 streams (spell bar deferred): selected-object
meter, shortcut drag/add/reorder/remove, inventory+paperdoll window. Current-code
anchors + dependency graph + build order + brainstorm questions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:29:04 +02:00
Erik
85a2371e11 docs(D.5.4): roadmap shipped + divergence register (event model + deferred parent pre-queue)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:03:28 +02:00
Erik
e4dd37a3b8 docs(D.5.4): plan — StackSizeMax int? for downstream type consistency
Code-review follow-up from Task 2: align StackSizeMax with the other quantity
fields (int?, ACE PropertyInt convention) in Tasks 3/4/5; drop the (int) cast.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 15:51:29 +02:00
Erik
2fc253d9ff docs(D.5.4): implementation plan (12 tasks, TDD, green-per-task)
Rename -> field capture -> EntitySpawn plumb -> ClientObject/WeenieData ->
Ingest merge-upsert -> container index -> ObjectTableWiring (off GameWindow) ->
PD manifest -> delete EnrichItem -> retire _liveEntityInfoByGuid -> toolbar
guid-filter -> bookkeeping+live run. Sequenced so every task builds green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 15:19:15 +02:00
Erik
969e55350b docs(D.5.4): client object/item data model design (brainstorm spec)
Two guid-keyed tables (retail shape), CreateObject = canonical merge-upsert
for the data table (ACCWeenieObject-equivalent holding ALL server objects),
container membership index, retire _liveEntityInfoByGuid + EnrichItem. Settles
the handoff crux against the named decomp: retail is TWO tables, not one, so
acdream's WorldEntity + item-table split is already faithful — fix ingestion,
don't unify.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 15:06:00 +02:00
Erik
5b568d000a docs(D.5): sub-phase ledger + item-model cold-start prompt
Roadmap: refresh the D.5.2 entry to its final shipped state (per-pixel gradient
surface overload 0x004415b0; AP-43/AP-44 retired by visual verification; range
419c3ac..fb288ad). Add an explicit D.5 sub-phase ledger: D.5.4 client object/item
data model (foundation, NEXT) -> D.5.3 selected-object + spell shortcuts -> window
manager -> D.5.5+ core panels. Handoff doc gains a paste-ready new-session prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:41:08 +02:00
Erik
9e0d2568cc docs: handoff for the client object/item data model (next phase after D.5.2)
Frames the root cause of the live-Coldeve 4/6-missing-hotbar-icons (acdream's
enrich-existing-only item model drops CreateObjects without a pre-seeded stub) and
the retail ClientObjMaintSystem model to port. CRUX to settle first: unify the
WorldEntity + ItemRepository tracks, or keep separate with shared ingestion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:33:31 +02:00
Erik
fb288ad852 fix(D.5.2): effect tint = per-pixel tile copy (surface ReplaceColor overload)
Visual verification (Coldeve, Energy Crystal) showed acdream's Magical blue as a
flat tint vs retail's gradient. Root cause: RenderIcons calls the SURFACE overload
of SurfaceWindow::ReplaceColor (0x004415b0), which copies the textured effect tile
pixel-by-pixel into the icon's pure-white pixels — not the flat color->color overload
(0x00441530) I'd approximated with the tile's mean color. Port the surface overload
exactly (dst[x,y]=src[x,y] where dst==white); confirmed via clean Ghidra decompile +
named decomp. Retires AP-43 (mean-color approximation); IA-18 updated to the surface op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:21:33 +02:00
Erik
40c97a53ac fix(D.5.2): always run effect recolor (effects==0 -> black) to match retail
Visual verification caught it: a no-mana scroll's icon edges are BLACK in retail
but rendered WHITE in acdream. Cause = the effects!=0 gate (registered AP-44) that
skipped retail's effects==0 recolor. Retail's effect tile is non-null even for
effects==0 (the 0x21 SOLID-BLACK fallback 0x060011C5), so RenderIcons recolors
pure-white pixels to black on mundane items and to the effect hue on magical ones.
Remove the gate (always recolor); retire AP-44 (now faithful). TryGetEffectColor
made internal + a golden test pins effects==0 -> ~black.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 22:54:15 +02:00
Erik
73adc3768c docs(D.5.2): retire IA-16, add IA-18/AP-43..45, roadmap + memory
Divergence register:
- Retire IA-16 (item-icon composite PARTIAL — D.5.2 now complete).
- Add IA-18 (effect overlay = ReplaceColor tint SOURCE, faithful retail
  behavior; anti-regression guard — do NOT re-implement as a blit layer;
  cites IconData::RenderIcons 0x0058d180 + ReplaceColor 0x00441530).
- Add AP-43 (effect tint = mean-opaque color; exact retail byte
  decompiler-ambiguous, visual/cdb confirmation pending).
- Add AP-44 (effects==0 black-fallback recolor skipped; regression-risk
  avoidance, pending visual/cdb confirm).
- Add AP-45 (0x02CE sequence byte not honored, latest-wins).
Section header counts updated: IA 15→17, AP 41→44.

Roadmap: mark D.5.2 shipped (419c3ac..2f789da; appraise dropped as no-op;
effect recolor + live 0x02CE).

Tests: update ToolbarControllerTests iconIds lambda arity 4→5 to match the
D.5.2 GetIcon signature change (was caught by the build).

Memory: project_d2b_retail_ui.md updated with D.5.2 shipped entry
(via claude-memory symlink to ~/.claude/projects/.../memory/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:52:15 +02:00
Erik
52306d9268 docs(D.5.2): implementation plan (9 TDD tasks) + spec wiring fix
Bite-sized TDD plan for the stateful item-icon system. Corrects spec 5.8:
the live 0x02CE event binds in GameWindow (next to VitalUpdated), not
GameEventWiring (which only handles the 0xF7B0 GameEvent dispatcher).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:19:26 +02:00
Erik
419c3ac40c docs(D.5.2): stateful item-icon spec + RESOLVED research
Research basis (clean Ghidra decompile via MCP + live-dat probe + ACE oracle)
overturns two handoff hypotheses:
  - Appraise carries NO icon/UiEffects data (Icon/IconOverlay/IconUnderlay +
    PropertyInt.UiEffects all lack [AssessmentProperty]); every icon input is
    CreateObject-only. The "wire appraise -> enrichment" item is a no-op.
  - The effect overlay (enum 0x10000005) is a ReplaceColor tint SOURCE, not a
    blit layer (RenderIcons 0x0058d180 + ReplaceColor 0x00441530); effect tiles
    are 32x32 fully-opaque colored squares.

Design (user-approved): capture UiEffects (weenieFlags 0x80, currently discarded)
-> ItemInstance.Effects; faithful 2-stage IconComposer recolor (white pixels ->
effect hue); live PublicUpdatePropertyInt(0x02CE) wire-up so the icon updates as
state changes ("item with mana vs out of mana"). Drops the appraise no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:12:45 +02:00
Erik
6770381fc3 docs(D.5.2): stateful icon-system handoff + roadmap (D.5.1 shipped, D.5.2/D.5.3 next)
Extensive handoff for the next session to build the full stateful item-icon system (the 5-layer IconData::RenderIcons composite + effect layer 0x10000005 + overlay ReplaceColor tint + appraise-driven enrichment/re-composition). D.5.1 toolbar flipped to SHIPPED; D.5.2 (icon system) + D.5.3 (toolbar interactivity / selected-object display) registered as next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:24:03 +02:00
Erik
b1e45bee1c docs(D.5.1): divergence rows IA-16/IA-17 + ISSUES toolbar-interactivity entry
IA-16: partial icon composite (layers 1-4 only; effect glow + ReplaceColor tint
deferred to D.5.2). IA-17: per-window UiNineSlicePanel chrome vs window-manager-
owned bevel (retail toolbar has no baked frame in LayoutDesc 0x21000016).

#140: toolbar interactivity (selected-object meters 0x100001A1/A2 + stack slider
0x100001A4 + name line) deferred to roadmap D.5.3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:21:19 +02:00
Erik
30b28c248a docs(D.5.1): register toolbar phase-1 in the roadmap 2026-06-16 21:40:46 +02:00
Erik
44fabd350e docs(D.5.1): toolbar phase-1 implementation plan (+ spec wiring-delta note)
12-task TDD plan: register D.5.1 -> CreateObject IconId capture -> ItemRepository.EnrichItem -> spawn-event icon wiring -> persist shortcuts -> IconComposer (CPU composite) -> UiItemSlot -> UiItemList + factory branch -> ToolbarController -> GameWindow mount -> visual gate -> bookkeeping. Concrete call sites pinned (WorldSession.cs:701 EntitySpawned, GameEventWiring.WireAll, GameWindow Items@598, BuildUse 0x0036). Synced the spec's CreateObject section with the wider-than-expected wiring found during planning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 21:27:49 +02:00
Erik
0b5e849325 docs(D.5.1): toolbar phase-1 design spec
First D.5 sub-phase: ship gmToolbarUI as the first data-driven game panel (18 slots from LayoutDesc 0x21000016, populated from the persisted PlayerDescription shortcuts, real composited icons, click-to-use). Minimal scope; faithful CPU icon pre-composite (IconData::RenderIcons port). Five bounded units: UiItemSlot, UiItemList, IconComposer, CreateObject IconId extension, ToolbarController. Roadmap registration of D.5.1 is plan step 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 21:05:04 +02:00
Erik
a5c5126e8d docs(D.5): action bar / inventory / paperdoll research drop
Five report-only deep-dives + synthesis for the next D.2b UI panels, built on the shipped widget toolkit. Confirms LayoutDesc ids (toolbar 0x21000016, inventory 0x21000023, backpack 0x21000022, paperdoll 0x21000024, 3ditems 0x21000021), the shared item-slot/item-list spine (UIElement_UIItem 0x10000032 / UIElement_ItemList 0x10000031), the 5-layer icon composite (IconData::RenderIcons @407524), the cross-panel wire catalog with acdream parse-status, and the dependency-ordered build plan.

Produced via a multi-agent research workflow; the spine agent died on a transient API error and was re-run as a focused follow-up with its decomp anchors verified against source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 21:04:57 +02:00
Erik
78c91875b8 docs: file #139 — D.2b retail UI polish (chat text colors + buttons)
Deferred cosmetic polish after the widget-generalization landing: tune the
per-ChatKind transcript text colors against retail, and add pressed/hover state
feedback to the chat buttons (UiButton draws only its default state today; the
dat carries Normal/Pressed/Highlight). Not a regression — the generalized chat
matches the prior hand-made build (user-confirmed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 19:01:50 +02:00
Erik
9e4faae9d2 docs(D.2b): roadmap — widget generalization (Plan 2) shipped
Record the D.2b widget-generalization landing: generic Type-registered widgets
built by DatWidgetFactory, thin find-by-id controllers, the ConsumesDatChildren
leaf rule, Type-3-not-registered decision, and the centered-UiText vitals numbers.
Both visual gates user-confirmed; 404 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:55:06 +02:00
Erik
89626cd400 feat(D.2b): vitals numbers as UiText (widget-generalization Task 8)
The vitals cur/max numbers now render through the generic UiText widget — retail
gmVitalsUI uses UIElement_Text for them, not a meter-internal label. VitalsController
attaches a centered, non-interactive UiText child to each meter and stops the meter
drawing its own label (UiMeter.Label -> null). New UiText.Centered draws the first line
centered H+V with the SAME formula UiMeter's overlay used, so the numbers are
pixel-identical — user-confirmed in the live client.

This completes the D.2b widget-generalization pass: every chat + vitals widget is now
built generically and registered to its retail Type (Button/Field*/Menu/Meter/Scrollbar/
Text), with thin find-by-id controllers. (*Field is controller-placed; Type 3 stays
UiDatElement for chrome.)

Divergence register: AP-37 vitals-numbers-via-UiMeter.Label clause retired. Full suite:
404 passed, 2 skipped, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:52:42 +02:00
Erik
83076cdbb6 docs(D.2b): spec correction — input is Variant B, Type 3 not registered
Record the two execution-time corrections to the design's registration
assumptions: the editable input resolves to Type 12 (Variant B, controller-placed
UiField), and Type 3 is NOT factory-registered (acdream's Type-3 elements are
chrome/containers, kept on the UiDatElement fallback).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:54:52 +02:00
Erik
cb082b59e4 feat(D.2b): UiText (Type 12) -- generic text + Type-12 flip; transcript factory-built (widget-generalization Task 5)
Rename UiChatView -> UiText (the retail UIElement_Text class,
RegisterElementClass(0xc) @ acclient_2013_pseudo_c.txt:115655).

Factory changes (DatWidgetFactory.cs):
- Remove the Type-12 skip (was: no-media -> null, with-media -> UiDatElement).
- Add Type 12 -> BuildText() -> UiText in the switch.
- BuildText extracts the element's Direct/Normal sprite as BackgroundSprite
  so any dat-media the element carried keeps rendering under the text.

UiText changes (renamed from UiChatView.cs):
- BackgroundColor default: (0,0,0,0.35) -> (0,0,0,0) (transparent).
  An unbound UiText draws nothing; the controller opts in to the translucent bg.
- New BackgroundSprite + SpriteResolve: optional dat state-sprite background
  drawn UNDER DrawFill+text (faithful UIElement_Text media support).

ChatWindowController.cs (Task 5 Step 8):
- Transcript property: UiChatView -> UiText.
- Bind() now uses layout.FindElement(TranscriptId) as UiText (factory-built)
  instead of manually constructing + AddChild-ing a new UiChatView.
- Sets BackgroundColor = (0,0,0,0.35) on the found widget (retail translucent bg).
- Removes the tInfo null-check from the early guard (transcript is factory-built;
  iInfo lookup kept for the input widget which is still manually constructed).
- BuildLines: UiChatView.Line -> UiText.Line throughout.

Vitals frozen: the Type-12 vitals number elements are meter children and are
never recursed by BuildWidget (the `if (w is not UiMeter)` gate), so they are
not built as widgets and keep rendering via UiMeter.Label. Vitals fixture
vitals_2100006C.json unchanged; LayoutConformanceTests + VitalsBindingTests green.

Tests:
- UiChatViewTests.cs -> UiTextTests.cs (class: UiTextTests, all UiChatView.* -> UiText.*)
- UiChatViewDatFontTests.cs -> UiTextDatFontTests.cs (same)
- DatWidgetFactoryTests: delete Type12_StylePrototype_ReturnsNull +
  DatWidgetFactory_Type12WithMedia_Renders; add Type12_Text_MakesUiText +
  DatWidgetFactory_Type12_AlwaysMakesUiText.
- LayoutImporterTests: BuildFromInfos_Type12Child_IsSkipped_Type3Present updated
  to assert IsType<UiText> (element is now in tree, transparent, not skipped).

Divergence register: AP-37 amended -- removed the "standalone Type-0 text
elements skipped / dat-text widget is Plan 2" clause (now shipped as UiText);
kept the meter-collapse clause and the vitals-numbers-via-UiMeter.Label clause.
AP-38/AP-39/AD-28 file references updated UiChatView.cs -> UiText.cs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:39:02 +02:00
Erik
955f7a69a8 feat(D.2b): UiMenu (Type 6) — generic dropdown; channel knowledge moves to controller (widget-generalization Task 4)
UiChannelMenu → UiMenu: removed ChatChannelKind, the 14-item array, the
button-text map, and the availability default. Generic surface: MenuItem
(label + object? Payload), Selected (object?), OnSelect, EnabledProvider,
ButtonLabelProvider, RowsPerColumn/RowHeight/ColumnWidth (all settable).
All draw/event mechanics unchanged — same popup geometry, same click
coordinates, same 8-piece bevel, same 3-slice button face.

ChatWindowController gains ChannelItems[], ChannelButtonLabel(), and
ChannelAvailable() (verbatim from old widget), and populates the
factory-built Type-6 UiMenu via find-by-id rather than constructing a
replacement widget. The Menu property type is now UiMenu. OnChannelChanged
wrap replaced with the generic OnSelect wrap for the ReflowInputRow hook.

DatWidgetFactory registers Type 6 → new UiMenu().

Tests: UiChannelMenuTests → UiMenuTests (10 tests, all green); factory
Type6 test added; ChatWindowControllerTests updated to use OnSelect.
Divergence register: AP-42 added (flat item model vs retail nested-submenu
MakePopup @0x46d310 — latent, unreachable through the chat menu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:18:27 +02:00
Erik
3593d6623d feat(D.2b): UiScrollbar (Type 11) — promote the generic chat scrollbar (widget-generalization Task 2)
- git mv UiChatScrollbar.cs → UiScrollbar.cs; rename class + update doc summary to
  "Generic scrollbar. Ports retail UIElement_Scrollbar (RegisterElementClass(0xb) @
  acclient_2013_pseudo_c.txt:124137); thumb size = trackLen * ThumbRatio (min 8px); step ±1 line."
- git mv UiChatScrollbarTests.cs → UiScrollbarTests.cs; rename test class + replace
  every UiChatScrollbar reference with UiScrollbar (bodies unchanged).
- DatWidgetFactory: register Type 11 → new UiScrollbar() before the _ fallback case.
- ChatWindowController: change Scrollbar property type to UiScrollbar; replace the old
  "construct-remove-add" block with a "find factory-built UiScrollbar and bind in place"
  block (no RemoveChild/AddChild); keep `var track` assignment in scope so the Max/Min
  block's track.Left/track.Width reads still compile against UiElement?.
- AP-41 divergence register: update file:line to UiScrollbar.cs:35; narrow wording to
  "fallback only — single-tile drawn only when cap ids are unset; the chat controller
  passes all three cap ids so the 3-slice path is the active code path."
- Update inline UiChatScrollbar doc-comment references in UiScrollable.cs + UiChatView.cs.
- Full suite: 399 passed, 2 skipped (dat/tower fixture skips), 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:02:49 +02:00
Erik
34e79096f3 docs(D.2b): widget-generalization implementation plan
8-task TDD plan: chat golden fixture + resolved-Type conformance (Task 1,
empirically resolves the input's Type), then one-widget-per-commit migration —
UiScrollbar(11), UiButton(1), UiMenu(6), UiText(12)+the Type-12 flip,
UiField(3) — then thin the controller (Task 7, visual gate) and the gated
vitals UiText rewire (Task 8). Each task: failing test, register in the
factory switch, controller find-by-id binding, build+test green, commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:47:32 +02:00
Erik
56f5bc7aa1 docs(D.2b): add strategic-purpose section to widget-generalization design
Capture the 'why beyond chat' the user articulated: chat is the proving ground;
the real payoff is inventory/spell-bar/vendor/character-sheet/trade becoming
data-driven assembly + thin controller. Notes what carries forward (the generic
widget toolkit + the find-by-id controller pattern) vs what those windows still
need (ListBox/Panel + Field drag-drop, the window-manager half of Plan 2, and
per-domain item/container data).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:33:14 +02:00
Erik
b7f7e2b4ef docs(D.2b): widget-generalization design (Plan 2 widget piece)
Design for refactoring the hand-named chat widgets + Send/MaxMin click-wiring
into generic, Type-registered widgets built by DatWidgetFactory, collapsing
ChatWindowController (and, gated-last, VitalsController) to a thin retail
gm*UI::PostInit-style find-by-id binder.

Key finding that reframes the pass: the importer's base-chain Type resolution
is already retail-faithful, and Type 12 is UIElement_Text (a real behavioral
class), not a style prototype to skip — verified against
acclient_2013_pseudo_c.txt:115655. The generalization is therefore a
registration task (register Types 1/3/6/11/12 -> generic widgets, delete the
Type-12 skip), not a new mechanism.

Approved scope: full registry (bounded to the Types chat+vitals use; rest stays
UiDatElement fallback), chat-first, vitals rewire as the final separately-gated
step. 7-step one-widget-per-commit migration; new chat_21000006.json golden
fixture; vitals fixture stays frozen through steps 1-6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:26:32 +02:00
Erik
0ec36f6197 fix(D.2b): chat input resolves the live command bus lazily (was bound to null) + register thumb-3-slice row
The live session + its LiveCommandBus are created after the retail-UI block in
OnLoad, so binding the bus by value captured NullCommandBus and silently dropped
outbound chat. Pass a Func<ICommandBus> resolved at submit time (mirrors how the
ImGui ChatPanel re-reads the bus each frame).

AP-41: scrollbar thumb drawn as single stretched tile (0x06004C63) instead of
retail's 3-slice top-cap/middle/bottom-cap — acknowledged in UiChatScrollbar.cs:37,
registered per the divergence-register rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:24:44 +02:00
Erik
12ab9663d2 feat(D.2b): cut GameWindow over to the data-driven chat window
Replace the hand-authored chat block (UiNineSlicePanel + inline UiChatView
+ local BuildRetailChatLines/RetailChatColor statics) with
ChatWindowController.Bind(LayoutDesc 0x21000006) — the same LayoutImporter
path as the vitals window.  The controller places UiChatView (transcript) +
UiChatInput (text entry, on-submit) + UiChatScrollbar + UiChannelMenu inside
the dat-authored chrome.  The dead local statics are deleted.

Wired to _commandBus (same LiveCommandBus as the ImGui ChatPanel) so
type+Enter dispatches SendChatCmd server-ward.  Transcript keyboard set from
_uiHost.Keyboard (set by WireKeyboard above the chat block) for Ctrl+C/Ctrl+A.

Divergence register: added AD-28 (two-widget split vs UIElement_Text),
AP-38 (no in-element word-wrap), AP-39 (per-line colour vs per-glyph runs),
AP-40 (no opacity fade / shared vitalsDatFont), TS-30 (tab buttons no-op),
TS-31 (no squelch); updated IA-15 to cover both vitals + chat importer paths.

Build: 0 errors/warnings.  Tests: 392 passed, 1 skipped (expected).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:15:04 +02:00
Erik
3d25e8760f @
docs(D.2b): chat-window re-drive implementation plan (8 tasks A-H)

TDD task breakdown for the data-driven chat window: ChatCommandRouter extraction
(A), UiChatView dat-font (B), UiScrollable + wire-in (C/C2), UiChatScrollbar (D),
UiChatInput (E), UiChannelMenu (F), ChatWindowController bind/route (G), GameWindow
cutover + divergence rows (H). Each ported widget cites its retail class::method.

Plan: docs/superpowers/plans/2026-06-15-chat-window-redrive.md
Spec: docs/superpowers/specs/2026-06-15-chat-window-redrive-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-06-15 22:04:35 +02:00
Erik
26cb34f126 @
docs(D.2b): chat-window re-drive design spec + list-ui-layouts research tool

Plan-2 chat piece of the LayoutDesc importer. Identifies the chat window as
LayoutDesc 0x21000006 (gmMainChatUI, element class 0x10000041) and grounds a
faithful, data-driven re-drive in the named retail decomp (ChatInterface +
gmMainChatUI + UIElement_Text/_Scrollable/_Scrollbar/_Menu) plus a user-provided
retail screenshot.

Design (full-faithful scope, user-approved):
- transcript = UIElement_Text 0x10000011 (dat font, bottom-pinned, 10k behead cap,
  pixel scroll, 1 line/wheel-notch)
- scrollbar = right-side track 0x10000012 + thumb 0x1000048c + up/down
- input = editable UIElement_Text 0x10000016 (caret, 100-entry history, Enter/Send)
- channel menu = UIElement_Menu 0x10000014 ("Chat" selector -> active channel)
- shared ChatCommandRouter extracted from ChatPanel
- screenshot correction: the four 0x10000522-525 left-edge elements are the
  numbered CHAT TABS (1-4), not scroll buttons (a research-agent inference the
  retail screenshot refutes)
- deferred (need non-UI plumbing, each gets a divergence row): tab switching/
  filtering, squelch, clickable name-tags, in-element word-wrap, styled runs,
  font config, opacity transition

Tooling: AcDream.Cli `list-ui-layouts <datdir> [0xRootType]` — read-only index of
every UI LayoutDesc by root element class + size + element-Type histogram; how the
chat layout was located (root type 0x10000041). Reusable for future panel re-drives.

Spec: docs/superpowers/specs/2026-06-15-chat-window-redrive-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-06-15 19:38:27 +02:00
Erik
50758d4795 docs(D.2b): chat-window re-drive session handoff (Plan 2 chat piece)
Captures: current hand-authored chat window (UiNineSlicePanel + UiChatView,
read-only, debug font), the importer toolkit to reuse, the retail gmMainChatUI
oracles, the open design questions (scope / behavioral widgets / dat font), and
the first research step (find the chat LayoutDesc id). Resume via brainstorming.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:07:05 +02:00
Erik
0474feb6ca docs(D.2b): correct roadmap/plan — vitals window IS resizable (resize shipped 8aa643f)
The earlier 'not resizable / fixed-size' note was wrong (inverted edge-flag
reading). Resize shipped: dat edge-anchors reflow per UIElement::UpdateForParentSizeChange.
Noted the two number-render fixes (submission-order + glyph pixel-snap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:35:29 +02:00
Erik
8aa643f3e0 fix(D.2b): correct edge-anchor mapping (RightEdge==1=stretch) + enable vitals horizontal resize
ToAnchors was inverted vs retail UIElement::UpdateForParentSizeChange @0x00462640:
stretch is RightEdge==1 (not ==2/==4), LeftEdge==2 = track-right. Verified against
all 19 vitals fixture pieces. Enables Resizable/ResizeX on the importer vitals root
(the prior 'dat is fixed-size' conclusion was wrong). At-rest render unchanged
(anchors only fire on resize). Added a 160->200 resize conformance test.
Also fixed DatWidgetFactoryTests.RectAndAnchors_SetFromElementInfo which encoded
the old inverted model (Right=2 expecting Right anchor; corrected to Right=1).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:05:04 +02:00
Erik
825536a2bd docs(D.2b): re-retire TS-30 in register (restore branch state lost in --theirs merge)
The earlier 'git checkout --theirs' resolution of the register merge conflict
took main's whole file, which reverted two branch-only changes: IA-15 (re-added
in c100484) and the TS-30 retirement. TS-30 (flat-rect UI panels) was retired by
D.2b Spec 1 when UiNineSlicePanel shipped the 8-piece chrome and is doubly moot
now that vitals draw the dat chrome via the importer. Removed the TS-30 row +
its phase-gated reference; TS count 30->29. All section counts now match actual
rows (IA 15 / AD 27 / AP 37 / TS 29 / UN 5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:41:41 +02:00
Erik
c1004847a2 docs(D.2b): record vitals default-flip shipped (importer is now the default vitals)
Roadmap: update D.2b LayoutDesc importer entry to record that the default
flip shipped 2026-06-15 (bf77a23) — importer is the default at
ACDREAM_RETAIL_UI=1; vitals.xml + ACDREAM_RETAIL_UI_IMPORTER flag retired;
window movable, resize deferred to Plan 2 (WindowManager).

Plan: update "After Plan 1" to mark the flip DONE, clean up the Plan 2
description now that vitals.xml is gone.

Register:
- AP-37 "Why" cell: replace "Gated opt-in (ACDREAM_RETAIL_UI_IMPORTER)"
  with "Now the default vitals path (the hand-authored markup vitals was
  retired)" — the flag is gone.
- IA-15: add row (was missing from this branch) — D.2b retail UI design
  stance, updated to note that the vitals window is now rendered by the
  LayoutDesc importer (dat chrome elements), not UiNineSlicePanel;
  UiNineSlicePanel/RetailChromeSprites now back only chat window + plugin
  panels. IA count header 14 → 15.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:36:47 +02:00
Erik
5ac9d8c19c merge: bring main into claude/hopeful-maxwell-214a12 (LayoutDesc importer branch)
main was 65 commits ahead of this branch's fork point. Only conflict was the
divergence register: both sides appended an 'AP-32' row. Resolved by keeping
main's AP-32..AP-36 (cell-shell lift, look-in cells, alpha deferral, dungeon
streaming, point lights) and renumbering the importer's row to AP-37; AP header
count -> 37. GameWindow.cs auto-merged cleanly. Verified: AcDream.App builds
0/0; AcDream.App.Tests 354 passed / 1 skipped / 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:19:15 +02:00
Erik
07cf120939 docs(D.2b): mark LayoutDesc importer Plan 1 shipped; defer default-flip to Plan 2 (drag/resize)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 15:03:31 +02:00
Erik
4dcc90cb51 docs(D.2b): register AP-32 + IA-15 amend for importer; doc/test review fixes (N1/N4)
Process/quality items from the LayoutDesc-importer final review — no runtime
behavior change.

I1a — amend IA-15: the 8-piece chrome edge/corner→position mapping is no longer
a guess.  The LayoutImporter (ACDREAM_RETAIL_UI_IMPORTER) reads real LayoutDesc
dat data and resolves positions + sprite ids directly; locked by the conformance
fixture vitals_2100006C.json.  Residual risk trimmed to anchor resolution at
non-800×600 + controls.ini cascade.  Pointers added to LayoutImporter.cs and the
format-doc.

I1b — add AP-32: the importer collapses the dat's nested meter structure
(Type-7 → two Type-3 containers → three image-slice grandchildren each) into
UiMeter's programmatic 3-slice fields instead of building those nodes generically
and porting UIElement_Meter::DrawChildren.  Standalone Type-0 text elements are
also skipped (Plan 2).  Retail oracles: UIElement_Meter::DrawChildren @0x46fbd0,
UIElement_Text::DrawSelf @0x467aa0.

I1c — AP section header 31 → 32.

N1 — ElementReader.cs: comment at the Type-merge line explaining that a derived
Type 0 (text element) inherits the base's Type 12 (style prototype), which
DatWidgetFactory skips; safe for Plan 1 because vitals numbers render via
UiMeter.Label.  Format-doc §10: correct the "render as UiDatElement" sentence to
"skipped entirely" (Type-0 → inherits Type-12 via Merge → factory returns null).

N4 — new conformance test VitalsTree_TextLabel_InheritsFontDidFromBaseLayout:
walks the raw ElementInfo tree from the fixture and asserts at least one element
carries FontDid==0x40000000, proving Resolve()'s inheritance merge fired against
real dat data.  FixtureLoader gains LoadVitalsInfos() that returns the raw tree
without calling Build.

Tests: 36 pass (was 35), 0 errors, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 14:55:01 +02:00
Erik
67819f35a4 docs(D.2b): LayoutDesc format enumeration (importer groundwork)
Resolves all 6 open unknowns for Tasks 2–6 of the LayoutDesc importer plan:

1. Edge-anchor flags: 1=near-pin, 2=far-pin, 3=float-center, 4=stretch.
   The plan's assumption of 4="pinned to that side" is corrected — 1 is
   the near-pin, 4 is stretch (both sides). Revised ToAnchors signature given.

2. ElementDesc members: all are public FIELDS (not properties). X/Y/Width/
   Height/LeftEdge/etc. are uint. Type is uint (not enum). States is
   Dictionary<UIStateId, StateDesc>. Children is Dictionary<uint, ElementDesc>.

3. StateDesc shape: Properties is Dictionary<uint, BaseProperty> with concrete
   subclasses (ArrayBaseProperty, DataIdBaseProperty, IntegerBaseProperty, etc.).
   Font DID (0x1A) is ArrayBaseProperty[ DataIdBaseProperty{Value=0x40000000} ].
   Font color (0x1B) is ArrayBaseProperty[ ColorBaseProperty ]. Fill (0x69) is
   NOT in the dat — pushed at runtime by gmVitalsUI::Update.

4. DrawModeType enum: Undefined=0, Normal=1, Overlay=2, Alphablend=3.
   No "Stretch" value exists. Vitals uses Normal(1) and Alphablend(3) only.

5. Type values confirmed from RegisterElementClass: 3=Field/container,
   7=Meter→UiMeter, 9=Resizebar, 0xC=Text, 2=Dragbar, 12=style prototype (skip).

6. Inheritance chain: vitals text labels (Type=0) inherit from base element
   0x10000376 in layout 0x2100003F (Type=12), which carries font DID 0x40000000.
   The full per-vital sprite id tables for 0x2100006C are confirmed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 13:05:53 +02:00
Erik
a7875cde22 docs(D.2b): LayoutDesc importer implementation plan (Plan 1 — vitals conformance) 2026-06-15 12:46:55 +02:00
Erik
64146bfc2a docs(D.2b): LayoutDesc importer design spec (data-driven retail windows) 2026-06-15 12:38:34 +02:00
Erik
d2b8a51426 docs: wrap-up — file #137 (dungeon collision) + #138 (teleport-out world loading); close #135/#136
- #137: dungeon collision wrong at doors / wall openings (EnvCell collision; needs repro).
- #138: teleport OUT of a dungeon loads the outdoor world incompletely (missing trees/
  scenery, broken collision) + a position desync (avatar moves but player position doesn't)
  — hypothesised as the dungeon-streaming collapse→EXPAND gap (same machinery as #135).
- #135 marked DONE (user-verified FPS-steady dungeon login); #136 closed (editor-marker hide).
- CLAUDE.md current-state refreshed: #135/#136 shipped, A7 lighting + #137/#138 remaining.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 21:08:40 +02:00
Erik
fd0ecfcf2e docs: close #136 — red cone was an editor-only placement marker (fixed 6f81e2c)
Rewrite the #136 entry with the definitive root cause (editor-only dat placement
marker hidden by retail's distance degrade, inherited as visible from the WB-derived
render path) replacing the earlier refuted texture-pipeline hypothesis; mark FIXED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 19:05:03 +02:00
Erik
b4ed8e7908 docs: file #136 — red-cone dungeon decoration renders red (frozen-phase render divergence)
Investigated the user-reported divergence (a solid-red cone in the 0x0007 dungeon
that retail doesn't draw). Narrowed by elimination:
- geometry, not VFX (survives particles-off)
- object 0x70007055 / Setup 0x020019F0, physState=0x1C — NOT NoDraw/Hidden
- its distinguishing texture 0x06006D65 (DXT1 256x128) DECODES tan/opaque offline,
  identical to a neighbour decoration (0x020019EE / tex 0x06006D63) that renders fine
- not a per-instance tint (hook dropped)
=> the red is introduced at runtime in the WB bindless texture-array upload/sampling
path (a #105-class "samples undefined until flushed" / layer-handle misassignment),
possibly lighting. Both WB-render-migration and sky/lighting are FROZEN phases, so the
fix awaits explicit sign-off. Full diagnosis + reusable diagnostic approach in the issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 18:11:15 +02:00
Erik
2f4520ee12 docs(D.2b): mark D.2b + D.4 shipped (Spec 1 — markup engine + retail vitals)
Roadmap: D.2b (custom retail-look backend) and D.4 (dat sprites + 9-slice +
DrawSprite) both shipped this session via the Spec-1 work — the UiHost-based
markup engine (MarkupDocument + ControlsIni + IUiRegistry) rendering a
markup-driven retail Vitals panel (8-piece dat chrome + red/gold/blue bars).
Records the direct-RenderSurface decode finding + the confirmed chrome sprite
ids. Remaining D.2b polish (gradient bar sprite, AcFont/D.3, input integration,
LayoutDesc importer, D.5 panels) noted inline.

Full suite green (2413 passed / 0 failed / 3 pre-existing skips).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:50:42 +02:00
Erik
2c923755c4 fix(G.3): place the player on the cell floor for an indoor dungeon login (#135 follow-up)
Two regressions from the pre-collapse (712f17f), found by live gate + a runtime
probe:

1) Login-into-dungeon stopped loading the dungeon. The login-hold streaming
   observer fell through to the OFFLINE fly-camera branch once
   _lastLivePlayerLandblockId was filtered to the player guid (a dungeon-local
   NPC used to keep it pinned). A camera-derived observer far from the
   pre-collapsed dungeon tripped ExitDungeonExpand and unloaded it. Fix: a LIVE
   in-world session never uses the fly camera for the observer — it follows the
   player's server landblock, falling back to the recentered spawn center
   (_liveCenterX/Y). The fly camera is the OFFLINE observer only.

2) Even with the dungeon resident, auto-entry hung: the #106 "ground ready" gate
   required SampleTerrainZ under the spawn, but a dungeon's negative-offset cells
   place the spawn's WORLD position in a NEIGHBOUR terrain landblock the #135
   collapse deliberately doesn't load (probe: cellReady=True, terrReady=False
   forever). The terrain gate is wrong for an indoor spawn — the player lands on
   the EnvCell FLOOR. Fix: gate an indoor (hydratable) spawn/teleport on
   IsSpawnCellReady, not the terrain heightmap; outdoor (and unhydratable→demote)
   spawns still hold on terrain. Applied to both isSpawnGroundReady (login auto-
   entry) and TeleportArrivalReadiness (teleport). This is the faithful equivalent
   of retail's synchronous cell load + place-on-floor; the pre-#135 terrain hold
   only passed because the 25x25 window streamed the neighbour terrain.

Verified live: login into 0x0007 → auto-entered player mode, snapped to
0x00070145, dungeon renders, FPS steady. Register AD-2 amended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:13:12 +02:00
Erik
b18403da02 feat(D.2b): wire UiHost + live retail Vitals panel (render-only); retire TS-30
Wires the dormant AcDream.App/UI retained-mode tree into GameWindow under
ACDREAM_RETAIL_UI=1: an 8-piece dat-sprite UiNineSlicePanel framing three
UiMeter vital bars bound to the existing VitalsVM. Render-only (UiHost input not
yet bridged to the InputDispatcher — next sub-phase). Coexists with the ImGui
devtools path; no regression there.

Visually verified against a live retail client: the bars match retail's vitals
structure (three stacked horizontal bars, current/max numbers centered) — so the
earlier "orbs" assumption was wrong (retail vitals ARE bars), and stamina is GOLD
not cyan (the #10F0F0 research note was wrong). UiMeter gains a centered numeric
Label (stub debug font for now). Spec §8 + the markup example corrected to match.

Bookkeeping: retired divergence row TS-30 (flat-rect panels -> real dat chrome)
and added IA-15 (our UiHost/markup engine vs keystone.dll's LayoutDesc tree).

Remaining polish (filed, §15): glassy gradient bar fill sprite + the retail dat
font for the numbers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:56:57 +02:00