fix(world): remove non-retail portal exit fade

This commit is contained in:
Erik 2026-07-15 23:20:52 +02:00
parent 842bd89c16
commit dded9e6b17
21 changed files with 919 additions and 347 deletions

View file

@ -91,9 +91,10 @@ 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`.
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
@ -185,31 +186,35 @@ WorldFadeIn:
-> Off
```
The narrow remaining-time window makes the one-second tunnel fade finish at
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.
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.
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
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
viewPlaneLevel = GetAnimLevel(elapsed / FadeTime) / 1024
WorldFadeOut or TunnelFadeOut:
currentDistance = gameDistance
+ (0.001 - gameDistance) * fadeLevel
+ (0.001 - gameDistance) * viewPlaneLevel
TunnelFadeIn or WorldFadeIn:
currentDistance = 0.001
+ (gameDistance - 0.001) * fadeLevel
+ (gameDistance - 0.001) * viewPlaneLevel
SmartBox.SetOverrideFovDistance(true, currentDistance)
```
@ -221,10 +226,22 @@ 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.
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::GetOverrideFovDistance` `0x00451BE0`: when no override is active,
returns `cot(activeVerticalFov / 2)` (`0x00798088 = 0.5`,
`0x007928C0 = 1.0`).
- `gmSmartBoxUI::UseTime` `0x004D7199` and `0x004D722E`: converts the signed
table result with `0x007BD6A0 = 1/1024` and performs the two lerps above.
- `Render::set_vdst` `0x0054B240`: x87 `fpatan` computes
`atan(1 / distance)`, doubles it, and sets
`znear = max(0.1, distance * 0.25)`.
## Player placement boundary
@ -261,7 +278,7 @@ 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
world view-plane transition while the normal motion/update stream settles. Therefore acdream
must not add an arrival-only animation reset.
## acdream integration translation
@ -272,11 +289,18 @@ must not add an arrival-only animation reset.
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
UI draws. There is no black compositor. 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.
and world viewport during the four `FADE` states.
- 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