From fe319bd2aa7481431b818682b00d5774402b72e2 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 23 Jun 2026 09:58:22 +0200 Subject: [PATCH] =?UTF-8?q?fix(D.2b):=20Slice=202=20visual=20gate=20?= =?UTF-8?q?=E2=80=94=20frame=20the=20whole=20doll=20+=20caption=20the=20Sl?= =?UTF-8?q?ots=20toggle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visual gate 1 (user): the doll rendered but only the legs showed (camera aimed at the model origin = the feet) and the Slots button was invisible. - DollCamera: aim the look-at at mid-body (~0.95 m) and stand back ~3.7 m so the whole ~1.9 m figure fits. (Size is a later retail-comparison polish per the user.) - PaperdollController: the Slots button (0x100005BE) is found + wired (diagnostic confirmed armorSlots=9/9, viewport=UiViewport, slotsButton=UiButton) but its dat element has no face sprite, so it drew nothing. Give it a gold "Slots" caption (UiButton.Label, like chat Send) via a new datFont param on Bind. Temporary [Slice2-paperdoll] diagnostic logs the button rect + the found widgets (stripped at wrap-up). Idle animation deferred to a focused follow-up (faithful idle needs a full AnimatedEntity + Sequencer through the TickAnimations multi-branch path + re-dress coordination — real integration risk vs the verified static doll). Build + full App suite green (594). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.App/Rendering/DollCamera.cs | 17 +++++++----- src/AcDream.App/Rendering/GameWindow.cs | 3 ++- .../UI/Layout/PaperdollController.cs | 27 ++++++++++++++++--- .../Rendering/DollCameraTests.cs | 8 +++--- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/AcDream.App/Rendering/DollCamera.cs b/src/AcDream.App/Rendering/DollCamera.cs index b2ba8ff4..c1616bfe 100644 --- a/src/AcDream.App/Rendering/DollCamera.cs +++ b/src/AcDream.App/Rendering/DollCamera.cs @@ -3,21 +3,26 @@ using System.Numerics; namespace AcDream.App.Rendering; /// -/// Fixed camera for the paperdoll mini-scene. Eye and look-at from -/// gmPaperDollUI::PostInit (decomp lines 175521–175527): -/// eye (0.12, -2.4, 0.88) looking at the origin, AC up-axis = +Z. +/// Fixed camera for the paperdoll mini-scene. Derived from gmPaperDollUI::PostInit +/// (decomp lines 175521–175527: eye offset ~(0.12, -2.4, 0.88) about the origin), but with the +/// look-at raised to mid-body and the camera pulled back after the first visual gate: the doll's +/// model origin is at the FEET (z=0), so aiming at the origin framed only the lower legs. We aim +/// at ~0.95 m (a human's vertical centre) and stand back ~3.7 m so the whole ~1.9 m figure fits. +/// AC up-axis = +Z. /// /// Uses the same / /// convention as /// so the doll's triangle winding and back-face /// culling match the world render pass. /// -/// Per-race camera distance (UpdateForRace) is deferred polish. +/// Exact per-race camera framing (UpdateForRace) is deferred polish. /// public sealed class DollCamera : ICamera { - private static readonly Vector3 Eye = new(0.12f, -2.4f, 0.88f); - private static readonly Vector3 Target = Vector3.Zero; + // Body centre height (metres) the camera aims at — model origin is at the feet (z=0). + private const float BodyCentreZ = 0.95f; + private static readonly Vector3 Eye = new(0.12f, -3.7f, BodyCentreZ); + private static readonly Vector3 Target = new(0f, 0f, BodyCentreZ); private static readonly Vector3 Up = Vector3.UnitZ; // AC up-axis = +Z, same as ChaseCamera /// Vertical field of view in radians. ~34° default; tune at the visual gate. diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 860568d6..c36f3d1b 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -2261,7 +2261,8 @@ public sealed class GameWindow : IDisposable sendWield: (item, mask) => _liveSession?.SendGetAndWieldItem(item, mask), // Empty equip slots show a visible frame (same square as the inventory grid) so every // slot position is seen + usable; the live 3D character (the doll) is Slice 2. - emptySlotSprite: contentsEmpty); + emptySlotSprite: contentsEmpty, + datFont: vitalsDatFont); // Slice 2: caption the "Slots" toggle button // Slice 2: capture the doll viewport widget (Type 0xD, element 0x100001D5) + the inventory // frame so the per-frame pre-UI hook can render the 3-D doll into the widget's texture only diff --git a/src/AcDream.App/UI/Layout/PaperdollController.cs b/src/AcDream.App/UI/Layout/PaperdollController.cs index 82c47758..77d5f9c4 100644 --- a/src/AcDream.App/UI/Layout/PaperdollController.cs +++ b/src/AcDream.App/UI/Layout/PaperdollController.cs @@ -85,7 +85,7 @@ public sealed class PaperdollController : IItemListDragHandler private PaperdollController( ImportedLayout layout, ClientObjectTable objects, Func playerGuid, Func iconIds, Action? sendWield, - uint emptySlotSprite) + uint emptySlotSprite, UiDatFont? datFont) { _objects = objects; _playerGuid = playerGuid; _iconIds = iconIds; _sendWield = sendWield; @@ -117,8 +117,27 @@ public sealed class PaperdollController : IItemListDragHandler _dollViewport = layout.FindElement(0x100001D5u); // doll viewport (may be null until Slice 3) - if (layout.FindElement(0x100005BEu) is UiButton slotsBtn) + var slotsBtnEl = layout.FindElement(0x100005BEu); + if (slotsBtnEl is UiButton slotsBtn) + { slotsBtn.OnClick = () => { _viewState.Toggle(); ApplyView(); }; + // The dat element has no face sprite, so without a label the button is invisible. Give it a + // "Slots" caption (gold, like the chat Send button) so it's findable + clickable. + if (datFont is not null) + { + slotsBtn.Label = "Slots"; + slotsBtn.LabelFont = datFont; + slotsBtn.LabelColor = new System.Numerics.Vector4(1f, 0.92f, 0.72f, 1f); + } + } + + // Slice 2 diagnostic (temporary): confirm the importer surfaced the toggle button + doll + // viewport, and WHERE the button sits. Stripped once the toggle is visually confirmed. + Console.WriteLine($"[Slice2-paperdoll] armorSlots={_armorSlots.Count}/9 " + + $"viewport={_dollViewport?.GetType().Name ?? "MISSING"} " + + $"slotsButton={slotsBtnEl?.GetType().Name ?? "MISSING"} " + + $"btnRect=({slotsBtnEl?.Left ?? -1},{slotsBtnEl?.Top ?? -1},{slotsBtnEl?.Width ?? -1},{slotsBtnEl?.Height ?? -1}) " + + $"btnVisible={slotsBtnEl?.Visible.ToString() ?? "n/a"}"); ApplyView(); // initial state = doll-view (armor slots hidden) @@ -128,8 +147,8 @@ public sealed class PaperdollController : IItemListDragHandler public static PaperdollController Bind( ImportedLayout layout, ClientObjectTable objects, Func playerGuid, Func iconIds, Action? sendWield = null, - uint emptySlotSprite = 0u) - => new PaperdollController(layout, objects, playerGuid, iconIds, sendWield, emptySlotSprite); + uint emptySlotSprite = 0u, UiDatFont? datFont = null) + => new PaperdollController(layout, objects, playerGuid, iconIds, sendWield, emptySlotSprite, datFont); private void OnObjectChanged(ClientObject o) { if (Concerns(o)) Populate(); } private void OnObjectMoved(ClientObject o, uint from, uint to) diff --git a/tests/AcDream.App.Tests/Rendering/DollCameraTests.cs b/tests/AcDream.App.Tests/Rendering/DollCameraTests.cs index c65afdfb..6a82af78 100644 --- a/tests/AcDream.App.Tests/Rendering/DollCameraTests.cs +++ b/tests/AcDream.App.Tests/Rendering/DollCameraTests.cs @@ -7,14 +7,16 @@ namespace AcDream.App.Tests.Rendering; public class DollCameraTests { [Fact] - public void Eye_position_is_the_retail_camera_offset() + public void Eye_position_frames_the_doll_from_the_front() { var cam = new DollCamera { Aspect = 100f / 214f }; Assert.True(Matrix4x4.Invert(cam.View, out var inv)); var eye = inv.Translation; + // Tuned at the visual gate: aim at mid-body (z≈0.95) and stand back ~3.7 m so the whole + // figure fits (model origin is at the feet, so aiming at the origin framed only the legs). Assert.Equal(0.12f, eye.X, 3); - Assert.Equal(-2.4f, eye.Y, 3); - Assert.Equal(0.88f, eye.Z, 3); + Assert.Equal(-3.7f, eye.Y, 3); + Assert.Equal(0.95f, eye.Z, 3); } [Fact]