correlated/fig-cone-9.png

Visualisation · RadianceLab

Where the
fireflies come from

The renderer can draw its own sampling geometry and colour it by the place its outliers are born.

Above: sixteen photon cones sharing one apex at the light, frozen for a whole frame instead of redrawn per sample. Ordinarily they average away before anyone sees one; this is the estimator's own geometry, photographed.

Film maximum, one frozen batch
4.9×10⁴→368.7
cone alone → trio under the balance heuristic; 4562 at flat weights in between
Fold tail index
2.00
measured 1.96 / 2.00 — the variance never converges
Photon sphere, best-in-pixel
0.0%
of 1600 pixels; it never wins anywhere
Explorer coverage
12 / 31
the other nineteen slots render flat grey

01 — What a firefly actually is

A firefly is a Jacobian going to zero, and there are four ways it can do that

Every estimator in this renderer's analytic family sweeps two dimensions of a light path into a surface, intersects the camera ray with it, and divides by the co-area Jacobian of the sweep. The Jacobian sits in the denominator. A camera ray tangent to the swept sheet drives it toward zero and the sample explodes.

So a bright isolated pixel is not a mystery to be denoised. It is a geometric event with a location, and the location is computable. The useful question is not how big the outlier was but how the Jacobian vanished, because that decides whether the estimator has a heavy tail at all.

W = f / ( p · J )   J = | ( ∂x/∂a × ∂x/∂b ) · d |
One weight, one Jacobian, the whole family. Only the two swept dimensions a, b change.
How J vanishesMembersWhat the tail doesConsequence
constant3max / median 1.7–3.0No in-pixel tail at all. The tangency shows up across pixels instead, as the crossing probability collapsing
linear10, 11, 14, 16max / median 200–7000, top-1% share 20–45%Integrable. The mean converged on all 16,758 (ray, estimator) pairs tested
square-root fold7, 9, 20, 22, 23, 24W ~ ε−1/2, Hill index 2E[W²] is log-divergent. The sample variance grows like ln N and never converges
Cauchy fan8relvar × δaxis = 0.42–0.45Bounded weights, no fold. Two exactly silent rays instead — through the axis and parallel to it

The fold row is the one that changes how you read every other number on this site. Two quadric roots merge at the silhouette of the swept surface, the weight diverges like the inverse square root of the distance to that merge, and the resulting Pareto tail has index exactly 2 — measured at 1.96 for the photon sphere and 2.00 for the photon cone, over three decades of the complementary CDF. A tail index of 2 puts the second moment on the boundary of existence, so a variance estimate from a fold member is a lower bound that keeps rising as you sample.

What the renderer does with a firefly — and the one it structurally cannot see

There are two firefly instruments in the megakernel and they answer two different questions. pathTrace.comp carries eight non-finite tests. One discards a splat outright: splatToImage returns before writing anything, under the comment “discard invalid contributions to prevent pixel poisoning”. Two replace a sample with black rather than dropping it — the returned radiance and the eye-subpath result. The other five gate a connection’s add: the BDPT connection, the light tracer’s emitter and camera contributions, the internal-connection loop and next-event estimation. Separately, a large but finite sample meets a hard clamp at 1000.0 with no UI at all, applied after the MIS weight; the source records that it replaced a pre-weight clamp of 100 which had been truncating bright glossy splats the path tracer counts in full.

Neither instrument can see the fireflies this page is about. A fold weight is W ~ ε−1/2: enormous, and perfectly finite. isnan and isinf are false for every one of them, so the guard never fires on a grazing event — it is there for a division that produced a NaN, which is a different failure. Only the clamp touches a fold sample, and the clamp lives on the GPU megakernel.

The film that actually carries this family has neither. In PPIntegrator.cpp the only finiteness test on a contribution is inside the camera-side splat of strategy 17; the main film write is an unguarded, unclamped running mean. That is a decision, not an oversight — a clamp is a bias, and this family’s whole acceptance argument is a ratio against a reference that has no clamp either. It is also the reason the statistics used on the evidence page are a per-pixel median or a top-0.1 % clipped total and never a raw frame energy. The 8.44 in section 03 is one pixel arriving in a film with nothing at all in its way.

Non-finite tests in pathTrace.comp8 — 1 discard, 2 zeroings, 5 gated adds  seen
Firefly clamp, megakernel1000.0, hard-coded, post-MIS-weight
Finiteness tests on the PP film1, in the strategy-17 splat only
Clamp on the PP filmnone, deliberately
dirsweep_fig_var_vs_N.png
Variance vs NRunning relative variance for the sphere and the cone as the sample count climbs from 10³ to 1.6×10⁷. It does not settle — it steps up each time a new record weight arrives. Sphere 5.2 → 20.1, cone 4.3 → 18.9. Reporting a variance for these estimators without stating N is meaningless, which is why the acceptance statistic used everywhere else on this site is a per-pixel median ratio.

