fix(N.5b T8): TerrainDiagMedian/P95 IndexOutOfRangeException on first flush

First diag flush fires ~5s after process start (Environment.TickCount64
threshold), but at that point only 1 sample may have been recorded if
the user is mid-login. The original `copy[copy.Length - nz / 2]` form
underflowed to copy[copy.Length] when nz=1 (nz/2=0), throwing
IndexOutOfRangeException at GameWindow.cs:8799 on the first OnRender
after login.

Fix: use `copy.Length - 1 - (nz - 1) / 2` for median (always >= 0 for
nz >= 1, returns the single sample for nz=1) and clamp the percentile
offset via `(nz - 1) * 0.05` for the same reason.

Caught by user's perf-baseline launch with ACDREAM_LEGACY_TERRAIN=1
(the benchmark toggle from 336ad34). The bug exists in T8 itself
regardless of the toggle.

Build green; existing tests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-09 09:40:22 +02:00
parent 336ad34444
commit 55e516c538

View file

@ -8796,7 +8796,12 @@ public sealed class GameWindow : IDisposable
int nz = 0; int nz = 0;
foreach (var v in copy) if (v > 0) nz++; foreach (var v in copy) if (v > 0) nz++;
if (nz == 0) return 0; if (nz == 0) return 0;
return copy[copy.Length - nz / 2]; // Sorted ascending: zero-padding at the front, samples at the back.
// Median of nz samples is the middle of the last nz entries; using
// (nz - 1) / 2 from the end keeps the offset >= 0 for all nz >= 1
// (the original nz / 2 form underflowed to copy.Length on first
// diag-flush when only 1 sample had been recorded).
return copy[copy.Length - 1 - (nz - 1) / 2];
} }
private static long TerrainDiagPercentile95Micros(long[] samples) private static long TerrainDiagPercentile95Micros(long[] samples)
@ -8806,8 +8811,10 @@ public sealed class GameWindow : IDisposable
int nz = 0; int nz = 0;
foreach (var v in copy) if (v > 0) nz++; foreach (var v in copy) if (v > 0) nz++;
if (nz == 0) return 0; if (nz == 0) return 0;
int idx = copy.Length - 1 - (int)(nz * 0.05); // 95th percentile = upper end of the sorted samples; clamp the
return copy[idx]; // offset to stay inside the populated tail when nz < 20.
int offset = (int)((nz - 1) * 0.05);
return copy[copy.Length - 1 - offset];
} }
private void OnClosing() private void OnClosing()