NEO-93: Address code review — AAA tests, single-call advance, windup fix.

Co-authored-by: Cursor <cursoragent@cursor.com>
pull/131/head
VinPropane 2026-05-27 23:08:34 -04:00
parent eed176d226
commit d3f178b5fb
2 changed files with 72 additions and 37 deletions

View File

@ -35,8 +35,8 @@ public sealed class NpcRuntimeOperationsTests
var (runtime, threat, behavior) = CreateFixture(); var (runtime, threat, behavior) = CreateFixture();
// Act // Act
Advance(T0, runtime, threat, behavior); Advance(T0, runtime, threat, behavior);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
// Assert // Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.Idle, snapshot.BehaviorState); Assert.Equal(NpcBehaviorState.Idle, snapshot.BehaviorState);
Assert.Null(snapshot.ActiveTelegraph); Assert.Null(snapshot.ActiveTelegraph);
} }
@ -49,8 +49,8 @@ public sealed class NpcRuntimeOperationsTests
SetHolder(threat, PrototypeNpcRegistry.PrototypeNpcMeleeId); SetHolder(threat, PrototypeNpcRegistry.PrototypeNpcMeleeId);
// Act // Act
Advance(T0, runtime, threat, behavior); Advance(T0, runtime, threat, behavior);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
// Assert // Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.Aggro, snapshot.BehaviorState); Assert.Equal(NpcBehaviorState.Aggro, snapshot.BehaviorState);
Assert.Equal(T0, snapshot.PhaseStartedUtc); Assert.Equal(T0, snapshot.PhaseStartedUtc);
Assert.Null(snapshot.ActiveTelegraph); Assert.Null(snapshot.ActiveTelegraph);
@ -64,8 +64,8 @@ public sealed class NpcRuntimeOperationsTests
runtime.LastAdvancedUtc = T0; runtime.LastAdvancedUtc = T0;
// Act // Act
Advance(T0.AddSeconds(10), runtime, threat, behavior); Advance(T0.AddSeconds(10), runtime, threat, behavior);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
// Assert // Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.Idle, snapshot.BehaviorState); Assert.Equal(NpcBehaviorState.Idle, snapshot.BehaviorState);
Assert.Null(snapshot.ActiveTelegraph); Assert.Null(snapshot.ActiveTelegraph);
} }
@ -79,8 +79,8 @@ public sealed class NpcRuntimeOperationsTests
Advance(T0, runtime, threat, behavior); Advance(T0, runtime, threat, behavior);
// Act // Act
Advance(T0.AddSeconds(3), runtime, threat, behavior); Advance(T0.AddSeconds(3), runtime, threat, behavior);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
// Assert // Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.TelegraphWindup, snapshot.BehaviorState); Assert.Equal(NpcBehaviorState.TelegraphWindup, snapshot.BehaviorState);
Assert.Equal(T0.AddSeconds(3), snapshot.PhaseStartedUtc); Assert.Equal(T0.AddSeconds(3), snapshot.PhaseStartedUtc);
Assert.NotNull(snapshot.ActiveTelegraph); Assert.NotNull(snapshot.ActiveTelegraph);
@ -95,13 +95,13 @@ public sealed class NpcRuntimeOperationsTests
Advance(T0, runtime, threat, behavior); Advance(T0, runtime, threat, behavior);
// Act // Act
Advance(T0.AddSeconds(2.9), runtime, threat, behavior); Advance(T0.AddSeconds(2.9), runtime, threat, behavior);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
// Assert // Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.Aggro, snapshot.BehaviorState); Assert.Equal(NpcBehaviorState.Aggro, snapshot.BehaviorState);
} }
[Fact] [Fact]
public void AdvanceAll_ShouldCompleteMeleeWindupDuration_BeforeAttackExecute() public void AdvanceAll_ShouldRemainTelegraphWindup_BeforeMeleeWindupCompletes()
{ {
// Arrange // Arrange
var (runtime, threat, behavior) = CreateFixture(); var (runtime, threat, behavior) = CreateFixture();
@ -110,14 +110,27 @@ public sealed class NpcRuntimeOperationsTests
Advance(T0.AddSeconds(3), runtime, threat, behavior); Advance(T0.AddSeconds(3), runtime, threat, behavior);
// Act // Act
Advance(T0.AddSeconds(4.4), runtime, threat, behavior); Advance(T0.AddSeconds(4.4), runtime, threat, behavior);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var duringWindup);
Advance(T0.AddSeconds(4.5), runtime, threat, behavior);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var afterWindup);
// Assert // Assert
Assert.Equal(NpcBehaviorState.TelegraphWindup, duringWindup.BehaviorState); runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.Recover, afterWindup.BehaviorState); Assert.Equal(NpcBehaviorState.TelegraphWindup, snapshot.BehaviorState);
Assert.Equal(T0.AddSeconds(4.5), afterWindup.PhaseStartedUtc); }
Assert.Null(afterWindup.ActiveTelegraph);
[Fact]
public void AdvanceAll_ShouldEnterRecover_WhenMeleeWindupCompletes()
{
// Arrange
var (runtime, threat, behavior) = CreateFixture();
SetHolder(threat, PrototypeNpcRegistry.PrototypeNpcMeleeId);
Advance(T0, runtime, threat, behavior);
Advance(T0.AddSeconds(3), runtime, threat, behavior);
Advance(T0.AddSeconds(4.4), runtime, threat, behavior);
// Act
Advance(T0.AddSeconds(4.5), runtime, threat, behavior);
// Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.Recover, snapshot.BehaviorState);
Assert.Equal(T0.AddSeconds(4.5), snapshot.PhaseStartedUtc);
Assert.Null(snapshot.ActiveTelegraph);
} }
[Fact] [Fact]
@ -131,8 +144,24 @@ public sealed class NpcRuntimeOperationsTests
Advance(T0.AddSeconds(4.5), runtime, threat, behavior); Advance(T0.AddSeconds(4.5), runtime, threat, behavior);
// Act // Act
Advance(T0.AddSeconds(7.5), runtime, threat, behavior); Advance(T0.AddSeconds(7.5), runtime, threat, behavior);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
// Assert // Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.TelegraphWindup, snapshot.BehaviorState);
Assert.Equal(T0.AddSeconds(7.5), snapshot.PhaseStartedUtc);
Assert.NotNull(snapshot.ActiveTelegraph);
}
[Fact]
public void AdvanceAll_ShouldRunFullMeleeCycle_ToSecondTelegraph_InSingleAdvanceCall()
{
// Arrange
var (runtime, threat, behavior) = CreateFixture();
SetHolder(threat, PrototypeNpcRegistry.PrototypeNpcMeleeId);
Advance(T0, runtime, threat, behavior);
// Act
Advance(T0.AddSeconds(7.5), runtime, threat, behavior, maxDeltaSeconds: 10);
// Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.TelegraphWindup, snapshot.BehaviorState); Assert.Equal(NpcBehaviorState.TelegraphWindup, snapshot.BehaviorState);
Assert.Equal(T0.AddSeconds(7.5), snapshot.PhaseStartedUtc); Assert.Equal(T0.AddSeconds(7.5), snapshot.PhaseStartedUtc);
Assert.NotNull(snapshot.ActiveTelegraph); Assert.NotNull(snapshot.ActiveTelegraph);
@ -149,16 +178,35 @@ public sealed class NpcRuntimeOperationsTests
_ = threat.TryClearHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId); _ = threat.TryClearHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId);
// Act // Act
Advance(T0.AddSeconds(3.5), runtime, threat, behavior); Advance(T0.AddSeconds(3.5), runtime, threat, behavior);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
// Assert // Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.Idle, snapshot.BehaviorState); Assert.Equal(NpcBehaviorState.Idle, snapshot.BehaviorState);
Assert.Null(snapshot.ActiveTelegraph); Assert.Null(snapshot.ActiveTelegraph);
} }
[Theory]
[InlineData("prototype_npc_ranged", 4.0)]
[InlineData("prototype_npc_elite", 5.0)]
public void AdvanceAll_ShouldEnterTelegraph_AtCatalogCooldownForArchetype(
string npcInstanceId,
double attackCooldownSeconds)
{
// Arrange
var (runtime, threat, behavior) = CreateFixture();
SetHolder(threat, npcInstanceId);
Advance(T0, runtime, threat, behavior);
// Act
Advance(T0.AddSeconds(attackCooldownSeconds), runtime, threat, behavior);
// Assert
runtime.TryGet(npcInstanceId, out var snapshot);
Assert.Equal(NpcBehaviorState.TelegraphWindup, snapshot.BehaviorState);
Assert.Equal(T0.AddSeconds(attackCooldownSeconds), snapshot.PhaseStartedUtc);
}
[Theory] [Theory]
[InlineData("prototype_npc_ranged", 4.0, 2.0)] [InlineData("prototype_npc_ranged", 4.0, 2.0)]
[InlineData("prototype_npc_elite", 5.0, 2.5)] [InlineData("prototype_npc_elite", 5.0, 2.5)]
public void AdvanceAll_ShouldUseCatalogTimings_ForEachArchetype( public void AdvanceAll_ShouldEnterRecover_AfterCatalogWindupForArchetype(
string npcInstanceId, string npcInstanceId,
double attackCooldownSeconds, double attackCooldownSeconds,
double telegraphWindupSeconds) double telegraphWindupSeconds)
@ -167,22 +215,15 @@ public sealed class NpcRuntimeOperationsTests
var (runtime, threat, behavior) = CreateFixture(); var (runtime, threat, behavior) = CreateFixture();
SetHolder(threat, npcInstanceId); SetHolder(threat, npcInstanceId);
Advance(T0, runtime, threat, behavior); Advance(T0, runtime, threat, behavior);
// Act
Advance(T0.AddSeconds(attackCooldownSeconds), runtime, threat, behavior); Advance(T0.AddSeconds(attackCooldownSeconds), runtime, threat, behavior);
runtime.TryGet(npcInstanceId, out var telegraphSnapshot); // Act
Advance( Advance(T0.AddSeconds(attackCooldownSeconds + telegraphWindupSeconds), runtime, threat, behavior);
T0.AddSeconds(attackCooldownSeconds + telegraphWindupSeconds),
runtime,
threat,
behavior);
runtime.TryGet(npcInstanceId, out var recoverSnapshot);
// Assert // Assert
Assert.Equal(NpcBehaviorState.TelegraphWindup, telegraphSnapshot.BehaviorState); runtime.TryGet(npcInstanceId, out var snapshot);
Assert.Equal(T0.AddSeconds(attackCooldownSeconds), telegraphSnapshot.PhaseStartedUtc); Assert.Equal(NpcBehaviorState.Recover, snapshot.BehaviorState);
Assert.Equal(NpcBehaviorState.Recover, recoverSnapshot.BehaviorState);
Assert.Equal( Assert.Equal(
T0.AddSeconds(attackCooldownSeconds + telegraphWindupSeconds), T0.AddSeconds(attackCooldownSeconds + telegraphWindupSeconds),
recoverSnapshot.PhaseStartedUtc); snapshot.PhaseStartedUtc);
} }
[Fact] [Fact]
@ -192,10 +233,10 @@ public sealed class NpcRuntimeOperationsTests
var (runtime, threat, behavior) = CreateFixture(); var (runtime, threat, behavior) = CreateFixture();
SetHolder(threat, PrototypeNpcRegistry.PrototypeNpcMeleeId); SetHolder(threat, PrototypeNpcRegistry.PrototypeNpcMeleeId);
Advance(T0, runtime, threat, behavior); Advance(T0, runtime, threat, behavior);
// Act — 10s gap with 5s cap should not reach second telegraph (needs 7.5s from aggro start) // Act
Advance(T0.AddSeconds(10), runtime, threat, behavior, maxDeltaSeconds: 5); Advance(T0.AddSeconds(10), runtime, threat, behavior, maxDeltaSeconds: 5);
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
// Assert // Assert
runtime.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot);
Assert.Equal(NpcBehaviorState.Recover, snapshot.BehaviorState); Assert.Equal(NpcBehaviorState.Recover, snapshot.BehaviorState);
Assert.Equal(T0.AddSeconds(4.5), snapshot.PhaseStartedUtc); Assert.Equal(T0.AddSeconds(4.5), snapshot.PhaseStartedUtc);
} }

View File

@ -171,19 +171,13 @@ public static class NpcRuntimeOperations
} }
cursor = phaseEnd; cursor = phaseEnd;
WriteState(
runtimeStore,
npcInstanceId,
NpcBehaviorState.AttackExecute,
cursor,
activeTelegraph: null);
WriteState( WriteState(
runtimeStore, runtimeStore,
npcInstanceId, npcInstanceId,
NpcBehaviorState.Recover, NpcBehaviorState.Recover,
cursor, cursor,
activeTelegraph: null); activeTelegraph: null);
// telemetry: telegraph_fired / npc_state_transition (NEO-96); NEO-95 applies attackDamage at windup complete // telemetry: telegraph_fired / npc_state_transition (NEO-96); logical attack_execute → recover (NEO-95 applies attackDamage)
continue; continue;
} }