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:
Erik 2026-07-14 14:59:48 +02:00
parent a51ebc66e9
commit 1e98d81448
46 changed files with 4883 additions and 127 deletions

View file

@ -181,6 +181,14 @@ src/
AcDream.App/ Layer 1 + Layer 4 wiring
Physics/
ProjectileController.cs -> live-record projectile orchestration/corrections
RemotePhysicsUpdater.cs -> ordinary/Hidden remote narrow-tick integration
RemoteTeleportController.cs -> incarnation-scoped loaded/pending placement owner
RemoteTeleportHook.cs -> ordered retail teleport teardown seam
RemoteTeleportPlacement.cs -> collision-seated SetPosition transition commit
World/
LiveEntityRuntime.cs -> canonical logical identity/state/spatial ownership
LiveEntityPresentationController.cs -> Hidden/NoDraw/effect/collision presentation
LiveEntityTeardown.cs -> failure-isolated multi-owner lifecycle drain
Rendering/
GameWindow.cs -> still owns too much runtime wiring
TerrainRenderer.cs -> done
@ -248,6 +256,36 @@ What exists and is active:
owns the frame;
delete, generation replacement, pickup/parent leave-world, and session reset
use `LiveEntityRuntime`'s normal lifecycle and never create a second GUID map.
- `RemoteTeleportController` owns the placement half of a fresh remote
teleport after `RemoteTeleportHook` has torn down movement/target state. It
collision-seats loaded destinations through `RemoteTeleportPlacement`; an
unloaded destination retains one generation- and PositionSequence-scoped
pending placement and resolves the latest accepted frame when that same
projection becomes visible. It neither owns GUID identity nor reconstructs
an entity. A placement failure after hydration restores the captured source
frame/cell/contact rather than leaving a visible collisionless projection;
source-resident shadows restore immediately, while an unloaded source
delegates one incarnation-scoped restore to
`LiveEntityPresentationController`, shared with Hidden/UnHide. A newer
placement transfers that marker into an explicit active-placement generation
before rebucketing even while Hidden. That generation suppresses every
intervening Hidden/UnHide and projection restore until the controller reaches
a stable result, then it either restores after collision seating, re-defers
its rollback source, or hands a Hidden result back for UnHide. The typed
`ILiveEntityRemotePlacementRuntime` seam permits a same-incarnation wrapper
rebind only around the canonical body; pending placement adopts that wrapper
before hydration and cannot silently lose ownership. Clearing a motion or
projectile component retains the incarnation's body/contract identity until
logical teardown. The production wrapper exposes an immutable body, while
hydration defensively validates arbitrary implementations against the record
and rolls the retained body back on mismatch. Runtime binding snapshots the
interface Body getter once for validation, assignment, and state mutation.
`GpuWorldState`
rebuckets atomically and commits spatial visibility before draining its
transition FIFO; `LiveEntityRuntime` rejects delayed duplicate edges. A
rollback/rebucket inside a visibility observer therefore cannot expose a
remove/add pulse, leave stale final visibility, or reorder the final
presentation edge.
- `BSPQuery` contains the partial retail-style BSP collision dispatcher used by
the transition path.
- `TransitionTypes` carries `SpherePath`, `CollisionInfo`, `ObjectInfo`,
@ -296,7 +334,15 @@ owns spatial buckets only: register/rebucket/unregister are separate operations,
and landblock reloads reuse the same `WorldEntity` without replaying renderer or
script creation. Its canonical materialized view remains stable across pending
landblocks, while a separate visible-only view feeds radar, picking, status, and
targeting. Pickup/parent leave-world clears cell membership and pauses root
targeting. Raw server PhysicsState and the final state produced by retail's
ordered Lighting/NoDraw/Hidden side effects are stored separately.
`LiveEntityPresentationController` drains those accepted transitions only after
the renderer/effect owner is ready: Hidden suppresses the retained root's mesh,
collision, interaction, radar, and target eligibility while preserving the
logical record, scripts, particles, lights, full cell, and local identity.
Direct attached children receive retail's NoDraw mutation, and typed
Hidden/UnHide effects resolve through the live PhysicsScriptTable.
Pickup/parent leave-world clears cell membership and pauses root
movement/animation without destroying retained owners. `GameWindow` retains
storage-free typed views while its large feature loops are extracted.