fix(vfx): classify hardwareless particle emitters once

This commit is contained in:
Erik 2026-07-27 00:03:31 +02:00
parent 921712f412
commit 5b3fb17775
8 changed files with 374 additions and 18 deletions

View file

@ -1,6 +1,8 @@
using System.Linq;
using System.Numerics;
using AcDream.Core.Content;
using AcDream.Core.Vfx;
using DatReaderWriter.Lib.IO;
using DatReaderWriter.Types;
using Xunit;
@ -267,6 +269,35 @@ public sealed class ParticleSystemTests
Assert.Same(desc, reg.Get(0x32001234u));
}
[Fact]
public void EmitterDescRegistry_NegativeCachesMissingDatAndRegisterClearsFailure()
{
int calls = 0;
var reg = new EmitterDescRegistry(_ =>
{
calls++;
return null;
});
const uint emitterId = 0x3200DEADu;
Assert.False(reg.TryGet(emitterId, out _, out var first));
Assert.False(reg.TryGet(emitterId, out _, out var second));
Assert.Equal(1, calls);
Assert.Equal(
EmitterDescResolutionFailureKind.MissingEmitterInfo,
first.Kind);
Assert.Equal(first, second);
Assert.Equal(1, reg.FailureCount);
var desc = new EmitterDesc { DatId = emitterId };
reg.Register(desc);
Assert.True(reg.TryGet(emitterId, out var resolved, out var failure));
Assert.Same(desc, resolved);
Assert.Equal(EmitterDescResolutionFailureKind.None, failure.Kind);
Assert.Equal(0, reg.FailureCount);
}
[Fact]
public void LocalVelocity_TransformsABySpawnRotation()
{
@ -398,6 +429,31 @@ public sealed class ParticleSystemTests
Assert.Equal(0u, EmitterDescRegistry.GetRetailHardwareGfxObjId(dat));
}
[Fact]
public void EmitterDescRegistry_ClassifiesAndCachesAuthoredHardwarelessEmitter()
{
const uint emitterId = 0x320002D6u;
var dats = new FakeDatObjectSource();
dats.Add(
emitterId,
new DatReaderWriter.DBObjs.ParticleEmitter
{
GfxObjId = 0x010016C9u,
HwGfxObjId = 0u,
});
var reg = new EmitterDescRegistry(dats);
Assert.False(reg.TryGet(emitterId, out _, out var first));
Assert.False(reg.TryGet(emitterId, out _, out var second));
Assert.Equal(
EmitterDescResolutionFailureKind.InvalidHardwareGfxObjId,
first.Kind);
Assert.Equal(first, second);
Assert.Equal(1, dats.GetCallCount(emitterId));
Assert.Equal(1, reg.FailureCount);
}
[Fact]
public void RetailParticleDegradeDistance_MatchesRetailEntrySelection()
{
@ -1100,4 +1156,31 @@ public sealed class ParticleSystemTests
sys.Tick(0.01f);
Assert.Equal(2, sys.ActiveParticleCount);
}
private sealed class FakeDatObjectSource : IDatObjectSource
{
private readonly Dictionary<uint, IDBObj> _objects = new();
private readonly Dictionary<uint, int> _calls = new();
public void Add(uint id, IDBObj value) => _objects[id] = value;
public int GetCallCount(uint id) =>
_calls.TryGetValue(id, out int count) ? count : 0;
public T Get<T>(uint fileId) where T : IDBObj
{
_calls.TryGetValue(fileId, out int count);
_calls[fileId] = count + 1;
return _objects.TryGetValue(fileId, out IDBObj? value)
&& value is T typed
? typed
: default!;
}
public bool TryGet<T>(uint fileId, out T value) where T : IDBObj
{
value = Get<T>(fileId);
return value is not null;
}
}
}