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

@ -1076,3 +1076,96 @@ publishes final part poses later in the same frame. PhysicsScript/particle/light
ticks therefore observe the projectile's current predicted root. The controller never
creates impact effects, collision messages, damage, or deletion; ACE's later
packets remain authoritative for every gameplay outcome.
## Step 9 — deterministic lifecycle-hardening map (2026-07-14)
Step 9 adds no new AC algorithm and therefore no replacement retail formula.
It verifies that the mechanisms above compose without retaining a displaced
object incarnation. The acceptance oracle is ownership symmetry:
```text
create 96 live missile/effect owners at a fixed clock and RNG seed
queue F754 before CreateObject
register one canonical live record/local ID
materialize through the ordinary live-entity resource seam
bind the projectile to that record's canonical PhysicsBody
register its collision shadow, effect profile, pose, emitter, and light
advance all owners together
execute the initial PES hook and retain a future hook in each serial FIFO
tick the ordinary ProjectileController path
never create a projectile-specific renderer registration or draw pass
churn spatial residence
rebucket half loaded -> pending -> loaded
unload/reload that destination landblock
retain the exact local ID, body, renderer owner, effect owner, and FIFO
pause/resume particle presentation and dynamic lights at projection edges
churn logical lifetime
rapidly delete 32 owners
recreate the same server GUIDs with a newer INSTANCE_TS
prove the replacement owns new local IDs and no prior-generation resource
mix individual deletes with session reset and never-created pending F754
terminal assertions
zero live/materialized records and null body/projectile/effect/world refs
zero loaded/pending/rescued live projection references
zero render owners and balanced EntitySpawnAdapter mesh refcounts
zero active, retained, and suspended collision-shadow registrations
zero ready effect profiles and pending F754/F755 packets
zero PhysicsScript owner queues, anchors, and delayed CallPES hooks
zero particle emitters/particles/bindings/logical IDs/owner/render-pass maps
zero effect poses and light controller/sink/manager owner maps
repeat the recall/portal lifecycle
resolve exact action motion 0x10000153 through an in-memory MotionTable
drain its animation CallPES through the shared router and serial scheduler
apply Hidden, park authoritative placement in an unloaded destination
hydrate and collision-seat that same body, then apply UnHide
repeat twelve times while retaining one local ID and one logical owner
finish with zero placement, presentation, script, particle, pose, and shadow owners
```
The stress design exposed five ownership defects rather than masking them:
1. `GpuWorldState::RemoveLandblock` can rescue a persistent player projection
before the next frame drains it. Rebucket/delete now scrubs an undrained
rescue reference, while the session-scoped persistent GUID classification
deliberately survives same-GUID generation replacement and clears only at
accepted delete/session teardown.
2. Spatial visibility callbacks are serialized and can be re-entrant. An old
queued GUID edge is now ignored unless it still equals current spatial truth,
so deleting/recreating a GUID inside an observer cannot hide or re-register
the replacement incarnation. Each rebucket/withdrawal also captures the
record reference plus a projection mutation token; an observer that replaces
the GUID or reprojects the same record supersedes the outer transaction.
3. Logical delete and generation replacement previously tore down spatial and
runtime indices by GUID after arbitrary callbacks. Teardown now captures and
removes the accepted record before callbacks, removes its exact WorldEntity
reference, and clears generation/local-ID owners only when they still belong
to that record. A per-GUID mutation epoch prevents an outer CreateObject from
resurrecting after a nested delete/reset, while unrelated GUID activity does
not cancel it. Its tombstone remains until session clear so nested
delete-then-create cannot repeat the outer operation's epoch. A teardown
failure is thrown at the runtime boundary even when a nested newer generation
supersedes the outer create. Materialization is an atomic boundary: logical mutation from a
resource-registration callback is rejected and rolled back, and a replacement
registered during old teardown cannot materialize until that teardown ends.
4. Session Clear previously snapshotted records while allowing callbacks to
register owners outside the snapshot. Registration is now rejected during
terminal clear, preventing untracked renderer/script resources; snapshot
entries already deleted by an earlier callback are skipped rather than torn
down twice.
5. Visibility dispatch previously stopped at the first throwing observer. Both
spatial and live-presentation fan-outs now drain every subscriber and every
queued owner edge before aggregating failures, so one faulty sink cannot
strand lights/particles or leak a stale edge into GUID reuse. Rebucket and
withdrawal finish their canonical cell, projection, and presentation-index
commits before rethrowing those observer aggregates.
The deterministic gate now validates the exact recall action ID and its
animation-hook/PES route plus Hidden/remote-placement/UnHide ownership. Installed
DAT colors, trail/cloud appearance, remote portal observation, and impact-origin
presentation remain visual acceptance items; an in-memory conformance fixture
cannot claim pixel appearance.