fix(render): #350 — the shadow ledger's lifetime counters are 64-bit; the 2h42m overflow is closed

RenderDeltaApplyResult's nine counters accumulate for the lifetime of
a world generation (the cumulative sum is never reset in place) yet
were int where every sibling lifetime counter in the same class was
already ulong/long. A 2h42m single-generation session (login to crash
with zero portals) overflowed one through ordinary per-tick churn from
SynchronizeActiveSources' two call sites per frame. Introduced
0eb66485 (2026-07-24); first reached by the vendor buy gate because
parking at a shop for hours produced the project's first multi-hour
unbroken generation. The vendor materializer was exonerated by routing
analysis (shop-item Ingest/Remove reaches inventory deltas only, never
the render journal).

Widened to long; the per-tick builder stays int and widens implicitly;
the arithmetic stays checked. All 68 render-shadow tests green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-08 10:29:39 +02:00
parent 3c9fc57adb
commit 58c8de264e
2 changed files with 41 additions and 10 deletions

View file

@ -401,18 +401,23 @@ internal readonly record struct RenderSceneIndexCounts(
};
}
// #350: these counters accumulate for the LIFETIME of a world generation
// (RenderSceneShadowRuntime._cumulativeApply is never reset in place), so
// they are 64-bit like the class's other lifetime counters — a 2h42m
// single-generation session overflowed the original int through ordinary
// per-tick churn. The arithmetic stays checked.
internal readonly record struct RenderDeltaApplyResult(
int Applied,
int Registered,
int Updated,
int Replaced,
int Unregistered,
int RejectedGeneration,
int RejectedOutOfOrderSequence,
int RejectedStaleIncarnation,
int RejectedMissing)
long Applied,
long Registered,
long Updated,
long Replaced,
long Unregistered,
long RejectedGeneration,
long RejectedOutOfOrderSequence,
long RejectedStaleIncarnation,
long RejectedMissing)
{
public int Rejected =>
public long Rejected =>
RejectedGeneration
+ RejectedOutOfOrderSequence
+ RejectedStaleIncarnation