feat(world): port retail portal-space viewport

Replace the opaque teleport cover with the DAT-authored CreatureMode tunnel, exact retail camera/light/animation timing and easing, and animation-hook audio routing. Compose portal and fade passes below retained UI so gameplay windows and input remain active through F751 travel.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-15 21:59:23 +02:00
parent 0eab7497c1
commit eab23cbdd1
14 changed files with 983 additions and 45 deletions

View file

@ -221,7 +221,8 @@ public sealed class GameWindow : IDisposable
// each frame on an indoor root (null on the outdoor root).
private AcDream.App.Rendering.RetailPViewRenderer? _retailPViewRenderer;
private AcDream.App.Rendering.PortalDepthMaskRenderer? _portalDepthMask;
private AcDream.App.Rendering.FadeOverlay? _fadeOverlay; // teleport fade cover (spec C)
private AcDream.App.Rendering.PortalTunnelPresentation? _portalTunnel;
private AcDream.App.Rendering.FadeOverlay? _fadeOverlay;
private AcDream.App.Rendering.InteriorEntityPartition.Result? _interiorPartition;
// Phase U.3: the shared per-frame clip data (binding=2 mesh SSBO + terrain
@ -2588,6 +2589,14 @@ public sealed class GameWindow : IDisposable
// DrawPortalPolyInternal (Ghidra 0x0059bc90).
_portalDepthMask = new AcDream.App.Rendering.PortalDepthMaskRenderer(_gl);
_fadeOverlay = new AcDream.App.Rendering.FadeOverlay(_gl);
_portalTunnel = AcDream.App.Rendering.PortalTunnelPresentation.TryCreate(
_gl,
_dats!,
_animLoader!,
_hookRouter,
_wbDrawDispatcher,
_sceneLightingUbo!,
_wbMeshAdapter!);
}
// Phase G.1 sky renderer — its own shader (sky.vert / sky.frag)
@ -8872,9 +8881,9 @@ public sealed class GameWindow : IDisposable
// destination landblock this frame) and the live-session drain, so a destination
// that became resident this frame materializes the same frame. The TAS holds at
// Tunnel until worldReady, fires Place (materialize, hidden), then after the min-
// continue + fades fires FireLoginComplete (regain control + ack). The fade overlay
// is opaque black during the tunnel states (we render a fade, not the 3D swirl) and
// ramps the world back in on WorldFadeIn.
// continue + fades fires FireLoginComplete (regain control + ack). PortalTunnelPresentation
// replaces the world viewport through the tunnel-family states; FadeOverlay transitions
// between the two viewports below the retained UI.
if (_teleportInProgress)
{
bool haveDest = _pendingTeleportCell != 0u;
@ -8889,8 +8898,14 @@ public sealed class GameWindow : IDisposable
}
}
var (snap, evts) = _teleportAnim.Tick((float)dt, ready);
_teleportFadeAlpha = snap.ShowTunnel ? 1f : snap.FadeAlpha;
int tunnelFrame = _portalTunnel?.CurrentAnimationFrame ?? 0;
var (snap, evts) = _teleportAnim.Tick((float)dt, ready, tunnelFrame);
// The fade is a viewport transition below retained UI. If the
// installed DATs are corrupt and the retail portal scene could
// not be built, retain an opaque world cover during transit.
_teleportFadeAlpha = _portalTunnel is null && snap.ShowTunnel
? 1f
: snap.FadeAlpha;
foreach (var e in evts)
{
@ -8904,6 +8919,12 @@ public sealed class GameWindow : IDisposable
_streamingController.PriorityRadius = 0;
}
break;
case AcDream.Core.World.TeleportAnimEvent.EnterTunnel:
_portalTunnel?.Enter();
break;
case AcDream.Core.World.TeleportAnimEvent.PlayExitSound:
_portalTunnel?.Exit();
break;
case AcDream.Core.World.TeleportAnimEvent.FireLoginComplete:
if (_playerController is not null)
_playerController.State = AcDream.App.Input.PlayerState.InWorld;
@ -8916,10 +8937,14 @@ public sealed class GameWindow : IDisposable
_teleportFadeAlpha = 0f;
break;
default:
// PlayEnterSound / EnterTunnel / PlayExitSound — audio polish deferred.
// The retained audio engine does not yet own the
// ClientUISystem sound-table enum path. Enter/exit
// visual edges are handled above.
break;
}
}
_portalTunnel?.Tick((float)dt);
}
// Phase K.1a — tick the input dispatcher so Hold-type bindings
@ -10455,6 +10480,16 @@ public sealed class GameWindow : IDisposable
SkipWorldGeometry: ;
}
// Retail gmSmartBoxUI swaps the world SmartBox viewport for a
// CreatureMode portal-space viewport. This replacement scene and
// its black transition draw BEFORE retained UI, so chat, windows,
// cursor, and toolbar remain visible and interactive in transit.
_portalTunnel?.Draw(
_window!.Size.X,
_window.Size.Y,
_cameraController!.Active.Projection);
_fadeOverlay?.Draw(_teleportFadeAlpha);
// Phase D.2b Sub-phase C Slice 2 — paperdoll 3-D doll: render the re-dressed player clone into the
// viewport's off-screen texture BEFORE the 2-D UI pass blits it, but only when the inventory window
// is open AND the paperdoll is in doll-view (the widget's Visible is driven by the Slots toggle).
@ -10481,11 +10516,6 @@ public sealed class GameWindow : IDisposable
_retailUiRuntime.Draw(new System.Numerics.Vector2(_window!.Size.X, _window.Size.Y));
}
// Teleport fade cover (retail-teleport spec C). Drawn AFTER the world + retail UI so
// it covers them during a transit; the ImGui devtools below composite on top so they
// stay visible for debugging. Alpha is 0 outside a teleport → Draw is a no-op.
_fadeOverlay?.Draw(_teleportFadeAlpha);
// Phase D.2a — end ImGui frame. Runs AFTER all scene + debug draws
// so ImGui composites on top. ImGuiController save/restores the
// GL state it touches (blend, scissor, VAO, shader, texture); any
@ -14649,6 +14679,8 @@ public sealed class GameWindow : IDisposable
_effectPoses.Clear();
}
_audioEngine?.Dispose(); // Phase E.2: stop all voices, close AL context
_portalTunnel?.Dispose();
_portalTunnel = null;
_wbDrawDispatcher?.Dispose();
_envCellRenderer?.Dispose(); // Phase A8
_portalDepthMask?.Dispose(); // T1