diff --git a/docs/plans/NEO-93-implementation-plan.md b/docs/plans/NEO-93-implementation-plan.md index 9775028..a9bb235 100644 --- a/docs/plans/NEO-93-implementation-plan.md +++ b/docs/plans/NEO-93-implementation-plan.md @@ -61,7 +61,7 @@ - Holder-only idle→aggro sync from **`IThreatStateStore`**; no proximity auto-aggro. - **`attack_execute`** chains instantly to **`recover`** within the same advance step (no player damage — NEO-95). - Dev **`combat-targets-fixture`** resets runtime rows alongside HP + threat. -- **22** NEO-93-focused tests green (`NpcRuntimeOperationsTests`, `InMemoryNpcRuntimeStateStoreTests`, fixture extension). +- **23** NEO-93-focused tests green (`NpcRuntimeOperationsTests`, `InMemoryNpcRuntimeStateStoreTests`, fixture extension). ## Technical approach diff --git a/server/NeonSprawl.Server.Tests/Game/Npc/NpcRuntimeOperationsTests.cs b/server/NeonSprawl.Server.Tests/Game/Npc/NpcRuntimeOperationsTests.cs index 2a17430..eeb1107 100644 --- a/server/NeonSprawl.Server.Tests/Game/Npc/NpcRuntimeOperationsTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Npc/NpcRuntimeOperationsTests.cs @@ -239,5 +239,23 @@ public sealed class NpcRuntimeOperationsTests runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); Assert.Equal(NpcBehaviorState.Recover, snapshot.BehaviorState); Assert.Equal(T0.AddSeconds(4.5), snapshot.PhaseStartedUtc); + Assert.Equal(T0.AddSeconds(5), runtime.LastAdvancedUtc); + } + + [Fact] + public void AdvanceAll_ShouldPreserveGradualCatchUp_AfterLongGapWithDeltaCap() + { + // Arrange + var (runtime, threat, behavior) = CreateFixture(); + SetHolder(threat, PrototypeNpcRegistry.PrototypeNpcMeleeId); + Advance(T0, runtime, threat, behavior); + Advance(T0.AddSeconds(100), runtime, threat, behavior, maxDeltaSeconds: 5); + // Act + Advance(T0.AddSeconds(100.5), runtime, threat, behavior, maxDeltaSeconds: 5); + // Assert + Assert.Equal(T0.AddSeconds(10), runtime.LastAdvancedUtc); + runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); + Assert.Equal(NpcBehaviorState.Recover, snapshot.BehaviorState); + Assert.Equal(T0.AddSeconds(9), snapshot.PhaseStartedUtc); } } diff --git a/server/NeonSprawl.Server/Game/Npc/NpcRuntimeOperations.cs b/server/NeonSprawl.Server/Game/Npc/NpcRuntimeOperations.cs index 0964e23..3e142ba 100644 --- a/server/NeonSprawl.Server/Game/Npc/NpcRuntimeOperations.cs +++ b/server/NeonSprawl.Server/Game/Npc/NpcRuntimeOperations.cs @@ -72,7 +72,7 @@ public static class NpcRuntimeOperations behaviorRegistry); } - runtimeStore.LastAdvancedUtc = nowUtc; + runtimeStore.LastAdvancedUtc = windowEnd; } /// Resets all prototype NPC runtime rows to idle (dev fixture).