The amplitude of that tail is not only observed; it is predicted in closed form, and the prediction lands within half a percent on the top 0.1 % of the tail. Working the fold out from the sampled density gives a scale of 1.537 for the sphere and 1.011 for the cone; the measured values are 1.536 and 1.016.

The same analysis says which member is worth using where. A 40×40 pinhole map, point light, 6000 samples per pixel per strategy, colouring each pixel by the strategy with the lowest relative variance × cost:

08 photon disk — best in38.8% of pixels
09 photon cone — best in22.2%
13 direction-trio mixture — best in39.0%
07 photon sphere — best in0.0%
13 worst pixel, relvar × cost3.9 (sphere 2.7e3, cone 1.66e3)
dirsweep_fig_best_point.png
Best estimator per pixelNotice the disk's vertical silence: a column of pixels through the light where it is exactly zero and something else has to carry the estimate. Notice also that the photon sphere is never the winner in any pixel of this image — it survives in the family only because it is the cheapest member to evaluate and because mixtures need partners whose singular loci sit elsewhere.

Where these numbers come from. Every number in this section is from docs/variance/, a numpy port of the C++ estimator validated against finite-difference co-area to a median agreement of 1e-10, with Monte-Carlo means matching closed-form quadrature on all 16,758 (ray, estimator) pairs of the plane study. It is a result about the mathematics, and the renderer was not run for these figures. The renderer takes over below this line — and the fold row arrives in it, measured, in section 03.

02 — The Primitive Explorer

Ray-trace the manifold itself and jet-colour it by its own singularity

Tick one checkbox and the renderer stops estimating light and starts drawing the object it would have sampled. One representative primitive, ray-traced per pixel, deterministic, coloured blue where the camera ray meets the sheet face-on and red where it grazes. The red band is the firefly locus, drawn in the picture.

This is the difference between a theorem and an instrument. The variance study above locates the singularity in parameter space, as a condition on tangent vectors. The explorer locates it on screen, in the same scene and from the same camera you are about to render from — the difference between knowing an estimator has a grazing band and knowing where that band is going to land across your caustic.

Nothing is hardcoded per primitive. The tool evaluates x(a,b) = y(u,v) + t·ω(θ,φ) on a grid, with a and b picked from {u, v, θ, φ, t}, and tessellates the result — so the sphere, the cone, the meridian half-plane and the ruled plane all emerge from which two dimensions you chose to sweep, rather than from twelve special cases.

Screenshot — preset 35 · Render Settings ▸ PP Estimator ▸ Primitive Explorer (theory) ▸ "Trace this primitive in the viewport" ON · Jacobian resolution 80 · strategy 9 · capture the whole window so the theory readout and the 2D schematic are both in frame
Explorer · strategy 09Three things are on screen at once, and they are the reason the estimator work moved as fast as it did: the traced manifold with its grazing band, the algebraic readout of the same object, and a 2D schematic driven by the same evaluator.

What is in the panel

  • The traced manifold. Blocked AABB acceleration over the tessellated grid, so it stays interactive while you orbit. The representative light path is drawn over it — yellow node at the emitter, yellow leg along ω, white node at the scatter point — solid where it passes in front of the sheet and marching-dashed where it passes behind. The camera leg is not drawn, because you are sighting straight down it.
  • The theory readout. Swept dimensions, both tangent vectors, the Jacobian written out for that primitive (t·sinθ·|θ̂·d| for the cone), and a green Σ free = 2 swept + 1 camera-ray = 3 → UNBIASED check. Dimension counting is where this family's bugs live, so the count is on screen rather than in a comment.
  • A distance-term class. Orange EXPECTED VALUE when t is one of the swept dimensions and the transmittance is carried explicitly, blue TRACK LENGTH when t is free-flight sampled and the transmittance has already cancelled to an albedo. The viewport reflects it: swept-t primitives are dimmed along t and drawn long, sampled-t primitives are flat and drawn at about a mean free path.
  • A mixture draws its whole set. For strategies 12, 13 and 15 every member appears at once, alpha-composited, each in its own tint — and the MIS weighting toggle is reflected in the shading, so switching from flat to balance weights visibly dims each member's grazing band. The picture and the estimator agree because the singular member really does down-weight itself.
