test(vfx #C.1.5a): real-emitter verification in OnRemove test + unused using
Code-review follow-up to 003c502:
1. Test 3 (OnRemove_StopsScriptsAndEmitters) now wires the runner into
the real ParticleHookSink instead of a RecordingSink, registers a
persistent EmitterDesc, lets the CreateParticleHook actually spawn an
emitter, then asserts the sink killed it after OnRemove. Previously
the test only verified runner-side state — sink.StopAllForEntity was
never observably exercised, so a regression dropping that call would
have passed silently.
2. Removed unused `using System.Numerics` from EntityScriptActivator.cs.
No production code changes. Tests 1 and 2 unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
003c502774
commit
e0529b023d
2 changed files with 50 additions and 9 deletions
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
|
||||||
using AcDream.Core.Vfx;
|
using AcDream.Core.Vfx;
|
||||||
using AcDream.Core.World;
|
using AcDream.Core.World;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,22 +89,64 @@ public sealed class EntityScriptActivatorTests
|
||||||
Assert.Empty(p.Recording.Calls);
|
Assert.Empty(p.Recording.Calls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Persistent emitter: TotalDuration=0 and TotalParticles=0 prevent
|
||||||
|
/// auto-finish; InitialParticles=1 ensures a particle spawns at t=0
|
||||||
|
/// without waiting for the Birthrate timer; Lifespan=999f keeps that
|
||||||
|
/// particle alive far past the test horizon.
|
||||||
|
/// </summary>
|
||||||
|
private static EmitterDesc BuildPersistentEmitterDesc() => new()
|
||||||
|
{
|
||||||
|
DatId = 100u,
|
||||||
|
Type = ParticleType.Still,
|
||||||
|
EmitterKind = ParticleEmitterKind.BirthratePerSec,
|
||||||
|
MaxParticles = 4,
|
||||||
|
InitialParticles = 1,
|
||||||
|
TotalParticles = 0, // 0 = no particle-count cap
|
||||||
|
TotalDuration = 0f, // 0 = no time-based finish
|
||||||
|
Lifespan = 999f,
|
||||||
|
LifetimeMin = 999f,
|
||||||
|
LifetimeMax = 999f,
|
||||||
|
Birthrate = 0.5f,
|
||||||
|
StartSize = 0.5f,
|
||||||
|
EndSize = 0.5f,
|
||||||
|
StartAlpha = 1f,
|
||||||
|
EndAlpha = 1f,
|
||||||
|
};
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void OnRemove_StopsScriptsAndEmitters()
|
public void OnRemove_StopsScriptsAndEmitters()
|
||||||
{
|
{
|
||||||
var p = BuildPipeline(
|
// For this test we need the runner to dispatch into the REAL
|
||||||
(0xAAu, BuildScript((0.0, new CreateParticleHook { EmitterInfoId = 100 }))));
|
// ParticleHookSink so OnRemove's sink.StopAllForEntity has a live
|
||||||
var activator = new EntityScriptActivator(p.Runner, p.Sink, _ => 0xAAu);
|
// emitter to kill. This is the only observable way to verify the
|
||||||
|
// call had effect without subclassing the sealed sink.
|
||||||
|
var registry = new EmitterDescRegistry();
|
||||||
|
registry.Register(BuildPersistentEmitterDesc());
|
||||||
|
|
||||||
|
var system = new ParticleSystem(registry);
|
||||||
|
var hookSink = new ParticleHookSink(system);
|
||||||
|
|
||||||
|
var script = BuildScript((0.0, new CreateParticleHook { EmitterInfoId = 100u, Offset = new Frame() }));
|
||||||
|
var table = new Dictionary<uint, DatPhysicsScript> { [0xAAu] = script };
|
||||||
|
var runner = new PhysicsScriptRunner(
|
||||||
|
id => table.TryGetValue(id, out var s) ? s : null,
|
||||||
|
hookSink); // runner dispatches into real sink, not RecordingSink
|
||||||
|
|
||||||
|
var activator = new EntityScriptActivator(runner, hookSink, _ => 0xAAu);
|
||||||
var entity = MakeEntity(serverGuid: 0xCAFEu, position: Vector3.Zero);
|
var entity = MakeEntity(serverGuid: 0xCAFEu, position: Vector3.Zero);
|
||||||
|
|
||||||
activator.OnCreate(entity);
|
activator.OnCreate(entity);
|
||||||
Assert.Equal(1, p.Runner.ActiveScriptCount);
|
runner.Tick(0.001f); // fires the CreateParticleHook → spawns emitter
|
||||||
|
|
||||||
|
Assert.True(system.ActiveEmitterCount > 0,
|
||||||
|
"Setup precondition failed: emitter should be alive after the hook fires.");
|
||||||
|
|
||||||
activator.OnRemove(0xCAFEu);
|
activator.OnRemove(0xCAFEu);
|
||||||
|
|
||||||
Assert.Equal(0, p.Runner.ActiveScriptCount);
|
Assert.Equal(0, runner.ActiveScriptCount);
|
||||||
// Tick after Remove must not surface any further hook fires.
|
// sink.StopAllForEntity marks the emitter Finished; system.Tick reaps it.
|
||||||
p.Runner.Tick(1.0f);
|
system.Tick(0.01f);
|
||||||
Assert.Empty(p.Recording.Calls);
|
Assert.Equal(0, system.ActiveEmitterCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue