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
|
|
@ -72,6 +72,88 @@ public class VitalsDetailToggleTests
|
|||
Assert.Equal((66f, 0f, 18f, 16f), health.DetailBackRect);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VitalsTree_OverlaysCarryTheAuthoredCenterAnchors()
|
||||
{
|
||||
// The 0x100004A9 overlays author LeftEdge=3 / RightEdge=3 — retail's
|
||||
// CENTER anchor modes (UIElement::UpdateForParentSizeChange
|
||||
// @ 0x004627ca / 0x00462827) — with Top=1/Bottom=1, inside the
|
||||
// 150x16 slice containers. These are what keep the icons centered
|
||||
// when the vitals window resizes the meters (2026-08-17 gate:
|
||||
// absorbed overlays previously kept only the authored rect, pinning
|
||||
// the icon at its authored X on any wider meter).
|
||||
var layout = FixtureLoader.LoadVitals();
|
||||
foreach (uint meterId in new[]
|
||||
{
|
||||
VitalsController.Health,
|
||||
VitalsController.Stamina,
|
||||
VitalsController.Mana,
|
||||
})
|
||||
{
|
||||
var m = Assert.IsType<UiMeter>(layout.FindElement(meterId));
|
||||
foreach (UiMeterDetailOverlaySpec overlay in new[] { m.DetailBack, m.DetailFront })
|
||||
{
|
||||
Assert.Equal(3u, overlay.LeftMode);
|
||||
Assert.Equal(1u, overlay.TopMode);
|
||||
Assert.Equal(3u, overlay.RightMode);
|
||||
Assert.Equal(1u, overlay.BottomMode);
|
||||
Assert.Equal(150f, overlay.ParentW);
|
||||
Assert.Equal(16f, overlay.ParentH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Icon placement: authored at authored size, centered on resize ────
|
||||
|
||||
[Fact]
|
||||
public void DetailIcon_AuthoredRectStandsAtTheAuthoredMeterSize()
|
||||
{
|
||||
// Retail reflows a child ONLY when its parent actually resizes; at
|
||||
// the authored size the designer's hand-placed rect stands — the
|
||||
// stamina sword authors X=32 where the mode-3 center formula yields
|
||||
// 33, so recomputing unconditionally would shift the un-resized
|
||||
// window by a pixel.
|
||||
var sword = new UiMeterDetailOverlaySpec(
|
||||
0x06007492u, 32f, 0f, 85f, 16f, 3u, 1u, 3u, 1u, 150f, 16f);
|
||||
Assert.Equal(
|
||||
(32f, 0f, 85f, 16f),
|
||||
UiMeter.ComputeDetailOverlayRect(in sword, 150f, 16f));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// Heart 18x16: near = W/2 - 9, far = W/2 + 9 - 1 -> width preserved.
|
||||
[InlineData(66f, 18f, 300f, 141f, 18f)]
|
||||
[InlineData(66f, 18f, 220f, 101f, 18f)]
|
||||
// Scepter 100x16: near = W/2 - 50.
|
||||
[InlineData(25f, 100f, 300f, 100f, 100f)]
|
||||
public void DetailIcon_CentersOnAWiderMeter(
|
||||
float authoredX, float authoredW, float meterW,
|
||||
float expectedX, float expectedW)
|
||||
{
|
||||
var overlay = new UiMeterDetailOverlaySpec(
|
||||
0x06007490u, authoredX, 0f, authoredW, 16f, 3u, 1u, 3u, 1u, 150f, 16f);
|
||||
(float x, float y, float w, float h) =
|
||||
UiMeter.ComputeDetailOverlayRect(in overlay, meterW, 16f);
|
||||
Assert.Equal(expectedX, x);
|
||||
Assert.Equal(0f, y);
|
||||
Assert.Equal(expectedW, w);
|
||||
Assert.Equal(16f, h);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetailIcon_OddWidthLosesOnePixelExactlyLikeRetail()
|
||||
{
|
||||
// Sword 85px: retail's integer halves (>>1) give near = 150 - 42 =
|
||||
// 108 and far = 150 + 42 - 1 = 191 on a 300px meter -> width 84,
|
||||
// one pixel narrower than authored. Faithful, not a bug.
|
||||
var sword = new UiMeterDetailOverlaySpec(
|
||||
0x06007492u, 32f, 0f, 85f, 16f, 3u, 1u, 3u, 1u, 150f, 16f);
|
||||
(float x, float _, float w, float _) =
|
||||
UiMeter.ComputeDetailOverlayRect(in sword, 300f, 16f);
|
||||
Assert.Equal(108f, x);
|
||||
Assert.Equal(84f, w);
|
||||
}
|
||||
|
||||
// ── The press toggle ─────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue