A standalone dev tool that renders any acdream UI panel through the PRODUCTION renderer (UiHost/LayoutImporter/dat-sprite path + the WB mesh pipeline for the 3-D doll), with an ImGui click-to-inspect inspector, sample-data fixtures, markup hot-reload + write-back, and render-config sliders. Collapses the edit→build→login→F12→eyeball loop into edit→glance. Full v1 scope (user pre-approved): both sources (dat LayoutDesc id + markup file), full fixtures, editable inspector + doll-camera sliders, live doll. Seven isolated units; the highest-risk step (extracting a shared RenderBootstrap from GameWindow.OnLoad) is sequenced first behind a "game still renders" gate, with a studio-local-duplicate fallback. The drag-drop designer is deferred to phase 2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
acdream UI Studio — design (live previewer + inspector)
- Date: 2026-06-25
- Status: approved (user pre-approved full v1 scope 2026-06-25)
- Branch: claude/hopeful-maxwell-214a12
- Supersedes/foundation for: the deferred phase-2 drag-drop designer
Goal
A standalone, fast-booting dev tool that renders any acdream UI panel through the production renderer, with a click-to-inspect element inspector, sample-data fixtures, markup hot-reload + write-back editing, and render-config sliders.
It collapses the panel-iteration loop — today edit → build → login to ACE → wait ~8s → F12 → eyeball vs retail → close → repeat (~30–60 s/iteration) — into edit → glance
(~1 s). Everything we did on the paperdoll this session (confirming the pose, framing the
camera, catching the mirrored heading) would have been a few clicks here instead of a dozen
relaunches.
Fidelity is the whole point. The previewed panel is drawn by the SAME
UiHost / LayoutImporter / DatWidgetFactory / dat-sprite path that ships, so what you
see is what the game renders. This is the decisive reason NOT to build the tool in a
separate stack (Godot/HTML) and NOT to rely on the existing render-vitals-mockup (a
separate CPU SurfaceDecoder composite that can drift from the GL path).
Scope — v1 is the maximum capability
IN:
- Load a panel from EITHER a dat
LayoutDescid OR a markup file (MarkupDocument). - Render via the production GL
UiHost(2-D panels) AND the full WB mesh pipeline (the 3-D paperdoll doll). - Sample-data fixtures so data-bound panels look populated: canned inventory items, sample vital values, a dressed sample creature for the doll.
- Inspector (ImGui): element tree, click-to-inspect, read-only props (id / Type / rect / anchors / state-sprites / ZOrder), editable markup props (write back to the file), and render-config sliders (the doll camera eye / FOV / heading — values that live in code, not markup).
- Hot-reload: file-watch the markup source; reload-on-demand (id picker / reload button) for dat layouts.
OUT (phase 2): the drag-drop visual designer (compose new layouts on a canvas and emit markup). The previewer is the foundation that phase sits on. Its natural home is the plugin-UI story, not the retail-panel work.
Not faithfulness-gated: this is a DEV tool. The previewed panels are faithful (same
renderer); the tool chrome (the ImGui inspector) is dev-only. No
retail-divergence-register rows are created by this work.
Architecture
Seven isolated, single-purpose units. The previewed panel is drawn by the production
renderer; the tool chrome is ImGui (already in AcDream.App for the ACDREAM_DEVTOOLS
overlay — ideal for a tree / sliders / editable fields at near-zero cost).
Program.cs ──"ui-studio"──▶ StudioWindow
│ owns: Silk window, ImGui ctx, frame loop
▼
RenderBootstrap.Create(gl, opts) ──▶ RenderStack
│ (GL + DatCollection + TextureCache + UiHost
│ + ObjectMeshManager + WbDrawDispatcher + lighting UBO)
│ ALSO consumed by GameWindow (shared = de-tangles OnLoad)
┌─────────────────────┼───────────────────────────────┐
▼ ▼ ▼
LayoutSource FixtureProvider StudioInspector (ImGui)
dat id | markup sample item table / vitals / tree · props(read+edit) ·
→ UiElement tree dressed creature → controllers render sliders · canvas(FBO)
│ │ click → HitTestTopDown
└────────── HotReloadWatcher (markup) ─────────────────┘
MarkupWriteBack (edited props → file)
1. RenderBootstrap — NEW, src/AcDream.App/Rendering/
Extracts the render-stack construction currently tangled inside GameWindow.OnLoad
(the GL api, dat-dir → DatCollection, TextureCache, the WB mesh pipeline —
ObjectMeshManager / WbDrawDispatcher / SceneLightingUboBinding — and UiHost) into a
reusable unit that BOTH GameWindow and StudioWindow build through.
- Interface:
RenderStack Create(GL gl, RenderBootstrapOptions opts);RenderStackis a record of the constructed pieces (dats, textureCache, meshManager, drawDispatcher, lightingUbo, uiHost, shaderDir). GameWindow.OnLoadis refactored to consume it — behavior-preserving, locked by the existing App/Core suites + a visual gate that the GAME still renders.- Highest-risk step → extract the SMALLEST cohesive slice only; do it first, gate it, build
the studio on it second. Fallback if extraction is too entangled:
StudioWindowbuilds a studio-local duplicate of the mesh-pipeline setup (more drift, lower regression risk) — to be decided at implementation, not assumed away.
2. StudioWindow — NEW, src/AcDream.App/Studio/
A Silk.NET windowed GL app — the studio entry. Owns the window, the RenderStack, the ImGui
context, the inspector, the hot-reload watcher, and the per-frame loop. No ACE /
world-streaming / physics / movement input. Launched from Program.cs:
AcDream.App ui-studio <datdir> [--layout 0x21000023 | --markup path.xml]. Config flows
through RuntimeOptions (one env/arg read), per the code-structure rules.
3. LayoutSource — NEW, src/AcDream.App/Studio/
Loads a panel into a UiElement subtree from a dat id (LayoutImporter.Import) or a markup
file (MarkupDocument.Build). Owns the current source + Reload().
- Interface:
UiElement Load(),void Reload(),SourceKind Kind,string? MarkupPath,uint? LayoutId, plus astring? LastErrorfor the inspector to surface parse failures.
4. FixtureProvider — NEW, src/AcDream.App/Studio/
Supplies sample data so data-bound panels render populated. A fake ClientObjectTable
seeded with a handful of canned items (icon ids + types); sample vital values; a sample
creature (Setup + ObjDesc) for the doll. Maps a known layoutId → its real controller
(VitalsController / InventoryController / ToolbarController / PaperdollController)
bound to the fixtures — REUSING the production controllers, not re-implementing them.
- Interface:
void Populate(uint layoutId, UiElement root, RenderStack stack). Unknown layout id → no fixture (structural preview only), not an error.
5. StudioInspector — NEW, src/AcDream.App/Studio/
The ImGui tool chrome, four docked windows:
- Canvas — the panel rendered to an FBO (RTT, the
PaperdollViewportRenderertechnique applied to the whole panel) shown viaImGui.Image. Click → map canvas-local coords →UiRoot.HitTestTopDown→ select. The FBO route gives a clean dockable IDE layout + accurate click mapping, and isolates the panel's GL state from ImGui's. - Tree — the live
UiElementhierarchy with each node's registered Type; select syncs with the canvas highlight. - Properties — the selected element's id / Type / rect / anchors / state-sprites (with
swatches) / ZOrder. Rect/anchor fields are editable (markup source) → applied live +
queued to
MarkupWriteBack. - Render config — sliders for the doll's
DollCameraeye / FOV / heading, mutating the live camera so the doll updates next frame; a "copy values" button emits the literals to paste into code.
6. MarkupWriteBack — NEW, src/AcDream.App/Studio/
Serializes an edited element's changed props back to the markup XML file (markup source only; dat layouts are read-only). v1 = targeted in-place attribute updates (find the element by id, rewrite the changed attribute values), NOT a full tree re-serialize — so hand-authored formatting/comments survive.
7. HotReloadWatcher — NEW, src/AcDream.App/Studio/
FileSystemWatcher on the markup file → debounced (≈150 ms) reload event → LayoutSource.Reload
→ FixtureProvider.Populate → re-select the previously-selected element by id when possible.
Data flow
- Boot:
StudioWindow→RenderBootstrap.Create→LayoutSource.Load(initial)→FixtureProvider.Populate→ loop. - Frame: bind panel FBO →
UiHost.Tick+Draw(panel, incl. the doll's own nested RTT) → unbind → ImGui frame (Canvas image + Tree + Properties + Render-config) → present. - Click canvas: map coords →
HitTestTopDown→ select → Properties shows props. - Edit prop: apply to the live
UiElement(instant) + (markup) queue write-back. - File change: watcher →
Reload→Populate→ re-select by id. - Render slider: mutate
DollCamera→ next frame the doll re-renders.
Error handling
- Bad layout id / unparseable markup →
LayoutSource.LastErrorshown in the inspector; the previous panel stays (or an empty canvas), no crash. - Missing dats at boot → clear error + exit non-zero.
- Panel build/draw wrapped in try/catch → a thrown panel surfaces its exception text in the inspector; the tool stays alive (you're often previewing half-broken markup).
- Write-back failure (file locked) → non-fatal inline notice, edit stays applied in-memory.
Testing
LayoutSource: load a known dat id + a markup string → assert theUiElementtree shape (reuse the existing golden-fixture pattern, e.g.vitals_2100006C.json).FixtureProvider: assert the seededClientObjectTable+ vitals shapes.MarkupWriteBack: round-trip — parse → edit a rect → serialize → re-parse → assert the changed attribute + that siblings/formatting are untouched.RenderBootstrap: the existing suites stay green + a manual game visual gate (the refactor is behavior-preserving).- Window / ImGui / doll render: manual visual gate — the studio is itself the visual tool.
- Tests live in
tests/AcDream.App.Tests/Studio/.
Build order (staged; each ≈ one subagent chunk + a gate)
RenderBootstrapextraction — behavior-preserving. GATE: game renders unchanged; suites green.StudioWindowskeleton +LayoutSource(dat id) — render a 2-D panel to the window. GATE:ui-studio --layout 0x2100006Cshows vitals.StudioInspector— ImGui + canvas-FBO + tree + read-only props + click-to-inspect. GATE: click an element → see its id/rect/sprites.FixtureProvider— 2-D panels populated (vitals / toolbar / inventory). GATE: inventory shows sample items.- Live doll —
PaperdollViewportRendererin the studio + sample creature. GATE: the paperdoll shows a dressed doll. - Markup source +
HotReloadWatcher— GATE: edit a markup file → studio reloads. - Editable markup props +
MarkupWriteBack— GATE: edit a rect in the inspector → file updates → reloads. - Render-config sliders — GATE: drag the eye/FOV → doll updates live.
Steps 1–4 deliver the high-value 2-D previewer + inspector + fixtures fast; 5–8 add the doll and the editing power. The end-state is the full maximum-capability tool.
Open risks
RenderBootstrapextraction from the tangledGameWindow.OnLoadis the riskiest step. Mitigation: smallest cohesive slice, behavior-preserving, gated by the game visual + the suites BEFORE the studio depends on it; documented fallback (studio-local duplicate).MarkupWriteBackformatting preservation — keep v1 to in-place attribute edits, never a full re-serialize, so hand-authored markup isn't clobbered.- ImGui + UiHost GL-state interplay — the panel renders to its own FBO and ImGui owns the default framebuffer, so their GL state can't fight (the same seal the paperdoll RTT uses).