From 55e516c538b4ad490fb35b63b813e9e4fa5528e6 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 9 May 2026 09:40:22 +0200 Subject: [PATCH] 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) --- src/AcDream.App/Rendering/GameWindow.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index f34fb77..bdf88d6 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -8796,7 +8796,12 @@ public sealed class GameWindow : IDisposable int nz = 0; foreach (var v in copy) if (v > 0) nz++; 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) @@ -8806,8 +8811,10 @@ public sealed class GameWindow : IDisposable int nz = 0; foreach (var v in copy) if (v > 0) nz++; if (nz == 0) return 0; - int idx = copy.Length - 1 - (int)(nz * 0.05); - return copy[idx]; + // 95th percentile = upper end of the sorted samples; clamp the + // 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()