video · The estimator drawing itself
preset 35 · Render Settings ▸ PP Estimator ▸ Primitive Explorer ▸ trace ON · Jacobian resolution 80 · slow continuous orbit throughout · step the Strategy combo 3 → 7 → 8 → 9 → 10 → 11 → 14 → 16 (drag the plane angle 0 → 90 → 180) → 15 → 12 → 13, about 1.5 s on each · 20 s
Watch for the shape changing because the two swept dimensions changed, not because a different draw routine ran; then on 12 and 13, the red grazing band dimming the moment the MIS weighting combo goes from Flat average to Balance heuristic

What it does not draw

The explorer covers twelve of the thirty-one strategy slots — 3, 4 and 7 through 16: the quad, sphere, disk, cone and plane primitives and three of the four mixtures. The rest fall through to a flat grey fill: the NEE baselines, the volume-light chords, the camera-side splat, the rasterised gather, the cross-family mixture, the glossy ring, the one-dimensional group, and the entire hourglass family, which is the research this renderer exists for. The tool that best explains the estimators does not yet draw the best one; the frozen batch in the next section does, stochastically, and that is where the hourglass is photographed.

Two smaller limits, both worth knowing before a capture session. It needs an emissive quad in the scene — findEmissiveQuad failing gives the same flat grey as an unsupported strategy, so grey is ambiguous. And the manifold trace runs at a maximum internal width of 420 pixels and is nearest-neighbour upsampled to the viewport, so a full-screen capture is soft. It is cached against a camera fingerprint and re-traced only when the camera or the parameters move, which is what keeps the orbit smooth.

03 — The frozen batch

Stop redrawing the primitive every sample and the estimator becomes a picture

Normally each camera sample draws a fresh photon primitive, so the sheets average away before anything is visible. Freeze a batch of sixteen instead, share it across the whole frame, and skip the divide by N — and the estimator's own geometry appears, one hue per member, at its own brightness.

The hero image at the top of this page is that picture, and it is the clearest explanation of the family that exists anywhere in this project. Sixteen photon cones, apexes coincident at the light, each a distinct hue, each with a thin incandescent outline where the camera ray runs tangent to it. It is the same object the explorer traces analytically, except that here it was sampled: these are real primitives that a real render drew, carrying real weights, and the bright outline is a real firefly locus rather than a colour ramp.

correlated/fig-cone-9.png
Strategy 09 · preset 41 · N = 16 · all-NWhat to notice is the apex. All sixteen conics meet at one point, because the apex of a photon cone is the light and all sixteen were seeded from the same emitter. The half-angle differs per member because cos θ is the sampled dimension; φ and t are the swept ones. This camera sits almost level with the light, so the conics degenerate to near-straight wedges — exactly as predicted, the shape is camera-versus-axis dependent, and this camera picked the degenerate case.

Three modes, because they are three different pictures

pp.correlatedEstimatorCost / passWhat it looks like
0 · offa fresh primitive per camera sample1×Byte-identical to before the feature existed
1 · stippledone uniformly chosen member of the batch per sample; no 1/N, one uniform pick is already the mean1×Neighbours show different sheets — the primitives read as dithered together
2 · all-Nevery pixel sums the whole batch and divides by N drawnN×Clean hard-edged overlapping sheets, roughly N× less noise

It cost almost nothing to build, for a structural reason worth stating. sampleOnePixel already draws every photon-side random number after the camera ray is built, in every one of its strategy blocks. So correlating is a split of the RNG stream rather than a refactor: re-seed once, immediately after cam.generateRay returns, and every pixel handed batch index k reconstructs primitive k bit for bit. Not one strategy block was touched, and the mode covers strategies 0–16 and 18–30 — the mixtures, the hourglass family, the glossy ring and the one-dimensional group included; the swarm of rings and hooks in section 04b of Caustics without a kernel is that reach arriving on three strategies that did not exist when the mode was written, and it needed no code. Only strategy 17 opts out, because it is a whole-image splat pass that returns before the branch.

The count that used to be in that sentence. It read “all nineteen strategy blocks”, which was true when it was written and is a figure the source's own comment still carries at the correlated branch. Strategies 26 and 27 have blocks of their own now, and rather than print a new number that will go stale the same way, this page states the property that is actually load-bearing: every block draws its photon-side randomness after the camera ray, and the byte-identity guarantee is what proves none was missed.

The palette is deliberately shared with the explorer: primitive k gets the same hue circle the explorer uses for strategy 15's orientation set, so a sheet in the deterministic picture and a sheet in the stochastic one are the same colour.

The one way to ship this quietly wrong

The divisor is N drawn, never N that crossed the camera ray. Dividing by the crossing count inflates the image by the reciprocal of the crossing probability. That was built behind a debug flag, fired on purpose, measured, and deleted:

