NEO-93: Preserve LastAdvancedUtc on clock regression and guard zero timings.
Do not move the lazy-tick marker backward when nowUtc regresses; reject non-positive catalog durations and break the phase loop when cursor stalls. Co-authored-by: Cursor <cursoragent@cursor.com>pull/131/head
parent
a22c900655
commit
e4f08b2015
|
|
@ -277,4 +277,43 @@ public sealed class NpcRuntimeOperationsTests
|
|||
Assert.Equal(T0.AddHours(1).AddSeconds(10), snapshot.PhaseStartedUtc);
|
||||
Assert.Equal(T0.AddHours(1).AddSeconds(10), runtime.LastAdvancedUtc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdvanceAll_ShouldPreserveLastAdvancedUtc_WhenClockRegresses()
|
||||
{
|
||||
// Arrange
|
||||
var (runtime, threat, behavior) = CreateFixture();
|
||||
Advance(T0, runtime, threat, behavior);
|
||||
runtime.LastAdvancedUtc = T0.AddSeconds(10);
|
||||
// Act
|
||||
Advance(T0.AddSeconds(5), runtime, threat, behavior);
|
||||
// Assert
|
||||
Assert.Equal(T0.AddSeconds(10), runtime.LastAdvancedUtc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasPositiveTimingDurations_ShouldRejectNonPositiveCatalogTimings()
|
||||
{
|
||||
// Arrange
|
||||
var invalidCooldown = new NpcBehaviorDefRow(
|
||||
"bad",
|
||||
"Bad",
|
||||
"melee_pressure",
|
||||
100,
|
||||
8.0,
|
||||
16.0,
|
||||
1.5,
|
||||
15,
|
||||
0.0);
|
||||
var invalidWindup = invalidCooldown with { Id = "bad2", TelegraphWindupSeconds = 0.0 };
|
||||
var valid = invalidCooldown with { AttackCooldownSeconds = 3.0, TelegraphWindupSeconds = 1.5 };
|
||||
// Act
|
||||
var cooldownOk = NpcRuntimeOperations.HasPositiveTimingDurations(invalidCooldown);
|
||||
var windupOk = NpcRuntimeOperations.HasPositiveTimingDurations(invalidWindup);
|
||||
var validOk = NpcRuntimeOperations.HasPositiveTimingDurations(valid);
|
||||
// Assert
|
||||
Assert.False(cooldownOk);
|
||||
Assert.False(windupOk);
|
||||
Assert.True(validOk);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ public static class NpcRuntimeOperations
|
|||
behaviorRegistry);
|
||||
}
|
||||
|
||||
runtimeStore.LastAdvancedUtc = nowUtc;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -93,6 +92,11 @@ public static class NpcRuntimeOperations
|
|||
return;
|
||||
}
|
||||
|
||||
if (!HasPositiveTimingDurations(behavior))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!runtimeStore.TryGet(npcInstanceId, out var snapshot))
|
||||
{
|
||||
return;
|
||||
|
|
@ -137,6 +141,7 @@ public static class NpcRuntimeOperations
|
|||
return;
|
||||
}
|
||||
|
||||
var loopCursorBefore = cursor;
|
||||
switch (snapshot.BehaviorState)
|
||||
{
|
||||
case NpcBehaviorState.Idle:
|
||||
|
|
@ -159,7 +164,7 @@ public static class NpcRuntimeOperations
|
|||
cursor,
|
||||
telegraph);
|
||||
// telemetry: npc_state_transition (NEO-96)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
case NpcBehaviorState.TelegraphWindup:
|
||||
|
|
@ -178,7 +183,7 @@ public static class NpcRuntimeOperations
|
|||
cursor,
|
||||
activeTelegraph: null);
|
||||
// telemetry: telegraph_fired / npc_state_transition (NEO-96); logical attack_execute → recover (NEO-95 applies attackDamage)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
case NpcBehaviorState.AttackExecute:
|
||||
|
|
@ -189,7 +194,7 @@ public static class NpcRuntimeOperations
|
|||
cursor,
|
||||
activeTelegraph: null);
|
||||
// telemetry: npc_state_transition (NEO-96); NEO-95 applies attackDamage here
|
||||
continue;
|
||||
break;
|
||||
|
||||
case NpcBehaviorState.Recover:
|
||||
{
|
||||
|
|
@ -208,15 +213,24 @@ public static class NpcRuntimeOperations
|
|||
cursor,
|
||||
telegraph);
|
||||
// telemetry: npc_state_transition (NEO-96)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (cursor <= loopCursorBefore)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Catalog timings must be positive; CI/content gate enforces for prototype defs.</summary>
|
||||
internal static bool HasPositiveTimingDurations(NpcBehaviorDefRow behavior) =>
|
||||
behavior.AttackCooldownSeconds > 0 && behavior.TelegraphWindupSeconds > 0;
|
||||
|
||||
private static bool TryHasAggroHolder(
|
||||
string npcInstanceId,
|
||||
IThreatStateStore threatStore,
|
||||
|
|
|
|||
Loading…
Reference in New Issue