Create AGENTS.md as a thin bridge to CLAUDE.md, fix isolated dat-layout previews by normalizing panel roots to the studio canvas, add a --mockup mode that mounts production-backed UI windows into one UiHost, and thread the per-element dat-font resolver through GameWindow's live imports.
Verified with dotnet test and headless studio screenshots for inventory and the mockup desktop.
Canvas clicks now reach the panel UiHost so buttons, tabs, and slots
respond to user interaction. Previously only UiRoot.Pick (inspector
selection) received click events; the panel itself was inert.
Key changes:
- StudioInspector.DrawCanvas now returns a CanvasInputEvent struct
(was nullable click tuple) — carries isHovered, move position,
leftDown/Up, and scroll delta, all in panel-local pixels.
- Coordinate mapping: panel_local = mouse_screen - GetItemRectMin()
(1:1, no scale factor). V-flip (uv0.Y=1, uv1.Y=0) makes screen
top = panel Y=0, so NO extra Y inversion. Documented in comments.
- StudioWindow.OnLoad: removed WireMouse — raw Silk window coords are
offset by the canvas sub-window position and land in the wrong place.
WireKeyboard kept (keyboard input needs no spatial remapping).
- StudioWindow.OnRender: forwards OnMouseMove always (hover states),
plus OnMouseDown/Up/OnScroll in Interact mode. Console.WriteLine
on each forwarded left-click for live verification.
- Interact/Inspect toggle: checkbox in the Studio toolbar (default
Interact). Inspect mode restores old click-to-select-element behavior
while still forwarding OnMouseMove for hover states.
- CanvasCoordMappingTests: 7 pure-math unit tests covering the
origin, interior points, corner, chrome OOB, and the no-extra-flip
invariant (no GL required).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Task 4b: a second load path for the UI Studio that reads the committed
retail UI layout dump (docs/research/2026-06-25-retail-ui-layout-dump.json)
and renders any of the 26 retail windows as a static sprite hierarchy.
New files:
- src/AcDream.App/Studio/UiDumpModel.cs — POCOs + System.Text.Json parse of
the dump (UiDump, DumpPanel, DumpNode, DumpRect, DumpStateSet, DumpImage,
DumpState + UiDumpModel static helpers: Parse, ListSlugs, PickImageId).
- src/AcDream.App/Studio/DumpLayout.cs — DumpLayout.Load(path, slug, resolve,
out err): parses the dump, finds the panel by slug, builds a UiElement tree.
Internal DumpSpriteElement draws its sprite via DrawSprite (not reusing
UiDatElement — avoids the ElementInfo/StateMedia dat-import dependency for
this static mockup). DumpGroupElement is a transparent container for Group
nodes. Rect basis is ABSOLUTE in the dump (verified: inventory root at
absolute x=500 and its children also start at x≈500 — child offset from
parent is 0–50px, not 500px); DumpLayout subtracts parent rect to produce
parent-relative Left/Top for each child.
- tests/AcDream.App.Tests/Studio/DumpLayoutTests.cs — 5 tests covering:
inventory load with 0x100001D5 check + >= 40 nodes, unknown slug → null+err,
root at origin, children are parent-relative, all 26 slugs smoke-load.
Modified files:
- StudioOptions: adds DumpSlug + DumpFile fields; --dump <slug> and
--dump-file <path> args; ResolveDumpFile() walks up to the solution root
to find the default dump JSON (mirrors ConformanceDats.SolutionRoot()).
--dump suppresses the default vitals layout so the two modes are exclusive.
- StudioWindow.OnLoad: when DumpSlug is set, loads via DumpLayout (no
FixtureProvider, no controllers — static structure only); else falls
through to the existing LayoutSource + FixtureProvider path.
Results: DumpLayoutTests 5/5 passed; full AcDream.App.Tests 609 passed, 2
skipped (same as before); dotnet build green, 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FixtureProvider.Populate for 0x21000023 was binding only
InventoryController and omitting PaperdollController, so the
~21 equip slots rendered empty even though sample equipped items
existed in the table. Also, the three per-list empty-slot
sprites were not being resolved from the dat (contentsEmpty /
sideBagEmpty / mainPackEmpty all defaulted to 0), so the slot cells
had no background art.
Fixes:
- FixtureProvider.Populate gains a DatCollection dats param
(mirrors the GameWindow.OnLoad lookup at line 2233-2235).
The 0x21000023 case now resolves all three empty sprites via
ItemListCellTemplate.ResolveEmptySprite and passes them to
InventoryController.Bind.
- PaperdollController.Bind is now called after InventoryController
in the same 0x21000023 case, matching GameWindow:2257-2265
(same layout subtree, contentsEmpty as the equip-slot placeholder,
sendWield: null since there is no live session in the studio).
- StudioWindow.OnLoad passes _dats! to the updated Populate signature.
SampleData.AddEquipped was already correct: MoveItem(guid, PlayerGuid,
-1, equipMask) at ClientObjectTable.cs:134 sets
CurrentlyEquippedLocation = newEquipLocation and calls Reindex which
places the item in _containerIndex[PlayerGuid]. No SampleData change
needed.
Tests: 2 new assertions in FixtureProviderTests —
sideBags_matchInventoryControllerFilter (Type.HasFlag(Container)
|| ItemsCapacity > 0 filter must match both bags) and
equippedItems_retainLocationAndAreInContents (equipped items have
CurrentlyEquippedLocation != None AND appear in GetContents so the
paperdoll controller can find them). 604 pass / 2 skip / 0 fail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task 4 of the UI Studio plan: adds SampleData (static ClientObjectTable
builder with a synthetic player, 6 loose items, 2 side bags, and 3 equipped
pieces) and FixtureProvider (switches on layoutId and calls the production
controller Bind methods — VitalsController, ToolbarController,
InventoryController — so the studio previews panels with plausible data
instead of empty widgets).
Icon ids approach: raw-resolve stub — resolves the base iconId via
RenderStack.ResolveChrome and returns the GL handle directly. This is v1
(single-layer icon); the full 5-layer IconComposer composite is a live-game
concern, not a layout-preview concern.
StudioWindow wires FixtureProvider.Populate after _source.Load; the
ClientObjectTable is stored on _objects so controller event subscriptions
(ObjectAdded/ObjectMoved) remain live for the window's lifetime.
4 new FixtureProviderTests (SampleTable_hasPackContents,
SampleTable_hasWeaponAndArmor, SampleTable_hasEquippedItems,
SampleTable_hasSideBags) — all pass. Full App suite: 602 passed / 2
skipped (pre-existing) / 0 failed. Build green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task 2 of the acdream UI Studio plan. Adds three new files under
src/AcDream.App/Studio/ and one new test file:
- StudioOptions.cs: record + Parse() for the ui-studio CLI args (positional
dat dir or ACDREAM_DAT_DIR, --layout 0xNNNN, --markup <path>; defaults to
vitals 0x2100006C when neither layout nor markup given).
- LayoutSource.cs: wraps LayoutImporter.Import for dat-backed layouts; markup
path sets LastError "markup unsupported (Task 6)" and returns null for now.
Exposes Kind / LayoutId / MarkupPath / LastError / CurrentLayout / Reload().
- StudioWindow.cs: Silk.NET 1280×720 GL 4.3 window; boots RenderBootstrap,
wires input to UiHost, loads the panel via LayoutSource, adds the root to
UiHost.Root.AddChild. QualitySettings resolved the same way GameWindow.Run()
does (SettingsStore → QualitySettings.From → WithEnvOverrides).
- Program.cs: ui-studio dispatch at the top of top-level statements (before
the dat-dir parse) so `dotnet run -- ui-studio` routes to StudioWindow.
- LayoutSourceTests.cs: dat-gated test (skips when dats absent); verifies that
the vitals LayoutDesc (0x2100006C) loads, root is non-null, byId contains the
vitals root element (0x100005F9), and Kind == DatLayout. Passes (1/1 with dats
present; silently skips on CI).
Note: the spec asserts FindElement(0x2100006Cu) — that id is the LayoutDesc
dat id, not a widget element id. The actual vitals root element id is 0x100005F9
(confirmed from the vitals_2100006C.json fixture). The test uses the correct
element id and documents the discrepancy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>