test(vfx): harden live missile and effect lifetimes

Add deterministic 96-owner lifecycle and renderer-resource stress gates plus an exact twelve-cycle recall/portal ownership gate. Prove zero retained records, projectiles, spatial buckets, script queues, particle/light owners, shadows, pending effects, and mesh references after churn, GUID reuse, deletion, and session reset.

Make logical teardown incarnation-specific and reentrancy-safe with lifetime epochs, atomic resource registration, generation-aware effect/teleport cleanup, projection mutation tokens, and failure-isolated visibility fan-out. Finish canonical spatial transactions before reporting observer failures and never discard superseded cleanup failures.

Synchronize architecture, roadmap, milestones, retail research, divergence bookkeeping, and durable memory. All three independent review tracks are clean; Release build and the full 5,454-pass/5-skip suite are green.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-14 15:47:00 +02:00
parent 1e98d81448
commit 8d63e5c28a
21 changed files with 2704 additions and 100 deletions

View file

@ -60,7 +60,7 @@ well-defined interfaces that the retail client never had.
├──────────────────────────────────────────────────────────────┤
│ LAYER 1: Renderer │
│ Silk.NET OpenGL 4.3 core profile │
│ TerrainRenderer, StaticMeshRenderer, TextureCache
│ TerrainModernRenderer, WbDrawDispatcher, EnvCellRenderer
│ Shaders (terrain blending, mesh lighting, translucency) │
│ ► completely different from retail (D3D7), same visual │
│ output │
@ -186,13 +186,14 @@ src/
RemoteTeleportHook.cs -> ordered retail teleport teardown seam
RemoteTeleportPlacement.cs -> collision-seated SetPosition transition commit
World/
LiveEntityRuntime.cs -> canonical logical identity/state/spatial ownership
LiveEntityRuntime.cs -> canonical identity/state/body/projectile/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
StaticMeshRenderer.cs -> done
TerrainModernRenderer.cs -> mandatory bindless+MDI terrain path
Wb/WbDrawDispatcher.cs -> ordinary live/static entity draw dispatch
Wb/EnvCellRenderer.cs -> indoor cell-shell draw path
TextureCache.cs -> done
ChaseCamera.cs -> done
FlyCamera.cs -> done
@ -285,7 +286,12 @@ What exists and is active:
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.
presentation edge. Observer failures are reported only after both spatial and
canonical runtime cell/projection state have committed.
Every projection operation also carries a per-record mutation token; if a
synchronous observer replaces the GUID or reprojects that same record, the
displaced outer operation cannot overwrite the newer cell or presentation
indices.
- `BSPQuery` contains the partial retail-style BSP collision dispatcher used by
the transition path.
- `TransitionTypes` carries `SpherePath`, `CollisionInfo`, `ObjectInfo`,
@ -326,12 +332,20 @@ Ownership by phase:
`LiveEntityRuntime` is now the shipped bridge to this target. It owns one
`LiveEntityRecord` per accepted server-object incarnation, ServerGuid-to-local-id
translation, accepted snapshots/timestamp gates, animation and remote-motion
components, parent-event state, effect-profile defaults, and exact logical
translation, accepted snapshots/timestamp gates, the canonical `PhysicsBody`,
animation/remote-motion/projectile components, parent-event state,
effect-profile defaults, and exact logical
teardown. Live `PhysicsDesc` effect fields replace Setup defaults on that same
record; rebucketing never recreates them. `GpuWorldState`
owns spatial buckets only: register/rebucket/unregister are separate operations,
and landblock reloads reuse the same `WorldEntity` without replaying renderer or
logical teardown removes the exact `WorldEntity` incarnation rather than every
projection sharing its server GUID, and per-GUID/session mutation epochs prevent
a callback from resurrecting an outer CreateObject after delete/reset. Per-GUID
epoch tombstones remain until session clear, so delete-then-create cannot repeat
an outer operation's epoch (the ABA problem). Resource registration is an atomic
boundary, superseded cleanup failures surface at the runtime boundary, and
visibility observers are failure-isolated without interrupting canonical commits.
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. Raw server PhysicsState and the final state produced by retail's
@ -416,7 +430,7 @@ public sealed class GameEntity
// Motion (ported from CMotionInterp)
public MotionInterpreter Motion { get; } // walk/run/turn state
// Render output (consumed by StaticMeshRenderer)
// Render output (consumed by WbDrawDispatcher)
public IReadOnlyList<MeshRef> MeshRefs { get; }
// Per-frame update (matches retail update_object)
@ -467,8 +481,10 @@ matches retail's `CPhysicsObj::DoObjDescChangesFromDefault` behavior.
5. Render tick
└── Read current entity mesh refs, draw
TerrainRenderer.Draw, StaticMeshRenderer.Draw
(frustum cull, translucency pass, etc.)
TerrainModernRenderer + WbDrawDispatcher + EnvCellRenderer
(frustum cull, translucency pass, portal visibility, etc.)
Projectiles remain ordinary live-entity draws; there is no global or
projectile-specific render pass.
6. Plugin tick
└── Fire IEvents, drain IActions queue
@ -484,8 +500,7 @@ matches retail's `CPhysicsObj::DoObjDescChangesFromDefault` behavior.
## Render Pipeline (SSOT — current state + unified-PView target)
> **The per-frame render step above is STALE** (it names deleted classes
> `TerrainRenderer` / `StaticMeshRenderer`). The modern path (Phase N.5, mandatory) is
> The modern path (Phase N.5, mandatory) is
> `WbDrawDispatcher` (entities) + `EnvCellRenderer` (indoor cell shells) +
> `TerrainModernRenderer` (terrain), fed by the portal-visibility stack. This section is
> the authoritative description of how indoor/outdoor rendering is *supposed* to work and