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

@ -1631,6 +1631,98 @@ public sealed class LocalPlayerTeleportControllerTests
Assert.Equal(loginCompletes, harness.Session.LoginCompleteCount);
}
// ── Gate-fix round (2026-08-17): void frames at the wormhole edges ──
[Fact]
public void LoginPlaceEdge_ReleasesWorldSimulationWhileTunnelInFront()
{
var harness = new Harness(worldReady: false);
harness.Reveal.BeginLogin(0x20210001u);
harness.Controller.Tick(0.016f);
harness.Presentation.Enqueue(TeleportAnimEvent.EnterTunnel);
harness.Controller.Tick(0.016f);
Assert.True(harness.Presentation.IsPortalViewportVisible);
Assert.False(harness.Transit.IsWorldSimulationAvailable);
// The hold ends: destination readiness + canonical first placement.
harness.WorldReady = true;
harness.Controller.OnLocalPlayerFirstEntryCompleted();
harness.Controller.Tick(0.016f);
// The login Place edge is retail's blocking-ends edge —
// CObjectMaint/CPhysics resume (SmartBox::UseTime @ 0x00455410)
// while the tunnel is STILL in front. The world generation must be
// available before the viewport swap so WorldFadeIn's first frame
// draws the world instead of an empty void.
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
harness.Controller.Tick(0.016f);
Assert.True(harness.Presentation.IsPortalViewportVisible);
Assert.True(harness.Transit.IsWorldSimulationAvailable);
Assert.True(harness.Transit.Snapshot.Materialized);
Assert.False(harness.Transit.Snapshot.Completed);
Assert.False(harness.Placement.Called);
// Swap edge: the tunnel retires on this same tick and the world is
// already available — no frame can exist where neither presents.
harness.Presentation.Enqueue(TeleportAnimEvent.PlayExitSound);
harness.Controller.Tick(0.016f);
Assert.False(harness.Presentation.IsPortalViewportVisible);
Assert.True(harness.Transit.IsWorldSimulationAvailable);
// The pump completes exactly as before.
harness.Presentation.Enqueue(TeleportAnimEvent.FireLoginComplete);
harness.Controller.Tick(0.016f);
Assert.Equal(1, harness.Session.LoginCompleteCount);
Assert.True(harness.Reveal.Snapshot.Completed);
}
[Fact]
public void LoginCover_PresentsPortalViewportShapeUntilChaseModeEntered()
{
// Entry-edge contract: retail's pre-player gameplay screen draws NO
// world (black behind the UI) between char-select Enter and
// CreatePlayer (CPlayerSystem::LogOnCharacter @ 0x0055F890 queues
// game mode immediately; SmartBox::teleport_in_progress
// @ 0x00451C20 stays low until the player exists). The composed
// render-state source must therefore present the portal-viewport
// frame shape for every live pre-world frame — the sky-only void
// backdrop must never present.
var harness = new Harness(worldReady: false);
var login = new StubLoginState { IsWaitingForLogin = true };
var source = new LocalPlayerTeleportRenderStateSource(
harness.Controller, login);
// Pre-reveal: no tunnel scene, but the login cover holds the frame
// black.
Assert.False(harness.Presentation.IsPortalViewportVisible);
Assert.True(source.IsPortalViewportVisible);
// Tunnel armed (the activation tick flips ChaseModeEverEntered and
// the tunnel visibility together, before the next render): the
// cover hands off to the tunnel with no gap.
harness.Reveal.BeginLogin(0x20210001u);
harness.Controller.Tick(0.016f);
harness.Presentation.Enqueue(TeleportAnimEvent.EnterTunnel);
harness.Controller.Tick(0.016f);
login.IsWaitingForLogin = false;
Assert.True(source.IsPortalViewportVisible);
// Swap edge: tunnel retires -> the world frame presents (the cover
// stays off; ChaseModeEverEntered never reverts mid-session).
harness.WorldReady = true;
harness.Controller.OnLocalPlayerFirstEntryCompleted();
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
harness.Controller.Tick(0.016f);
harness.Presentation.Enqueue(TeleportAnimEvent.PlayExitSound);
harness.Controller.Tick(0.016f);
Assert.False(source.IsPortalViewportVisible);
}
private sealed class StubLoginState : IRenderLoginStateSource
{
public bool IsWaitingForLogin { get; set; }
}
private sealed class FakePresentation : ILocalPlayerTeleportPresentation
{
private readonly List<string> _order;