# Retail portal-space presentation pseudocode Date: 2026-07-15 Scope: the client-side 3-D scene and timing shown while a player is between world positions. This is presentation only. The server remains authoritative for teleport start, destination position, world readiness, and completion. ## Retail oracle Named Sept-2013 retail symbols: - `gmSmartBoxUI::BeginTeleportAnimation` `0x004D6300` - `gmSmartBoxUI::EndTeleportAnimation` `0x004D65A0` - `gmSmartBoxUI::PostInit` `0x004D6B60` - `gmSmartBoxUI::UseTime` `0x004D6E30` - `CreatureMode::CreatureMode` `0x00454390` - `CreatureMode::SetCameraDirection_Degrees` `0x00453760` - `Frame::rotate` `0x004525B0` - `CreatureMode::AddObject` `0x004556D0` - `UIGlobals::Init` `0x004EE470` - `UIGlobals::GetAnimLevel` `0x004EE540` - `CPhysicsObj::set_sequence_animation` `0x0050F6F0` - `SmartBox::TeleportPlayer` `0x00453910` - `SmartBox::PlayerPositionUpdated` `0x00453870` - `CPhysicsObj::teleport_hook` `0x00514ED0` - `CommandInterpreter::PlayerTeleported` `0x006B32B0` - `SmartBox::SetOverrideFovDistance` `0x00451BC0` - `SmartBox::GetOverrideFovDistance` `0x00451BE0` - `Render::set_vdst` `0x0054B240` Constants were checked against static x86 disassembly. The Binary Ninja pseudocode loses several x87 operands in `UseTime`; disassembly is authoritative for those comparisons. ## Asset construction (`gmSmartBoxUI::PostInit`) ```text portalSetupDid = ResolveClientEnum(enumValue=0x10000001, category=7) teleportObj = CPhysicsObj.makeObject(portalSetupDid, noWeenie, makeParts) portalViewport = child 0x10000436 as UIElement_Viewport portalViewport.CreatureMode.AddObject(teleportObj) portalViewport.CreatureMode.AddLight(DISTANT_LIGHT, intensity=2.0) portalViewport.CreatureMode.SetLightDirection(0, (0.3, -1.9, 0.65)) portalViewport.CreatureMode.SetCameraPosition((0.24, -2.7, 0.88)) portalViewport.CreatureMode.UseSmartboxFOV() ``` `CreatureMode` starts with ambient light `(0.3, 0.3, 0.3)` and a 45-degree default FOV, but `UseSmartboxFOV` makes this viewport inherit the active world view's current FOV on every draw. Its identity camera frame looks along AC `+Y`, with `+Z` up. The portal viewport is a replacement for the world viewport. It is not a full-screen UI overlay: normal retained UI continues to update and draw above it. ## Begin/end and scene visibility ```text BeginTeleportAnimation(requestedState): if currentState == Off: gameViewDistance = SmartBox.GetOverrideFovDistance() currentViewDistance = gameViewDistance clear rotation-segment state currentState = requestedState transitionStart = now play centered UI sound Sound_UI_EnterPortal EndTeleportAnimation(): if currentState != Off: currentState = TunnelContinue transitionStart = now SmartBox.SetOverrideFovDistance(disabled) ``` Portal/login/death transit begins directly in `Tunnel`. Logout begins in `WorldFadeOut`. When a tunnel-family state first becomes visible: ```text disable vivid target indicator portalAnimDid = ResolveClientEnum(enumValue=0x10000002, category=7) teleportObj.set_sequence_animation( animation=portalAnimDid, clearExisting=true, lowFrame=1, highFrame=end, fps=40) reset portal camera position to (0.24, -2.7, 0.88) show portal viewport hide world SmartBox viewport ``` At the end of `TunnelFadeOut`, retail restores the world view distance, hides the portal viewport, shows the world viewport, clears the teleport object's sequence animations, plays `Sound_UI_ExitPortal`, and enters `WorldFadeIn`. ## Camera motion The camera rolls around its own AC `+Y` forward axis. It does not yaw around world `+Z`. `SetCameraDirection_Degrees` resets the frame to identity, converts the vector to radians, then passes `(0, angle, 0)` to `Frame::rotate`, whose input is an axis-angle rotation vector. The forward direction therefore stays down the portal passage while local `+Z` up rolls around it. Each segment is independent: ```text if now >= rotationStart + rotationDuration: currentAngle = previousEndAngle rotationStart = now rotationDuration = RandomUniform(0.6, 1.8) seconds startAngle = currentAngle endAngle = RandomUniform(0, 360) degrees display "In Portal Space - Please Wait..." else: t = (now - rotationStart) / rotationDuration level = UIGlobals.GetAnimLevel(t) / 1024 currentAngle = startAngle + (endAngle - startAngle) * level CreatureMode.SetCameraDirection_Degrees((0, currentAngle, 0)) ``` `UIGlobals.GetAnimLevel` is a 100-entry cumulative sine table, not a polynomial smoothstep: ```text for i in 0..99: sample[i] = truncate(sin(i * pi / 99) * 1024) total = sum(sample) running = 0 for i in 0..99: running += sample[i] level[i] = (running << 10) / total // integer division, 0..1024 GetAnimLevel(t): t = clamp(t, 0, 1) index = floor(t * 99) return level[index] ``` ## State timing (`gmSmartBoxUI::UseTime`) Golden data constants: ```text FadeTime = 1.0 seconds (0x007BD278) MinContinue = 2.0 seconds (0x007BD268) MaxContinue = 5.0 seconds (0x007BD270) PortalFPS = 40 frames/s (0x007BD280) EndFrame = 120 ExitWindowLow = FadeTime + 0.1 = 1.1 seconds ExitWindowHigh = FadeTime + 0.3 = 1.3 seconds ``` ```text WorldFadeOut: after 1 second -> TunnelFadeIn TunnelFadeIn: after 1 second -> Tunnel Tunnel: hold until SmartBox teleport-in-progress clears EndTeleportAnimation -> TunnelContinue TunnelContinue: elapsed = now - transitionStart if elapsed >= 2 seconds: remaining = unsigned(120 - teleportObj.currentFrame) / 40 if elapsed >= 5 seconds or 1.1 < remaining < 1.3 seconds: -> TunnelFadeOut TunnelFadeOut: after 1 second: hide/clear portal scene show world play exit sound -> WorldFadeIn WorldFadeIn: after 1 second: send LoginComplete clear teleport-in-progress -> Off ``` The narrow remaining-time window makes the one-second tunnel fade finish at the end of the 120-frame animation. The five-second ceiling is retail's escape for a missed window. World/tunnel fades use `GetAnimLevel(elapsed / FadeTime)`. The fade belongs between the 3-D viewport and the retained UI. It must never cover or disable the retained UI. ## View-plane warp Retail couples every fade state to a projection transition. On begin it captures the current SmartBox view-plane distance; for a perspective projection this is `cot(verticalFov / 2)`. The transition endpoint is the named constant `TRANSITION_VIEW_PLANE_DISTANCE = 0.001` at `0x007BD260`. ```text fadeLevel = GetAnimLevel(elapsed / FadeTime) / 1024 WorldFadeOut or TunnelFadeOut: currentDistance = gameDistance + (0.001 - gameDistance) * fadeLevel TunnelFadeIn or WorldFadeIn: currentDistance = 0.001 + (gameDistance - 0.001) * fadeLevel SmartBox.SetOverrideFovDistance(true, currentDistance) ``` `Render::set_vdst` turns the distance back into projection values: ```text verticalFov = 2 * atan(1 / currentDistance) znear = max(0.1, currentDistance * 0.25) ``` At `0.001`, the view is nearly 180 degrees wide. Black covers the singular endpoint; as `WorldFadeIn` clears, the FOV eases back to the captured game projection. This is retail's characteristic destination-world warp, separate from both the alpha fade and chase-camera movement. ## Player placement boundary ```text SmartBox.TeleportPlayer(position): player.SetPositionSimple(position, slide=true) PlayerPositionUpdated(isTeleport=true) PlayerPositionUpdated(isTeleport=true): clear position-update/teleport bookkeeping player.teleport_hook() commandInterpreter.PlayerTeleported() SmartBox.set_viewer(player.position, reset_sought=true) update CellManager position player.teleport_hook(): MovementManager.CancelMoveTo(error=0x3c) PositionManager.UnStick() PositionManager.StopInterpolating() PositionManager.UnConstrain() TargetManager.ClearTarget() TargetManager.NotifyVoyeurOfEvent(Teleported) report_collision_end(force=true) PlayerTeleported(): SetAutoRun(false, send=true) SendMovementEvent() ``` The viewer reset copies the player's complete position into both `viewer` and `viewer_sought_position`. Subsequent ordinary camera updates extend the chase boom outward through the same damped and swept path used after camera collision. Reusing the source-world viewer is not retail behavior. The retail teleport hook does not clear the character's animation sequence. The arrival presentation is instead hidden by the portal scene and its final world fade while the normal motion/update stream settles. Therefore acdream must not add an arrival-only animation reset. ## acdream integration translation - A dedicated App-layer portal presentation owns the synthetic portal object, animation sequence, camera, scene light, resource references, and draw pass. - The existing pure teleport state machine remains the lifecycle owner, but it consumes the portal object's current frame for retail exit timing and uses the exact table-driven easing curve. - During tunnel states the portal scene replaces world pixels before retained UI draws. The black fade also draws before retained UI. Input dispatch and `RetailUiRuntime.Tick` continue unchanged. - The projection owner captures the active camera's M22 view-plane distance at teleport begin and applies `Render::set_vdst` semantics to both the tunnel and world viewport during the four fade states. - Destination placement resets the retail chase camera's published and sought positions to the player before the normal update path resumes. - Destination residency remains acdream's asynchronous adaptation. It supplies the same `EndTeleportAnimation` edge retail receives when its blocking cell load completes; it does not alter presentation ordering. - The portal object is synthetic and never enters `LiveEntityRuntime`, world picking, collision, radar, or server GUID ownership. ## Cross-reference notes - `references/WorldBuilder` supplies the extracted setup/GfxObj mesh and modern draw pipeline used by the synthetic scene; it has no teleport lifecycle. - Holtburger's network study (`docs/research/2026-05-10-holtburger-network-stack-study.md`) corroborates the LoginComplete wire acknowledgement, but intentionally has no retail UI/CreatureMode implementation. The named retail client is therefore the presentation oracle.