Frame energy, wrong divisor / reference2.1× – 6.8×
Per lit pixel, wrong / correct6.6× – 14.2×
Wrong / reference at 150 / 600 / 2400 passes4.39 / 4.45 / 4.45
Correct / reference, same passes0.96 / 0.99 / 0.99

Read the third row again. The wrong normalisation is flat to 1.5% over a sixteen-fold range in pass count. It does not flicker, it does not drift, it does not look noisy. It converges cleanly to four and a half times the right answer, and nothing in the image says so. This is the failure mode a picture cannot catch and only a reference can.

The design record's own prediction was 2.5×–3.7×, from a Python model of the cone. The renderer's answer is 2.1×–6.8× on frame energy, because the crossing probability in these presets is much smaller than in the model's geometry. Same mechanism, bigger multiplier here — so the page quotes the mechanism, and the correction stands in the record next to the prediction it replaced.

How it was accepted

Twelve films were pinned from the pre-change binary and re-rendered from the final one at 300 passes each, across three presets and ten strategies. All twelve byte-identical, checked twice — once with the debug trap compiled in and once after removing it. Then the correlated films were compared against the uncorrelated reference at 2400 passes:

Film / referenceN = 64N = 1024N = 65536
p47 s8 disk · stippled1.00871.00241.0009
p47 s18 beam · stippled—0.99931.0003
p48 s21 MIS · stippled—1.00121.0034
p47 s9 cone · stippled, raw1.12390.92218.4436
p47 s9 cone · stippled, top-0.1% clipped1.00220.99890.9985

The last two rows are the fold row of section 01 arriving in the shipped renderer. Strategy 9's raw frame energy wanders in both directions with no trend and the 8.44 is one pixel; clip the top 0.1% from both films and every fold member agrees to 0.2% at every N. That is a finding about the reference, not about correlation — the uncorrelated film is itself unconverged in its own tail at 2400 passes. Strategy 8, the one member with bounded weights, needs no clipping at all and agrees to four digits raw, which is why it is the clean unbiasedness gate.

And one thing the frozen mode is not, by design. Freezing the batch across passes converges the film to that batch's realisation, not to L. More camera samples only clean up the camera side; the light-side error is permanent. The UI says so in orange while the toggle is on. Relatedly, the per-pixel variance of a correlated pass is not an error estimate — the correlation coefficient is independent of N, so a bigger batch shrinks the amplitude of the structure without changing its shape, and any adaptive sampler or denoiser reading that variance is being lied to.

video · Sixteen sheets becoming an image
preset 41 · integrator 9 · strategy 9 · PP full resolution · Render Settings ▸ PP Estimator ▸ Correlated batch ▸ All N · Show primitives ON · Freeze batch ON · let it settle, then drag Primitives / pass from 1 to 256 over 8 s, then turn Show primitives OFF and let 200 passes accumulate · 18 s
Watch for the individual tinted conics staying sharp while their apex stays pinned to the light; then, as N climbs, the hues washing to grey as the batch starts to look like the answer; then the hard rims going soft the moment the display mode is switched off and the film divides by N again

04 — One primitive, one picture

Each family has a visual signature, and it is the row of the variance table it belongs to

These five frames are the same feature pointed at different strategies. Read them against the table in section 01: the shape you see is how that member's Jacobian vanishes, made visible, because the Jacobian is in the denominator and the bright places are where it is small.

07 · photon spherecorrelated/fig-sphere-7.png
10 · UT planecorrelated/fig-plane-10.png
Fold and linearLeft, preset 41, N = 16, all-N: nested bubbles centred on the light, each a filled disc with an incandescent rim, and the rims sear as they close on the light. That rim is the fold — two quadric roots merging at the silhouette. Right, preset 35, N = 128, stippled: a pick-up-sticks pile of straight tinted bands converging in perspective from the ceiling quad's edge into the fog, each with a hot streak running along it. That streak is the linear zero, and it is integrable, which is why the plane family is usable solo and the sphere is not.
08 · photon diskcorrelated/fig-disk-8.png
03 · UV quadcorrelated/fig-quad-3.png
Fan and constantLeft, preset 35, N = 16, all-N: blades radiating from the light's image, brightening toward the axis like 1/δ. No fold anywhere — this is the Cauchy fan, the calmest member of the family and the one that wins 38.8% of the pixels in the map above. Right, preset 35, N = 64, stippled: hard-edged parallelogram slabs of fog, evenly lit, no interior structure at all. Strategy 3's Jacobian is constant over its sheet, so there is nothing to see inside one — it is literally the light quad, translated.
correlated/fig-hourglass-20.png
Strategy 20 · preset 48 · N = 16 · all-NThe photon hourglass: the exit ray from a glass sphere revolved about the light-to-centre axis. Two things to notice. The individual swept sheets are visible as fine crossing lines, so you can count the sixteen. And every one of them piles its brightest region on the same central axial line — that is the paraxial focus, the reason the finished caustic's skeleton is already legible at sixteen primitives. The speckle along the sheets is real: the grazing guard is forced off in this display mode, because the guard's whole job is to delete the rim this picture exists to show.

