docs(studio): design spec — acdream UI Studio (live previewer + inspector)
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>
This commit is contained in:
parent
4d65a683f4
commit
d607b55e61
1 changed files with 195 additions and 0 deletions
195
docs/superpowers/specs/2026-06-25-ui-studio-previewer-design.md
Normal file
195
docs/superpowers/specs/2026-06-25-ui-studio-previewer-design.md
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
# 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 `LayoutDesc` id 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)`; `RenderStack` is a
|
||||
record of the constructed pieces (dats, textureCache, meshManager, drawDispatcher,
|
||||
lightingUbo, uiHost, shaderDir).
|
||||
- `GameWindow.OnLoad` is 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: `StudioWindow` builds 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 a `string? LastError` for 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 `PaperdollViewportRenderer` technique
|
||||
applied to the whole panel) shown via `ImGui.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 `UiElement` hierarchy 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 `DollCamera` eye / 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.LastError` shown 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 the `UiElement` tree shape
|
||||
(reuse the existing golden-fixture pattern, e.g. `vitals_2100006C.json`).
|
||||
- `FixtureProvider`: assert the seeded `ClientObjectTable` + 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)
|
||||
|
||||
1. **`RenderBootstrap` extraction** — behavior-preserving. GATE: game renders unchanged;
|
||||
suites green.
|
||||
2. **`StudioWindow` skeleton + `LayoutSource`(dat id)** — render a 2-D panel to the window.
|
||||
GATE: `ui-studio --layout 0x2100006C` shows vitals.
|
||||
3. **`StudioInspector`** — ImGui + canvas-FBO + tree + read-only props + click-to-inspect.
|
||||
GATE: click an element → see its id/rect/sprites.
|
||||
4. **`FixtureProvider`** — 2-D panels populated (vitals / toolbar / inventory). GATE:
|
||||
inventory shows sample items.
|
||||
5. **Live doll** — `PaperdollViewportRenderer` in the studio + sample creature. GATE: the
|
||||
paperdoll shows a dressed doll.
|
||||
6. **Markup source + `HotReloadWatcher`** — GATE: edit a markup file → studio reloads.
|
||||
7. **Editable markup props + `MarkupWriteBack`** — GATE: edit a rect in the inspector → file
|
||||
updates → reloads.
|
||||
8. **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
|
||||
|
||||
- **`RenderBootstrap` extraction from the tangled `GameWindow.OnLoad`** is 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).
|
||||
- **`MarkupWriteBack` formatting 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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue