truncation

Counters for the render path’s silent truncations.

Each of these is a fixed ceiling that degrades the image when it binds – transport that should have reached the pixel does not – and every one of them used to pass without a word. DESIGN_mesh_identity_open.md §Y states the rule they broke: an instrument that reports zero may not be looking. So the counters here are unconditional; a zero is a measurement, not an absence of one.

The ceilings, and what each costs when it binds:

surfaces_per_ray

max_surfaces_per_ray (256) surfaces composited along one primary ray. The walk stops and the ray’s leftover weight is handed to the background, so a deep translucent stack goes see-through at the 257th layer.

shadow_lights

max_shadow_lights (16, ALGAN_MAX_SHADOW_LIGHTS) shadowed lights per fragment. A compile-time vector length, so the surplus lights are still lit – they simply never cast. Each RectAreaLight emitter sample spends one slot, so a 4x4 area light fills the default cap alone.

sheet_layers

16 overlapping layers of one surface in one pixel (the conflict rank the sheet compaction packs into the sheet key). Layers past the 16th merge into the last sub-band and attenuate once between them instead of once each, so a self-overlapping morph renders too light.

dropped_continuations

A reflection/refraction continuation that could not reserve a slot in the tile’s shared ray pool. A splitting batch (pool_ratio > 1) discards and retries the tile, so it never loses one; a batch at pool_ratio == 1 has no spare slots at all and the reservation simply fails, dropping that branch’s contribution.

closed_shell_ring

4 declared closed shells simultaneously entered along one path-traced camera ray (pt_shade’s per-ray ring, the solid_shell_alpha ceiling’s per-ray form). The surplus shell’s exit crossing composites again instead of being suppressed, so translucent solids nested more than four deep render slightly too opaque.

medium_stack

Four simultaneously tracked closed physical interiors. An overflowing path or visibility query is absorbed (fail-closed), never leaked to the sky.

medium_query

A containment/extinction query exhausted max_surfaces_per_ray. Its result is absorbed rather than pretending unknown geometry was vacuum.

Reporting. Truncation is a correctness event, not a budgeting one, so the first occurrence of each ceiling in a render is a WARNING – unlike the wavefront’s pool retries and batch splits, which are the memory model working as designed and log at PERF. Later batches of the same render log their counts at PERF instead of repeating the warning, because a scene that truncates one frame truncates all of them and a warning per batch would be noise. The running totals ride on RenderPlan either way, so a script can assert on them without parsing logs.

Scope: one render job. _TruncationRecorder.reset() is called by RenderLoopMixin.get_frames, which is the boundary of a save_video / save_frame.

One non-truncation statistic rides here too: the path tracer’s mean samples per pixel (record_path_samples()). It is not a ceiling and never warns – adaptive sampling stopping a converged pixel is the sampler working as designed, so it logs at PERF like the budget events – but it wants exactly this module’s three properties: render-job scope, the same snapshot/restore so a chunk discarded for memory does not count twice, and a graft onto the frozen RenderPlan afterwards.

Classes

TruncationCounts

How many times each render ceiling bound during a render.

Functions

attach_render_stats(plan)[source]

Return plan carrying the render job’s non-ceiling statistics.

Today that is the path tracer’s mean samples per pixel, which adaptive sampling makes a measurement rather than an echo of samples_per_pixel. Grafted like the truncations, and for the same reason: RenderPlan is frozen and is built before the batch renders.

attach_truncations(plan)[source]

Return plan carrying the render job’s truncation counts.

RenderPlan is frozen and is built before the batch renders, so the counts are grafted on afterwards rather than mutated in place.

path_samples_mean()[source]

Mean samples per pixel the path tracer took this render job (0 = none).

Return type:

float

record_path_samples(sample_sum, cells)[source]

Record one path-traced chunk’s samples: sum(n_p) over cells.

record_truncation(ceiling, count, cap=None)[source]

Count count occurrences of one ceiling binding.

report_truncations()[source]

Log this batch’s truncations and return the running totals.

Return type:

TruncationCounts

reset_truncations()[source]

Begin a render job’s truncation accounting.

Called by RenderLoopMixin.get_frames, so every save_video / save_frame reports its own render rather than inheriting the last one’s counts and its already-spent warnings.

restore_path_samples(state)[source]

Roll the path-sample accumulator back to state.

restore_truncations(counts)[source]

Roll the counts back to a snapshot (used by the OOM chunk retry).

Parameters:

counts (TruncationCounts)

snapshot_path_samples()[source]

The path-sample accumulator’s state, for the OOM chunk retry.

snapshot_truncations()[source]

The render job’s truncation counts so far.

Return type:

TruncationCounts