05 — Two predictions that did not survive

The design record predicted every frame before it existed. Six landed as predicted; two did not, for the same reason

Section 3 of the correlated-mode design record predicted every frame in the gallery above before any of them existed, reading each prediction off the variance classification. Six landed as predicted. Two did not, and neither failure was in the mathematics — both were geometry that no preset in this repository can produce.

The disk's pinwheel is unreachable

Predicted: a pinwheel of blades with a dark hole through the light. What appeared: a fan of vertical blades hanging off the light, with a bright seam where the hole was supposed to be.

The blades are half-planes that all contain the light's axis, so a pinwheel is what you see looking down that axis. The axis is the emitter's normal for an area light and world-up for a point light, so in every scene here it is vertical — and every PP preset points the camera level. The pinwheel view would require looking straight down at the ceiling light, and no preset does. From the side it projects to a fan; on preset 41, with the camera almost at the light's own height, it degenerates further into two half-frames split by the axis. The predicted dark column is there, but neighbouring blades close over it, so it reads bright.

The quad's tangency is one scanline, not scattered slivers

Predicted: a few blinding thin slivers among ordinary patches, wherever a quad happened to be seen edge-on. What appeared: a single incandescent line straight across the frame at the camera's own eye height, carrying essentially all of the estimator's variance.

Strategy 3 translates the emissive quad along ω; it does not rotate it. A ceiling quad therefore stays horizontal at every distance, and a level camera sees every member of the batch near edge-on at exactly one height — its own. The slivers are not scattered because nothing scatters them. The same geometry explains why this family needs a batch in the thousands before anything crosses the frame: its screen footprint is small because of the edge-on geometry, not incidentally.

Why this stays on the page. Both misses came from reasoning about a sheet in isolation and forgetting that a camera has to look at it — a specific, repeatable failure mode, and the same one the triage document records after three confident wrong conclusions in a single session, each settled by one counter. The rule it left behind is the reason the two tools above exist: instrument first.

06 — The mixture, on one frozen batch

Same sixteen primitives, same seeds, same pixels. Only the edge weights changed

This is the strongest argument for multiple importance sampling anywhere in this project, and it is an argument that only a frozen batch can make. The balance heuristic changes a weight formula and draws no random numbers, so freezing the batch pins every shape in place and lets the weighting act alone: one batch, film maximum 4.9 × 10⁴ for the cone by itself, 368.7 for the trio under the balance heuristic, and every rim between them going out along its whole length.

A · flat 1/N weightscorrelated/fig-mis-13-flat.png
B · balance heuristiccorrelated/fig-mis-13-balance.png
Strategy 13 · sphere + disk + cone · identical batchThe bubbles, blades and wedges are in exactly the same places in both panes, because they are the same primitives. On the left every rim is a hard bright line and there are firefly dots between them. On the right the rims have gone soft. Nothing moved; the singular member simply stopped being trusted where it is singular.
Film maximum — cone alone4.9 × 10⁴
Film maximum — trio, flat weights4562
Film maximum — trio, balance weights368.7
Knockdown attributable to the heuristic alone12.4×

Both panes are unbiased. Flat weights are a legitimate MIS combination and converge to the same image; they are simply a bad one. What the balance heuristic does is replace the effective Jacobian with the sum of the members' densities, so a set whose tangency loci are disjoint has no zero that any camera ray can reach. In the numpy study the same change turns the folds of the sphere and the cone into Hill indices between 5 and 60, and the worst pixel of the image from 2.7×10³ to 3.9.

Correlation also changes how this reads in motion. Uncorrelated, "MIS removes fireflies" looks like isolated pixels going out. Correlated, it looks like a hot rim extinguishing along its whole length — which is what is actually happening, and is much harder to mistake for a denoiser.

