feat(vfx): port retail hidden and teleport presentation
Preserve canonical live-object ownership across Hidden transitions and remote teleport placement so effects, collision, streaming, and targeting remain synchronized.
This commit is contained in:
parent
a51ebc66e9
commit
1e98d81448
46 changed files with 4883 additions and 127 deletions
|
|
@ -698,8 +698,110 @@ The recall Animation's direct DAT hooks must be executed rather than replaced
|
|||
with a guessed cloud. Hidden/UnHide state transitions and explicit typed
|
||||
scripts remain distinct packet-driven events. A normal visible spawn alone is
|
||||
not evidence for an UnHide materialization effect; the observer portal-in
|
||||
sequence must be validated against its actual inbound state/effect packets
|
||||
during Step 8.
|
||||
sequence remains a final two-client packet/visual gate.
|
||||
|
||||
### 11.5 Step 8 implementation boundary
|
||||
|
||||
The port keeps raw wire state and side-effect-derived final state separate.
|
||||
`LiveEntityPresentationController` drains accepted state transitions only after
|
||||
the renderer, effect profile, and PhysicsScript owner are ready; pending mixed
|
||||
F754/F755 packets replay after that construction-state drain. Hidden removes
|
||||
the retained entity from drawing, collision, radar, picking, status, and new
|
||||
target acquisition, but does not unregister its record, cell, script queue,
|
||||
particles, lights, or canonical local ID. Attached children receive the exact
|
||||
direct NoDraw mutation.
|
||||
|
||||
The Hidden update is a narrow tick, not a frozen entity. In
|
||||
`CPhysicsObj::UpdatePositionInternal` (`0x00512C30`) Hidden skips PartArray
|
||||
root-motion/animation advancement and `UpdatePhysicsInternal`, but still
|
||||
composes `PositionManager::adjust_offset` and processes timed object hooks.
|
||||
The surrounding `UpdateObjectInternal` (`0x005156B0`) then continues the
|
||||
target, movement, and position-manager tails. A moving Hidden local or remote
|
||||
therefore updates its root/cell and effect-pose snapshot without accepting
|
||||
input, gravity, or ordinary body integration.
|
||||
|
||||
Retail `CObjCell::hide_object` (`0x0052BE30`) also sends DetectionManager
|
||||
`LeftDetection`. DetectionManager is not yet ported, so TS-49 records the one
|
||||
explicit adaptation: acdream sends the existing TargetManager non-Ok
|
||||
availability edge to tear down MoveTo/Sticky consumers and clears the hidden
|
||||
object's watched-role subscriptions.
|
||||
|
||||
Fresh remote `TELEPORT_TS`, or first placement while the canonical cell is
|
||||
zero, follows `MoveOrTeleport` Branch A before its contact test:
|
||||
|
||||
```text
|
||||
CancelMoveTo(0x3c)
|
||||
UnStick
|
||||
StopInterpolating
|
||||
UnConstrain
|
||||
ClearTarget + NotifyVoyeurOfEvent(Teleported)
|
||||
report_collision_end
|
||||
SetPosition the same body at received full cell + frame through CTransition
|
||||
derive Contact/OnWalkable/contact plane and ground-transition state
|
||||
```
|
||||
|
||||
This order is required even for an airborne packet; the ordinary airborne
|
||||
no-op is only a Branch B/C decision and cannot undo a genuine teleport. A
|
||||
canonical nonzero cell whose spatial projection is unavailable is classified
|
||||
as cell-less for this decision: it cannot take the ordinary interpolation path
|
||||
without a resident cell.
|
||||
|
||||
If the destination landblock is pending, `RemoteTeleportController` parks the
|
||||
authoritative frame without contact, retains the original contact edge, and
|
||||
waits for projection visibility. The request is scoped to live generation and
|
||||
accepted PositionSequence. Every newer accepted Position packet refreshes that
|
||||
same request; hydration collision-seats only the latest sequence, while delete,
|
||||
reset, or GUID reuse discards the old incarnation's placement.
|
||||
|
||||
`SetPositionInternal` captures the old Contact/OnWalkable flags at entry. It
|
||||
writes destination Contact while retaining the source OnWalkable bit for the
|
||||
first `calc_acceleration`, then invokes `set_on_walkable` (therefore
|
||||
HitGround/LeaveGround), and only then calls `handle_all_collisions` at
|
||||
`0x005154FE`. Retaining source walkability through that first calculation
|
||||
matters for a grounded source that hydrates onto a steep contact: retail clears
|
||||
grounded angular drift before installing the destination's non-walkable state.
|
||||
The callback may change velocity, so collision response must consume that
|
||||
post-callback velocity while still receiving the captured source flags. This
|
||||
ordering is shared by loaded teleports and local/remote Hidden PositionManager
|
||||
movement. A Hidden projectile that shares the same RemoteMotion body also uses
|
||||
this narrow manager tick once; projectile integration remains paused.
|
||||
|
||||
If projection hydration occurs but placement resolution fails, the visibility
|
||||
edge cannot be treated as a retry clock. The controller restores the captured
|
||||
source body frame, full cell, contact state, and projection, then drops the
|
||||
pending request. A resident source restores its shadow immediately; an unloaded
|
||||
source delegates one generation-scoped restore to
|
||||
`LiveEntityPresentationController`, the same owner used by Hidden/UnHide, until
|
||||
that projection returns. Before any newer accepted placement rebuckets, it
|
||||
transfers the deferred restore into an explicit active-placement generation,
|
||||
including while Hidden. That ownership remains active for the complete pending
|
||||
lifetime, so any number of intervening Hidden/UnHide or visibility edges cannot
|
||||
restore an unresolved pose. Only the controller's stable completion clears it:
|
||||
the placement either restores after successful collision seating, re-defers its
|
||||
captured source after failure, or hands a stable Hidden result back for UnHide.
|
||||
A same-incarnation MovementManager/runtime rebind cannot replace the canonical
|
||||
physics body or discard the typed placement contract. A wrapper replacement
|
||||
around that same body is adopted by the pending request before hydration, so
|
||||
component rebinding cannot strand active-placement ownership. Operational
|
||||
component clearing removes the wrapper, not the incarnation's body identity or
|
||||
placement requirement; only logical teardown releases those invariants. The
|
||||
production wrapper's body is constructor-owned and immutable, and hydration
|
||||
validates both pending/current wrappers against the record's captured body. A
|
||||
malformed dynamic wrapper is detached and the canonical body is rolled back.
|
||||
Every runtime-binding boundary reads the wrapper's Body exactly once, validates
|
||||
that local snapshot, and installs the same reference into the live record.
|
||||
A cell-less source withdraws the projection and publishes the final
|
||||
invisible presentation edge. Spatial rebucketing removes
|
||||
and places atomically, visibility transitions commit their new set first and
|
||||
drain through a non-reentrant FIFO, and `LiveEntityRuntime` rejects delayed
|
||||
duplicates against its last logical state. A rollback triggered by a
|
||||
destination-visible callback therefore cannot expose remove/add pulses or be
|
||||
overwritten by the outer notification.
|
||||
A newer loaded destination that fails placement consumes the original pending
|
||||
request and performs the same rollback. Logical teardown is independently
|
||||
failure-isolated across presentation, effects, teleport, movement/target,
|
||||
shadow, and light owners; session reset clears pending teleport state even when
|
||||
another teardown callback throws.
|
||||
|
||||
## 12. Secondary-reference cross-checks
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue