diff --git a/docs/ISSUES.md b/docs/ISSUES.md index ae486384..ad437873 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -4225,6 +4225,50 @@ selector. --- +## #125 — GL InvalidOperation during staged texture upload: failed uploads are STICKY (never retried) + uncaught crash in GenerateMipmaps + +**Status:** OPEN — HIGH (the likely #119 missing-stairs mechanism + a process crash) +**Filed:** 2026-06-11 (in-tower WB_DIAG launch, `tower-wbdiag3.log` — preserved in the worktree root) +**Component:** render — WB staged texture pipeline (ObjectMeshManager / ManagedGLTextureArray) + +**Evidence (one launch, character spawned inside the #119 tower):** +1. `[wb-error] Error uploading mesh data for 0x0100321D` — GL + `InvalidOperation` thrown in `ManagedGLTextureArray..ctor:70` + (TextureAtlasManager ctor → CreateTextureArrayInternal), caught by + `UploadMeshData`'s try/catch → returns null. **The drop is STICKY:** + `_preparationTasks.TryRemove` runs BEFORE the upload + (ObjectMeshManager.cs:685), so a failed upload is never re-prepared — + that mesh is permanently invisible for the session (only a one-line + [wb-error] marks it). +2. Same session, `Tick()` → `GenerateMipmaps()` → + `ManagedGLTextureArray.ProcessDirtyUpdatesInternal:283` threw the SAME + GL InvalidOperation **uncaught** → process death (exit 82). Both on the + render thread (Tick/OnRender) — not a thread-affinity bug. + +**Why this matters for #119:** the missing tower stairs are per-cell +Setup statics whose parts are individually uploaded; an intermittent GL +error burst during atlas creation/flush kills whichever uploads are in +flight — "partially invisible", varying with load order, hitting the +late-loading AAB3 interior statics consistently. The dat + extraction + +registration + dispatcher are all exonerated by read/test +(Issue119TowerDumpTests; the [up-null] pair was a separate, legitimate +no-draw class). + +**Open questions (next session):** (a) what makes the GL context error +out — a stale error queued by an earlier unchecked call being +mis-attributed to WB's diligent glGetError checks (classic GL +attribution trap; suspects: the #117 stencil punch state, the new #118 / +#121 passes, or a pre-existing per-frame state leak), vs. a genuine +invalid texture-array creation state; (b) whether upload failures should +re-enqueue instead of dropping (retail has no such failure mode — the +sticky drop is OUR invention and must go regardless); (c) the uncaught +GenerateMipmaps path needs the same handling either way. +**Repro lever:** the test character's save spawns INSIDE the tower — +every launch loads the exact content; `ACDREAM_WB_DIAG=1` prints the +meshMissing counters. + +--- + # Recently closed ## #113 — Phantom staircase: REOPENED 2026-06-11, folded into the HOLISTIC BUILDING-RENDER PORT diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs index 2f5a16f9..f7214e9e 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs @@ -1583,7 +1583,12 @@ public sealed unsafe class WbDrawDispatcher : 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 front, samples at the back. (nz - 1) / 2 + // from the end keeps the offset >= 0 for all nz >= 1 — the original + // nz / 2 form indexed copy[copy.Length] (crash) on the first diag flush + // when exactly 1 sample was recorded. Same fix as GameWindow's + // TerrainDiagMedianMicros twin. + return copy[copy.Length - 1 - (nz - 1) / 2]; } private static long Percentile95Micros(long[] samples)