video · One weight formula, twice
preset 41 · integrator 9 · strategy 13 · Correlated batch ▸ All N · N = 16 · Show primitives ON · Freeze batch ON · 300 passes · hold 3 s, then toggle MIS weighting between Balance heuristic and Flat average twice, 2 s apart · 12 s
Watch for the shapes not moving at all — every bubble, blade and wedge is pinned — while every rim goes hard, then soft, then hard again. Shoot this before anything else in the session: it is the clip that proves the frozen batch is really frozen
correlated/fig-mis-21.png
Strategy 21 · beam + hourglassA bonus the design document did not predict: correlation makes a heterogeneous mixture legible. Both primitive kinds are in the frame at once — thick straight tinted beam segments at their kernel width, and the analytic horn between them. Compare the horn's sheets against the solo hourglass in section 04: smooth here, speckled with J → 0 fireflies there. That difference is the picture of the same fact the source records numerically, that under strategy 21 the grazing guard is retired in all but name.

07 — The rest of the bench

Three more tools, one shared vocabulary

The explorer and the frozen batch are the two that changed how the research ran. Three others sit alongside them, and one of them has never been looked at.

Split A/B

Two independent estimator passes over the same scene and the same camera, composited as a wipe in the main viewport with a draggable divider, plus a radio for which side advances so you can freeze one and let the other climb. Side B's configuration is a copy of side A's with only the compared fields overridden, so a field added later stays in lockstep automatically instead of silently diverging.

Two deliberate details. Both panes show raw estimator output with the post-process and stylisation chain bypassed — that chain is single-input and carries temporal state, so running it on one side only would put a filter on half of an A/B, which is a lie. And identical settings on both sides give bit-identical panes, because the two sides share an RNG stream. That is correct and it reads as broken, so it is the first thing to test in a capture session.

never viewed  Built, headless-reachable through pp.split, and not yet seen by a person on screen — which is why it is first on the shooting list.

video · Two estimators, one divider
preset 48 · integrator 9 · PP full resolution · Compare ▸ Split A/B ON · A = strategy 20, B = strategy 18 with B min bounce 2 and B max bounces 3 · FIRST set B strategy to 20 as well and confirm both panes are bit-identical, then set it back to 18 · let both climb from 1 to 200 passes with the pass counters visible, dragging the divider slowly across · 14 s
Watch for the analytic caustic on one side of the divider and the kernel-blurred one on the other, converging at different rates from the same camera; and the bit-identity test at the head of the clip, which is the reason the rest of it can be believed

The Photon Primitive Validator (PPE)

A separate tool with a different job: it visualises sampling geometry, it does not estimate. The panel names itself — Photon Primitive Validator, at Render Settings ▸ Photon Primitives ▸ Enable — and it is as unphotographed as anything here: eleven rows in the inventory, and every single one of them graded never viewed. Take one traced sub-path, choose vertices, sweep {θ, φ, distance} at them, and watch the endpoint become a curve, a surface or a volume. Nine render modes, of which four are worth naming — the seamless surface, whose quad corners are the neighbouring real sample points rather than a bilinear guess, so curved sweeps show no diagonal seams; the ray-marched volume, which splats photons into an integer voxel grid through atomics, converts to a filterable float texture and marches it fullscreen, so a two-dimensional sweep reads as a glowing shell; the MIS double primitive, shading each cell by the harmonic mean of its own and the other primitive's Jacobian; and the analytical tracer, which ray-traces the exact closed-form surface per pixel with a per-hit exact Jacobian.

You pick both ends of the path by hand, and the panel prices the picks. One pick-mode radio puts the cursor in Camera or Photon mode; a camera pick is a viewport pixel, committed and then locked in world space along with the camera basis, field of view and aspect at pick time, so the gizmo stays where you put it when you orbit away; a photon pick is a world-space hit on a light, and the panel reports which light and whether it came from scene.lights[] or from an emissive entity. Above them sits one shared bounce budget, and this is the part worth copying: the camera and photon sliders are each clamped by the other, and the remainder is printed as its own line — green Slack: n (available for sweep), grey Slack: 0 (no sweep capacity), orange Over budget by n. The dimension count this family’s bugs live in is on the panel face, exactly as it is in the explorer’s Σ free readout in section 02.

