fix(ui): gate — no void frames around the login wormhole; vitals icons centered
The login tunnel now covers from the first world-facing frame (the sky-void backdrop can never present pre-tunnel) and holds through an atomic tunnel-to-world swap at reveal completion — the void is structurally unreachable on both edges, pinned by frame-sequence tests across WorldSceneRenderer/WorldRevealCoordinator/LocalPlayerTeleport- Controller/RuntimeWorldTransitState. Vitals detail icons draw at their authored centered offsets in both stacked and side-by-side layouts. Implemented and live-probed by the fix agent; finalized by the lead after the agent parked post-verification (gates re-run green: App 5512/3, Runtime 1747/0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2f8c046aba
commit
fdc4fd496d
17 changed files with 649 additions and 66 deletions
|
|
@ -7,6 +7,24 @@ namespace AcDream.App.UI;
|
|||
/// — the same mapping ElementReader applies at element level).</summary>
|
||||
public enum UiMeterLabelAlign : byte { Left = 0, Center = 1, Right = 2 }
|
||||
|
||||
/// <summary>
|
||||
/// One absorbed vitals detail-icon overlay (the <c>0x100004A9</c> child of a
|
||||
/// meter's back/front slice container): its ShowDetail sprite, authored rect
|
||||
/// (local to the container, which spans the meter at 0,0), the authored raw
|
||||
/// edge-anchor modes (<c>ElementDesc</c> Left/Top/Right/Bottom, values 0–4),
|
||||
/// and the container's authored size. The overlays author <c>L3/R3</c> —
|
||||
/// retail's CENTER anchors — so when the meter is resized away from its
|
||||
/// authored width the icon re-centers through the exact
|
||||
/// <see cref="UiLayoutPolicy"/> port of
|
||||
/// <c>UIElement::UpdateForParentSizeChange @0x00462640</c> instead of staying
|
||||
/// pinned at its authored X (the gate-observed left-drift).
|
||||
/// </summary>
|
||||
internal readonly record struct UiMeterDetailOverlaySpec(
|
||||
uint Sprite,
|
||||
float X, float Y, float W, float H,
|
||||
uint LeftMode, uint TopMode, uint RightMode, uint BottomMode,
|
||||
float ParentW, float ParentH);
|
||||
|
||||
/// <summary>
|
||||
/// A horizontal vital bar (retail HP/Stamina/Mana style): a background rect, a
|
||||
/// partial-width solid fill, and an optional centered "current/max" numeric
|
||||
|
|
@ -28,19 +46,22 @@ public sealed class UiMeter : UiElement, IUiDatStateful
|
|||
// Vitals ShowDetail icon overlays (see ConfigureDetailOverlay).
|
||||
private bool _detailConfigured;
|
||||
private bool _detailPassToChildren;
|
||||
private uint _detailBackSprite;
|
||||
private uint _detailFrontSprite;
|
||||
private (float X, float Y, float W, float H) _detailBackRect;
|
||||
private (float X, float Y, float W, float H) _detailFrontRect;
|
||||
private UiMeterDetailOverlaySpec _detailBack;
|
||||
private UiMeterDetailOverlaySpec _detailFront;
|
||||
|
||||
/// <summary>True when this meter absorbed the vitals detail-icon overlays. Exposed for tests.</summary>
|
||||
internal bool HasDetailOverlay => _detailConfigured;
|
||||
/// <summary>The dim back-container detail icon (ShowDetail media). Exposed for tests.</summary>
|
||||
internal uint DetailBackSprite => _detailBackSprite;
|
||||
internal uint DetailBackSprite => _detailBack.Sprite;
|
||||
/// <summary>The bright fill-clipped front-container detail icon. Exposed for tests.</summary>
|
||||
internal uint DetailFrontSprite => _detailFrontSprite;
|
||||
internal uint DetailFrontSprite => _detailFront.Sprite;
|
||||
/// <summary>The back overlay's authored meter-local rect. Exposed for tests.</summary>
|
||||
internal (float X, float Y, float W, float H) DetailBackRect => _detailBackRect;
|
||||
internal (float X, float Y, float W, float H) DetailBackRect =>
|
||||
(_detailBack.X, _detailBack.Y, _detailBack.W, _detailBack.H);
|
||||
/// <summary>The complete absorbed back overlay. Exposed for tests.</summary>
|
||||
internal UiMeterDetailOverlaySpec DetailBack => _detailBack;
|
||||
/// <summary>The complete absorbed front overlay. Exposed for tests.</summary>
|
||||
internal UiMeterDetailOverlaySpec DetailFront => _detailFront;
|
||||
|
||||
/// <summary>Dat element id, set by the layout importer so duplicated page copies can be scoped.</summary>
|
||||
public uint ElementId { get; set; }
|
||||
|
|
@ -142,22 +163,69 @@ public sealed class UiMeter : UiElement, IUiDatStateful
|
|||
/// <c>m_pcChildImage</c> child, element id 2, to the 0x69 fraction), so
|
||||
/// the icon itself fills up with the vital. Both overlays author media
|
||||
/// ONLY for <c>ShowDetail</c> (<c>HideDetail</c> authors File=0), so they
|
||||
/// draw solely in that state. Rects are the overlays' authored X/Y/W/H
|
||||
/// local to the meter (the containers span the meter at 0,0).
|
||||
/// draw solely in that state. Each spec carries the overlay's authored
|
||||
/// rect (local to its container, which spans the meter at 0,0), its raw
|
||||
/// edge-anchor modes, and the container's authored size — see
|
||||
/// <see cref="ComputeDetailOverlayRect"/> for how those position the icon
|
||||
/// on a resized meter.
|
||||
/// </summary>
|
||||
internal void ConfigureDetailOverlay(
|
||||
uint backSprite, float backX, float backY, float backW, float backH,
|
||||
uint frontSprite, float frontX, float frontY, float frontW, float frontH,
|
||||
in UiMeterDetailOverlaySpec back,
|
||||
in UiMeterDetailOverlaySpec front,
|
||||
bool passToChildren)
|
||||
{
|
||||
_detailBackSprite = backSprite;
|
||||
_detailBackRect = (backX, backY, backW, backH);
|
||||
_detailFrontSprite = frontSprite;
|
||||
_detailFrontRect = (frontX, frontY, frontW, frontH);
|
||||
_detailConfigured = backSprite != 0 || frontSprite != 0;
|
||||
_detailBack = back;
|
||||
_detailFront = front;
|
||||
_detailConfigured = back.Sprite != 0 || front.Sprite != 0;
|
||||
_detailPassToChildren = passToChildren;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The overlay's effective meter-local rect at the meter's CURRENT size.
|
||||
/// At the authored container size the authored rect stands verbatim —
|
||||
/// retail's <c>UIElement::UpdateForParentSizeChange @0x00462640</c> runs
|
||||
/// only when a parent actually resizes, and the designers hand-placed
|
||||
/// rects that are not always the exact center formula (the stamina sword
|
||||
/// authors X=32 where the mode-3 formula yields 33). On any other size
|
||||
/// the authored edge modes are applied from the ORIGINAL child/parent
|
||||
/// rects through <see cref="UiLayoutPolicy.Apply"/>, the exact port of
|
||||
/// that retail routine — the overlays' authored <c>L3/R3</c> center
|
||||
/// anchors are what keep the heart/sword/scepter centered when the
|
||||
/// vitals window is resized (retail near mode 3 =
|
||||
/// <c>curParentW/2 - origChildW/2</c> @0x004627ca; far mode 3 =
|
||||
/// <c>curParentW/2 + origChildW/2 - 1</c> @0x00462827, integer
|
||||
/// arithmetic, so odd authored widths lose one pixel exactly as retail
|
||||
/// does).
|
||||
/// </summary>
|
||||
internal static (float X, float Y, float W, float H) ComputeDetailOverlayRect(
|
||||
in UiMeterDetailOverlaySpec overlay, float meterW, float meterH)
|
||||
{
|
||||
int parentW = (int)meterW, parentH = (int)meterH;
|
||||
int origParentW = (int)overlay.ParentW, origParentH = (int)overlay.ParentH;
|
||||
if (origParentW <= 0 || origParentH <= 0
|
||||
|| (parentW == origParentW && parentH == origParentH))
|
||||
{
|
||||
return (overlay.X, overlay.Y, overlay.W, overlay.H);
|
||||
}
|
||||
|
||||
var originalChild = UiPixelRect.FromPositionAndSize(
|
||||
(int)overlay.X, (int)overlay.Y, (int)overlay.W, (int)overlay.H);
|
||||
var originalParent = UiPixelRect.FromPositionAndSize(
|
||||
0, 0, origParentW, origParentH);
|
||||
var currentParent = UiPixelRect.FromPositionAndSize(
|
||||
0, 0, parentW, parentH);
|
||||
UiPixelRect effective = UiLayoutPolicy.Apply(
|
||||
overlay.LeftMode,
|
||||
overlay.TopMode,
|
||||
overlay.RightMode,
|
||||
overlay.BottomMode,
|
||||
originalChild,
|
||||
originalParent,
|
||||
originalChild,
|
||||
currentParent);
|
||||
return (effective.X0, effective.Y0, effective.Width, effective.Height);
|
||||
}
|
||||
|
||||
public bool TrySetRetailState(uint stateId)
|
||||
{
|
||||
// Vitals detail toggle (gmVitalsUI::ListenToElementMessage @0x004BFC00
|
||||
|
|
@ -271,12 +339,22 @@ public sealed class UiMeter : UiElement, IUiDatStateful
|
|||
&& ActiveRetailStateId == RetailUiStateIds.ShowDetail;
|
||||
DrawHBar(ctx, resolve, BackLeft, BackTile, BackRight, Width);
|
||||
if (detail)
|
||||
DrawDetailIcon(ctx, resolve, _detailBackSprite, _detailBackRect, Width);
|
||||
{
|
||||
DrawDetailIcon(
|
||||
ctx, resolve, _detailBack.Sprite,
|
||||
ComputeDetailOverlayRect(in _detailBack, Width, Height),
|
||||
Width);
|
||||
}
|
||||
if (pct is not null && p > 0f)
|
||||
{
|
||||
DrawHBar(ctx, resolve, FrontLeft, FrontTile, FrontRight, Width * p);
|
||||
if (detail)
|
||||
DrawDetailIcon(ctx, resolve, _detailFrontSprite, _detailFrontRect, Width * p);
|
||||
{
|
||||
DrawDetailIcon(
|
||||
ctx, resolve, _detailFront.Sprite,
|
||||
ComputeDetailOverlayRect(in _detailFront, Width, Height),
|
||||
Width * p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue