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

@ -41,6 +41,15 @@ public sealed class LightingHookSink : IAnimationHookSink
/// </summary>
public event Action<uint, bool>? OwnerLightingChanged;
/// <summary>
/// Diagnostic ownership counts for lifecycle verification. The light
/// manager's registered-light count alone cannot reveal a stale owner
/// lighting latch or pose-tracking list.
/// </summary>
public int OwnedLightOwnerCount => _byOwner.Count;
public int PoseTrackedOwnerCount => _trackedByOwner.Count;
public int RetainedOwnerStateCount => _enabledByOwner.Count;
public LightingHookSink(LightManager lights, IEntityEffectPoseSource poses)
{
_lights = lights ?? throw new System.ArgumentNullException(nameof(lights));

View file

@ -540,6 +540,18 @@ public sealed class ShadowObjectRegistry
public int TotalRegistered => _entityToCells.Count;
/// <summary>
/// Total logical shadow registrations, including dynamic live objects
/// suspended with zero cell rows while their landblock is unavailable.
/// Lifecycle stress gates must inspect this value as well as
/// <see cref="TotalRegistered"/> so a retained collision payload cannot
/// survive its owning live-object incarnation unnoticed.
/// </summary>
public int RetainedRegistrationCount => _entityReg.Count;
/// <summary>Suspended logical registrations awaiting spatial re-entry.</summary>
public int SuspendedRegistrationCount => _suspendedEntities.Count;
/// <summary>
/// Debug: enumerate every registered ShadowEntry (deduplicated across cells).
/// Single-shape entities return one entry per shape; multi-part entities

View file

@ -42,6 +42,18 @@ public sealed class ParticleHookSink : IAnimationHookSink
public Action<string>? DiagnosticSink { get; set; }
/// <summary>
/// Diagnostic ownership counts used by lifecycle stress gates. These count
/// the sink's retained bookkeeping, not only emitters still present in the
/// particle simulator, so teardown tests can detect a stale logical ID or
/// per-owner handle bag after the underlying emitter has gone away.
/// </summary>
public int ActiveBindingCount => _bindingsByHandle.Count;
public int LogicalEmitterCount => _handlesByKey.Count;
public int TrackedOwnerCount => _handlesByEntity.Count;
public int RenderPassOwnerCount => _renderPassByEntity.Count;
public int HiddenPresentationOwnerCount => _hiddenPresentationOwners.Count;
private void OnEmitterDied(int handle)
{
if (!_bindingsByHandle.TryRemove(handle, out EmitterBinding binding))

View file

@ -55,6 +55,7 @@ public sealed class PhysicsScriptRunner
public int ActiveOwnerCount => _owners.Count;
public int ScheduledCallPesCount => _delayedCalls.Count;
public int OwnerAnchorCount => _ownerAnchors.Count;
public bool DiagEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_DUMP_PLAYSCRIPT") == "1";