RowWhat it isMark
PPE —
Per-leg bounce budgets and seeds
Total budget 2–16, split between a camera leg and a photon leg with the slack readout above. Each leg carries its own RNG seed, shown as an integer beside a Resample button that increments it — so a sub-path is addressable as a side and a number, and can be returned tonever viewed
PPE —
Regex path descriptors
A per-vertex type string for each leg, printed live. Not a pattern and not a filter — see belownever viewed
PPE —
Occlusion and reachability gates
Two different mechanisms sharing a heading. Occlusion depth-tests the drawn segments against the raster pre-pass and continues a blocked one at low alpha. Reachability is a real cull, run per photon inside the march shader — see belownever viewed
PPE —
Derived-path diagnostic
Answers “why is this pairing zero”: occluded photons are tinted dark red instead of being culled, and hovering one draws its whole reconstructed sub-path in magenta with red overdraw on the segments that are blocked. Disabled in the volume-march mode, which has no per-photon path to drawnever viewed
PPE —
Batch and pinning
The hover gathers every photon whose screen projection lands inside a pick radius — 5 to 200 px on a logarithmic slider, default 30 — and depth-stratifies the result to 32; the GPU picker keeps up to 1024 hits before the CPU thins them. Left-click pins that whole set, right-click clears it. A Show structure sub-toggle draws a regular 64-path grid across the swept parameter space at low alpha, independent of the cursornever viewed
PPE —
9 render modes
Disc, oriented splat, surface patch, seamless surface, transport estimate, ray-marched volume, analytical surface, MIS double primitive, analytical tracer. Modes 1–4 need exactly two swept dimensions and the panel says so in place, falling back to Disc rather than drawing something wrongnever viewed
PPE —
Volume grid march
Core GL has no atomic add on a float image and none on a four-component one, so the accumulation grid is four separate r32ui images and each photon’s rgb is added as fixed point scaled by 1024, with a fourth image counting hits. A convert pass turns it into a filterable float texture and a fullscreen pass marches it. Grid 32–256 (default 128), steps 16–256 (default 256) or auto-tracked to about twice the grid resolution; deposit either a flat 1.0 for clean geometry or the transport weightnever viewed
PPE —
Analytical primitive rendering, 4 materials
Matte, Glossy, Metallic and Anisotropic (silk), the last elongating its highlight along the parametric u direction so a swept sheet reads as brushed metal. Shaded fill and wireframe are independent toggles — with both off, nothing renders — and a smoothing option takes the fragment normal from screen-space derivatives of world position instead of the per-cell cross product, to remove faceting at cell boundariesnever viewed
PPE —
A/B harmonic-mean MIS editing
Mode 7 marches two fully independent swept primitives and draws both clouds at once; the MIS toggle replaces each cell’s own Jacobian with the harmonic mean of its own and the other primitive’s. The result is cached on a hash of all four march hashes plus the toggle, so editing B does not re-march Anever viewed
PPE —
The floating sweep editor
A separate auto-resizing window joined to the selected vertex by a dashed arrow, opened by clicking a vertex in either the schematic or the viewport. Per-dimension enable, 2–1024 samples and a range: θ is a full sphere at π, φ a full revolution at 2π, and dist scales the leg by u·R, so R = 0 collapses the vertex and the sweep closes into a solid ball. In mode 7 the same window switches between primitive A and primitive Bnever viewed

The “regex” is a label, not a pattern. buildRegex walks a sub-path’s vertices and emits one character each from a seven-letter alphabet — L light, E camera, D diffuse, S specular, V volume, G glass, e emitter — separated by spaces, and the panel prints it. Nothing is matched against it and nothing is filtered by it. It is the path’s type written down so that the eye can check the pairing it is looking at, which is the right thing for a validator to do and is not what the name promises. It is also a different object from the path-type filter on the tracer’s own Filters tab, which does filter.

The reachability cull carries this project’s byte-identity convention into a visualiser. With the gate off, per-photon visibility is left at 1.0 and the march shader’s own comment records that the output is byte-identical to the behaviour before the feature existed. With it on, every segment of the reconstructed sub-path gets an any-hit test, and the photon is culled on the first blocked one. The tests are written inside the march shader to mirror the CPU intersector exactly — the comment says same epsilons, same clip and slab arithmetic, and the same minT semantics — because a cull that disagreed with the renderer’s own intersector would delete photons the estimator can genuinely reach, and a picture of a sampling geometry that quietly drops reachable samples is worse than no picture.

Underneath it is a taxonomy: a header-only classifier that maps a set of swept dimensions to an analytical shape, unit-tested by a target that links glm and nothing else. The test binary carries 61 assertions across two files — the shape classifier and the evaluator — and returns non-zero on any failure, so it doubles as a gate.

Self-described status is ongoing, and the document says where. The analytic tracer handles sphere, cone, disk, parallelogram, parallelepiped, planar sector and ball sector; the higher-dimensional shapes have no closed form and are not traced. The rasterised MIS mode has a cache-timing bug where the density sometimes only appears after a resample, and its own notes propose deprecating it in favour of doing MIS in the analytic tracer instead.

