Commit graph

9 commits

Author SHA1 Message Date
Erik
4988dd4dfe feat(studio): Character panel pilot — gmCharacterInfoUI report text + menu-bar picker
Task A — CharacterController (LayoutDesc 0x2100002E)
- CharacterSheet.cs: data record for all fields used by the report sections,
  with retail property-id citations per field (DateOfBirth 0x62, TotalPlayTime
  0x7d, NumDeaths 0x2b, SkillCredits 0xb5/0xc0, AugmentationStat 0x162,
  EncumbranceVal 0x5/0xe6).
- CharacterController.cs: static Bind(layout, data, datFont) wires element
  0x1000011d (m_pMainText, confirmed gmCharacterInfoUI::PostInit 0x004b86f0) as
  a UiText; LinesProvider builds the report. Report section order mirrors
  gmCharacterInfoUI::Update (0x004ba790):
  1. UpdatePlayerBirthAgeDeaths 0x004b8cb0 — birth/age/deaths
  2. UpdateEnduranceInfo 0x004b8eb0 — vitals (H/S/M cur/max)
  3. UpdateInnateAttributeInfo 0x004b87e0 — 6 attrs InqAttribute 1,2,4,3,5,6
     = Strength, Endurance, Quickness, Coordination, Focus, Self
  4. UpdateFakeSkills 0x004b8930 — skill credits (InqInt 0xb5 / 0xc0)
  5. UpdateAugmentations 0x004b9000 — aug name (InqInt 0x162 switch 1..0xb)
  6. UpdateLoad 0x004b8a20 — burden cur/max/pct
  Element 0x1000011d imports as UiText (Type 12); multi-line text set via
  LinesProvider — same pattern as ChatWindowController transcript.
- SampleData.SampleCharacter(): plausible retail-scale CharacterSheet fixture.
- FixtureProvider.Populate case 0x2100002Eu: CharacterController.Bind with
  SampleData.SampleCharacter + VitalsDatFont.
- CharacterControllerTests.cs: 10 pure data-wiring + content tests (no GL/dats).

Task B — Studio panel picker → main menu bar
- StudioWindow.OnRender: replaced the floating "Studio" toolbar window (kToolbarH
  40px) with ImGui.BeginMainMenuBar / EndMainMenuBar (kMenuBarH 22px). Panel
  picker combo now lives in the always-on-top menu bar and cannot be occluded by
  Tree/Canvas/Props panes. paneY reduced from 40→22, freeing ~18px for the canvas.

Build: dotnet build green (0 CS errors; DLL-lock warnings are AcDream.App being
live — not compile errors). Full suite: 619 passed, 2 skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 18:37:30 +02:00
Erik
c03a241884 feat(studio): UX pass — selection highlight, in-studio panel picker, fixed layout
Fix 1 (Selection highlight): StudioInspector.DrawCanvas draws a bright-green
2px outline over Selected using the window draw list. ScreenPosition maps
directly to FBO pixel space; rectMin offsets into screen space.

Fix 2 (In-studio panel picker): DrawToolbar (BeginCombo over ListSlugs output).
StudioWindow resolves _dumpFile once in OnLoad, tracks _currentSlug, and calls
LoadDumpPanel on combo change. LoadDumpPanel removes old root via RemoveChild,
loads new slug, resets Selected. No relaunch needed.

Fix 3 (Fixed layout): All four panes use SetNextWindowPos + SetNextWindowSize
with ImGuiCond.FirstUseEver. Toolbar (0,0 x windowW x 40), tree (0,40 x 280),
canvas (280,40 x centre), props (right-340,40 x 340). User can drag from these.

Build + dotnet test green (609 passed, 0 failed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:58:50 +02:00
Erik
f778b100b7 feat(studio): headless --screenshot mode (render panel to PNG + exit)
Add `--screenshot <path>` to the UI Studio: when set the window is
created hidden (WindowOptions.IsVisible = false), the panel is loaded
and rendered into PanelFbo on the first OnRender tick, pixels are read
back with the new PanelFbo.ReadColorRgba(), rows are flipped (GL
bottom-left → PNG top-left), and the result is saved via ImageSharp
SaveAsPng before calling _window.Close().

Render size is derived from the loaded root's Width/Height (clamped
256–2048 px); falls back to 1280×720 when the root has no explicit
size.  In headless mode ImGui, the inspector, and input wiring are
all skipped — only PanelFbo is created.  Interactive path is
unchanged.  SixLabors.ImageSharp 3.1.12 was already a direct dep of
AcDream.App (Phase O-T7); no new PackageReference needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 16:36:10 +02:00
Erik
e3de7f0dab feat(studio): dump LayoutSource — preview any retail window from the UI dump
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>
2026-06-25 16:25:23 +02:00
Erik
9ed9d8dbd9 fix(studio): Task 4 — bind PaperdollController + wire empty-slot sprites
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>
2026-06-25 15:59:13 +02:00
Erik
0bdc866c15 feat(studio): FixtureProvider — sample data populates the 2-D panels
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>
2026-06-25 15:45:11 +02:00
Erik
101a35cc2d fix(studio): Task 3 review — UiRoot.Pick, RenderStack IDisposable, dt cleanup
Code-review follow-ups to the ImGui inspector:
- Add public UiRoot.Pick(x,y) over the private HitTestTopDown (honors
  Z-order + modal exclusivity); StudioWindow uses it instead of a manual
  UiElement.HitTest with subtracted ScreenPosition.
- RenderStack : IDisposable — disposes the GL pieces it owns in one place;
  StudioWindow OnClosing + Dispose both call _stack?.Dispose(), closing the
  error-path leak (only UiHost was disposed on the Dispose-without-OnClosing
  path).
- Drop the stale _dt field; OnRender passes its own dt to Tick + BeginFrame.
- Fix a stale PanelFbo comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:14:47 +02:00
Erik
d2240974ec feat(studio): ImGui inspector — canvas FBO, element tree, props, click-to-inspect
Task 3 of the acdream UI Studio plan. The studio previously drew the
panel straight to the window; it now renders the panel into an off-screen
FBO (PanelFbo) and displays it in an ImGui Canvas pane alongside a Tree
pane (recursive element hierarchy, click-to-select) and a Properties pane
(EventId/type/rect/anchors/ZOrder/flags of the selected element).

Click-to-inspect: a left-click inside the Canvas calls UiElement.HitTest
on the panel root (same-assembly internal access) and selects the topmost
element at the cursor, wiring the canvas directly to the tree selection.

PanelFbo lifecycle mirrors PaperdollViewportRenderer (RGBA8 color +
Depth24Stencil8 renderbuffer, GLStateScope-sealed, lazy resize on size
change). V-flip in DrawCanvas (uv0=(0,1)/uv1=(1,0)) corrects GL's
bottom-left FBO origin to ImGui's top-left convention so click Y maps
directly to panel-local Y without inversion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 15:06:12 +02:00
Erik
df6b5b3b39 feat(studio): StudioWindow + LayoutSource — render a dat panel standalone
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>
2026-06-25 14:26:47 +02:00