fix(runtime): align portal and movement presentation

Port retail portal viewport projection and reveal behavior, preserve outbound combat style, drive remote and local grounded movement from authored CSequence root frames, and reuse the local prepared pose so animation hooks advance once.

User-verified portal, observer movement, combat stance, and short-tap locomotion gates. Release build passed with 5,767 tests and five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 08:48:27 +02:00
parent e95f55f25b
commit 124e046976
30 changed files with 1362 additions and 348 deletions

View file

@ -232,6 +232,100 @@ 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.
`CreatureMode::Render @ 0x004529D0` changes the portal viewport's FOV through
the SmartBox path but does not install a private far clip. The resulting
`PrimD3DRender::SetFOVInternal @ 0x0059AB40` projection uses the shared
`Render::zfar` (default `4000` at `0x0081EC88`). The portal CreatureMode must
therefore inherit the active SmartBox projection's near and far planes rather
than installing a private short clip range.
### Portal viewport buffer lifecycle
The portal tunnel is rendered by `UIViewportObject::DrawContent @ 0x00695030`.
Before calling `CreatureMode::Render`, it executes:
```text
SetViewport(portal bounds)
RenderDevice.Clear(flags = 4, color = black, depth = 1.0)
CreatureMode.Render()
restore previous viewport and matrices
```
`RenderDeviceD3D::Clear @ 0x0059FD30` maps retail clear flags explicitly:
```text
flag 1 -> D3DCLEAR_TARGET
flag 2 -> D3DCLEAR_STENCIL, when a stencil buffer exists
flag 4 -> D3DCLEAR_ZBUFFER
```
Therefore the portal viewport clears **depth only**. The black color argument
is not used for flag 4. This does not preserve an earlier swap-chain image:
`Client::UseTime @ 0x00411C40` begins the complete frame through
`SceneTool::BeginScene @ 0x0043DAD0`, whose `RenderDevice.Clear(flags=7,
black, depth=1)` clears color, depth, and stencil. The later viewport clear
retains that frame's fresh black color target while resetting depth for the
portal CreatureMode.
`gmSmartBoxUI::UseTime @ 0x004D6E30` swaps visibility between the normal
SmartBox and the portal `UIElement_Viewport`. The world viewport is therefore
not redrawn behind portal space; the portal mesh is drawn over the whole-frame
black target, never over the destination world or stale tunnel history.
The faithful presentation rule is:
```text
at the beginning of every frame:
clear color, depth, and stencil to black
while the portal viewport is visible:
do not draw the normal world viewport
clear depth to 1.0
draw portal CreatureMode over this frame's black color target
```
One high-refresh presentation edge remains. `UIGlobals::GetAnimLevel` yields
1022 at table index 96, 1023 at index 97, and 1024 at indices 98 and 99. The
2013 client capture presents 1022 as the last outgoing viewport sample and
does not present the finite tunnel at 1023/1024 before switching viewports.
With the world pass suppressed, acdream runs portal space at roughly 2000 FPS
and can publish those sub-20.2 ms samples; the desktop compositor may then
hold one for a complete monitor refresh and expose the finite tunnel boundary.
The frame-rate-independent presentation adaptation is therefore:
```text
if state is WorldFadeOut or TunnelFadeOut
and GetAnimLevel(elapsed / FadeTime) > 1022:
perform the normal state transition before publishing the draw snapshot
```
This advances only the outgoing presentation edge by at most two table quanta
(about 20.2 ms) and is recorded as AD-38. Incoming fades retain the literal
timer.
### Shared sky and landscape projection during destination reveal
`SmartBox::RenderNormalMode @ 0x00453AA0` installs the active SmartBox
view-plane/FOV through `Render::set_vdst` or `Render::SetFOVRad` before calling
`LScape::draw @ 0x00506330`. `GameSky::Draw @ 0x00506FF0` then saves
`Render::zfar`, sets it to four times the active value for the sky draw, and
restores it. It does **not** install an independent sky FOV.
The destination world is first revealed at a nearly 180-degree projection.
Sky and landscape must therefore preserve the same horizontal/vertical
projection scales and off-center terms. Only the sky's near/far depth mapping
may differ. acdream's former fixed 60-degree sky projection left part of the
frame covered only by the clear/fog background while terrain used the teleport
projection; this was the brief background flash at tunnel exit.
```text
activeProjection = SmartBox view-plane projection
skyProjection = activeProjection
skyProjection.depthRange = authored sky near/far range
draw sky
draw landscape with activeProjection
```
Instruction-level checks that disambiguate the Binary Ninja x87 output:
- `SmartBox::GetOverrideFovDistance` `0x00451BE0`: when no override is active,