video · Sweeping a path into a shape
preset 33 · Render Settings ▸ Photon Primitives ▸ Enable · pin a path · render mode 5 (ray-marched volume), hold 4 s, then mode 8 (analytical tracer) · drag the θ, φ and distance ranges through their full extent on each · 18 s
Watch for in mode 5, a two-dimensional sweep reading as a solid glowing shell rather than a film, because of the splat radius; in mode 8, the same object as an exact traced surface with no tessellation — watch the silhouette stay clean as the ranges move
video · Picking both ends of a path, and asking one why it is zero
preset 33 · Render Settings ▸ Photon Primitives ▸ Enable · Total bounce budget 4 · set Cam bounces to 1 and Photon bounces to 3 so the slack line reads 0, then drop the photon leg to 2 so it reads green "Slack: 1 (available for sweep)" · Pick mode Camera, click a pixel on the caustic; Pick mode Photon, click the light · read the two descriptor strings in the panel · turn on Show derived path, then Reachable only, then hover a photon and left-click to pin the bundle · 25 s
Watch for first, the slack line changing colour as the two sliders are moved against a fixed total — that number is the sweep capacity, and it is the same count as the explorer's Σ free check. Then, at the reachable-only toggle, whole regions of the point cloud vanishing at once: those photons cannot see their own origin. Then the pinned magenta sub-paths, with the blocked segments overdrawn red — the diagnostic answering "why is this pairing zero" in the picture rather than in a log

The overlay bank, and what it reveals

Fifteen overlay draw functions cover the rest of the renderer — seven of them per-integrator path and photon visualisations for the photon mapper, VCM, MLT, PSSMLT, UPBP, path guiding and manifold exploration, plus ray differentials, normal arrows, the light tree, the acceleration structures and the BDPT strategy pyramid.

The acceleration overlay is worth calling out because of what it reveals rather than what it draws. The BVH and the uniform grid are built, and the overlay is their only consumer — nothing traverses them on either the CPU or the GPU, where the tracer is a flat linear scan over all primitives. The visualiser is drawing a data structure the renderer does not use.

All of the estimator-side drawing shares one glyph vocabulary: cyan for the camera side, warm yellow for the photon side, a red centre dot for a swept or specular vertex, a dashed ring for a camera or light endpoint and a solid ring for an emitter or a surface. The same vocabulary is used by the in-app 2D schematic, the 3D overlay, and the schematics drawn for these pages — which is why a diagram here and a screenshot from the app read as the same object.

08 — What is still invisible

The interesting category is the built-and-never-seen one, so it gets a list rather than a silence

A page about visualisation should be able to say which of its own things have never been on a screen. These compile, pass their headless or numeric checks, and have never been looked at by a person. Anything odd in them is a real candidate bug, not an expected gap.

ThingStatusNote
The Photon Primitive Validator, entirenever viewedEleven rows in the inventory and not one of them has been on a screen; I have not found another cluster on this site that size. Section 07 is its roster, and the two clips there are the shooting list
Split A/B comparenever viewedHeadless-reachable via pp.split. Shoot it early, in case it is broken
Clean view (G)never viewedAnd it is incomplete by inspection — it does not hide photon dots, path overlays, the KD-tree, physics particles, the hover BSDF badge or the dolly-zoom HUD
Hourglass controls — mirror relays, J-floor slider, include-photon-pointnever viewedAll three are measured; none has been driven by hand
Explorer coverage for strategies 0–2, 5, 6, 17–30design onlyNineteen of thirty-one render flat grey — including the whole hourglass family, the cross-family mixture, the glossy ring and the one-dimensional group
Correlated rasterising gathermeasuredBuilt. Bounds each primitive in screen space and gathers per scanline instead of tracing per pixel, and it passed the strongest acceptance test here: md5-identical to the all-N tracer on 97 of 97 comparisons, the same sum in a different order. 16.7× on the quad and 5.8× on the beam at N = 256, 1.1× on the hourglass. Invisible in the truest sense — its film is bit for bit the traced one
Photon Mapper panel · "Show Jacobian"deadThe flag has no reader anywhere in the source. Since 2026-09-04 the toggle is greyed and labelled [inert], and the two placeholder switches beside it are greyed with it

The last row is the one to keep, and it is why the control was greyed rather than left alone. A control that looks like a visualiser and does nothing is worse than a missing feature, because a screenshot of it would be a lie, and this whole page is built on screenshots meaning what they appear to mean.

What none of this replaces. Every picture on this page is a diagnostic, not evidence. The frozen batch is explicitly not converging to the right answer; the display mode skips the divide by N and switches the grazing guard off; the explorer's colour is a ramp, not a radiance. The wrong-divisor experiment in section 03 is the whole argument in one number — an image that looked entirely correct at every sample count was four and a half times too bright, and only a reference film and a median ratio ever said so. These tools decide where to look. They never decide whether the answer is right.