fix(ai): make debug-message cooldown comparisons wraparound-safe (#2659)

When an AI debug-message cooldown crosses the signed tick-counter boundary, DebugInterpolatedStringHandler can emit before the deadline or suppress a message after it. Both constructors compare absolute tick values.

Use subtraction-based deadline comparisons in both constructors, following dev-docs/tick-counts.md. This is a two-line production change; message formatting, cooldown duration, and gameplay behavior are unchanged.

Adds 35 regression cases exercising the actual handler and DebugSayFormatted:
- Both constructor overloads, with debugging enabled and disabled.
- Before/at/after deadlines on positive and negative clocks.
- Future and expired deadlines across signed wraparound.
- Cooldown rearming across wraparound and buffer clearing.
- A compiler-generated interpolated call through the public extension method.

Validation on Linux / .NET 10, based on clean upstream 35e3a31b4:
- Release build: 0 warnings, 0 errors.
- New tests against unchanged production code: 28 passed, 7 failed.
- With the fix: 35 passed, including without client map data (no skips).
- Selected AI/pet suites: 134 passed / 7 failed before; 141 passed / 0 failed after.

Tests use synthetic entities; no running shard or world saves. The faulty comparisons were reverified on upstream main before submission. Windows execution remains unverified. The tests exercise explicitly assigned deadlines; deadline initialization is outside this patch.

Addresses only the DebugInterpolatedStringHandler comparison item in #2627; it does not close the other audit items.
This commit is contained in:
Robert Dickey 2026-09-19 11:59:00 -05:00 • committed by GitHub
parent 12b0886cef
commit 9d9e672a09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 147 additions and 2 deletions

View file

@ -56,7 +56,7 @@ public ref struct DebugInterpolatedStringHandler
/// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
public DebugInterpolatedStringHandler(int literalLength, int formattedCount, Mobiles.BaseAI ai)
{
_debugActive = ai.Mobile?.Debug == true && Core.TickCount >= ai.NextDebugMessage;
_debugActive = ai.Mobile?.Debug == true && Core.TickCount - ai.NextDebugMessage >= 0;
if (_debugActive)
{
@ -79,7 +79,7 @@ public ref struct DebugInterpolatedStringHandler
/// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
public DebugInterpolatedStringHandler(int literalLength, int formattedCount, IFormatProvider? provider, Mobiles.BaseAI ai)
{
_debugActive = ai.Mobile?.Debug == true && Core.TickCount >= ai.NextDebugMessage;
_debugActive = ai.Mobile?.Debug == true && Core.TickCount - ai.NextDebugMessage >= 0;
if (_debugActive)
{