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:
Erik 2026-08-17 12:30:51 +02:00
parent 2f8c046aba
commit fdc4fd496d
17 changed files with 649 additions and 66 deletions

View file

@ -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]