12 KiB
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::BeginTeleportAnimation0x004D6300gmSmartBoxUI::EndTeleportAnimation0x004D65A0gmSmartBoxUI::PostInit0x004D6B60gmSmartBoxUI::UseTime0x004D6E30CreatureMode::CreatureMode0x00454390CreatureMode::SetCameraDirection_Degrees0x00453760Frame::rotate0x004525B0CreatureMode::AddObject0x004556D0UIGlobals::Init0x004EE470UIGlobals::GetAnimLevel0x004EE540CPhysicsObj::set_sequence_animation0x0050F6F0SmartBox::TeleportPlayer0x00453910SmartBox::PlayerPositionUpdated0x00453870CPhysicsObj::teleport_hook0x00514ED0CommandInterpreter::PlayerTeleported0x006B32B0SmartBox::SetOverrideFovDistance0x00451BC0SmartBox::GetOverrideFovDistance0x00451BE0Render::set_vdst0x0054B240
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)
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
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:
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 commits the current transition view
distance, hides the portal viewport, shows the world viewport, clears the
teleport object's sequence animations, plays Sound_UI_ExitPortal, and enters
WorldFadeIn. That following state restores the ordinary game view distance.
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:
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:
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:
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
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 projection transition finish at the end of the 120-frame animation. The five-second ceiling is retail's escape for a missed window.
The four states whose retail enum names contain FADE use
GetAnimLevel(elapsed / FadeTime). Instruction-level x86 inspection of
gmSmartBoxUI::UseTime (0x004D7133..0x004D725F) shows that this value feeds
only SmartBox::SetOverrideFovDistance. The function performs no alpha or
black-quad draw; FADE names the view-plane transition, not a transparency
compositor. The retained UI remains independent because only the two 3-D
viewports are switched.
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.
viewPlaneLevel = GetAnimLevel(elapsed / FadeTime) / 1024
WorldFadeOut or TunnelFadeOut:
currentDistance = gameDistance
+ (0.001 - gameDistance) * viewPlaneLevel
TunnelFadeIn or WorldFadeIn:
currentDistance = 0.001
+ (gameDistance - 0.001) * viewPlaneLevel
SmartBox.SetOverrideFovDistance(true, currentDistance)
Render::set_vdst turns the distance back into projection values:
verticalFov = 2 * atan(1 / currentDistance)
znear = max(0.1, currentDistance * 0.25)
At 0.001, the view is nearly 180 degrees wide. Retail does not cover that
endpoint with black: it switches directly from the tunnel viewport to the
destination world at this projection, then WorldFadeIn expands the world
from the center as the captured game projection is restored. This is retail's
characteristic destination-world warp, independent of chase-camera movement.
Instruction-level checks that disambiguate the Binary Ninja x87 output:
SmartBox::GetOverrideFovDistance0x00451BE0: when no override is active, returnscot(activeVerticalFov / 2)(0x00798088 = 0.5,0x007928C0 = 1.0).gmSmartBoxUI::UseTime0x004D7199and0x004D722E: converts the signed table result with0x007BD6A0 = 1/1024and performs the two lerps above.Render::set_vdst0x0054B240: x87fpatancomputesatan(1 / distance), doubles it, and setsznear = max(0.1, distance * 0.25).
Player placement boundary
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 view-plane transition 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. There is no black compositor. Input dispatch and
RetailUiRuntime.Tickcontinue unchanged. - The projection owner captures the active camera's M22 view-plane distance at
teleport begin and applies
Render::set_vdstsemantics to both the tunnel and world viewport during the fourFADEstates. - F751 remains a notification gate and does not itself advance
TELEPORT_TS. Presentation correlates its sequence with exactly one accepted destination Position. A Position that arrives before F751 is buffered only when it advances that timestamp; after F751, the first accepted matching Position is consumed even if the channel already advanced. Thus reordered delivery and retransmitted notifications cannot materialize the wrong destination or strand the transit, while canonical physics retains its own retail gates. - 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
EndTeleportAnimationedge 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/WorldBuildersupplies 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.