# 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` - `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` 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 rotates around AC's vertical `+Z` axis. 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. ## 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() move world viewer to player 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 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. - 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.