feat(ui): AD-109 — arm the login wormhole at the char-select Enter click

USER-DIRECTED deviation from retail (register row AD-109, same commit):
retail presents the empty pre-player gameplay screen — black behind the
retained UI — from the Enter click (CPlayerSystem::LogOnCharacter
@0x0055F890 -> CM_Login::SendNotice_BeginEnterWorld @0x006AD810, UI mode
0x10000008) until CreatePlayer raises SmartBox::teleport_in_progress
@0x00451C20 and gmSmartBoxUI::UseTime @0x004D6EAB begins TAS_TUNNEL. The
user prefers the tunnel to cover that whole wait.

- ILocalPlayerTeleportNetworkSink.ArmLoginTunnel: begins the login
  wormhole presentation at the Enter click, consuming the sequencer's
  begin-edge events SYNCHRONOUSLY (the Enter command blocks the update
  thread for the whole ServerReady round trip, so a deferred first tick
  would leave exactly the black window this deviation removes). The
  enter cue plays at the click: retail's own rule is cue-at-animation-
  begin (Sound_UI_EnterPortal @0x004D638E, unconditional inside
  BeginTeleportAnimation), and the animation begin moved to the click.
- Armed pre-reveal pump: tunnel animates across the round trip
  (worldReady pinned false, sequencer holds in Tunnel); the hold clock
  accumulates from the click.
- Adoption: the Runtime login reveal ADOPTS the running presentation
  (no re-Begin, no second cue); rejected EnterWorld (lifecycle back to
  AwaitingSelection) disarms and retires the tunnel.
- Wired at the ONE host edge every entry route shares:
  ILiveSessionLifecycleHost.ApplySelectedCharacter (direct connect,
  roster Enter, enter-after-create) via
  LiveSessionSelectionBindings.ArmLoginTunnel (default no-op keeps
  headless and every existing construction site unchanged).
- ILocalPlayerLoginLifecycleSource: typed seam (not a stored delegate —
  the frame-phase owner delegate-field guard) projecting the Runtime
  character-selection lifecycle for the disarm edge.
- Frame contract update: [login-frames] over a login is tunnel -> world
  from the click — no void, and no black between click and world.

Tests: 4 new armed-tunnel tests (arm/adopt/disarm/frame-shape); App
suite live-DAT 5516 passed / 3 skipped (baseline 5512/3 + 4 new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-17 13:42:30 +02:00
parent fdc4fd496d
commit 2bc81480d4
10 changed files with 437 additions and 13 deletions

View file

@ -14,7 +14,16 @@ public sealed record LiveSessionSelectionBindings(
Action<uint> SetChatIdentity,
Action<uint> MarkPersistent,
Action<uint> SetVanishProbeIdentity,
Action ClearCombat);
Action ClearCombat,
/// <summary>Enter-click round (2026-08-17): arms the graphical host's
/// login-wormhole presentation at the selected-character edge — the one
/// host callback every entry route (direct connect, roster Enter,
/// enter-after-create) fires immediately BEFORE the EnterWorld wire
/// send, so the tunnel covers the whole server round-trip (registered
/// user-directed deviation from retail's pre-CreatePlayer black).
/// Default no-op preserves headless and existing construction sites.
/// </summary>
Action? ArmLoginTunnel = null);
public sealed record LiveSessionEnteredWorldBindings(
Action<string> SetActiveCharacter,
@ -252,6 +261,9 @@ public sealed class LiveSessionHost
_selection.MarkPersistent(id);
_selection.SetVanishProbeIdentity(id);
_selection.ClearCombat();
// Enter-click round (2026-08-17): LAST, after identity wiring — the
// armed tunnel's own logging can then already attribute the session.
_selection.ArmLoginTunnel?.Invoke();
}
private void ApplyEnteredWorld(LiveSessionCharacterSelection selection)