Documentation
Illumination Analyzer
Operating reference for the non-sequential ray tracer: what the engine does, what every output means, where every pinned number came from, and what the model does not claim.
1. What this tool is
The Illumination Analyzer is a non-sequential Monte-Carlo ray tracer with two surfaces: an instant detector preview that traces in your browser as you edit, and a server Run that delivers the production result (§3). You build a scene from optical elements — planes, spheres with ports, concentrators, tubes, lenses — attach detectors, and trace rays from a source through every reflection, refraction and scatter event until the power is absorbed, escapes, or is accounted for by the run’s own bookkeeping. The outputs are radiometric: irradiance maps in W/m², far-field intensity in W/sr, and a power ledger in watts that closes to numerical precision on every run.
“Non-sequential” is the load-bearing word. In the Ray-Optics Designer, light visits surfaces in the order you list them — that is what lens design needs. Here, a ray visits whatever the geometry puts in its way, as many times as the physics demands: the fifth bounce inside an integrating sphere, the double-reflection ghost inside a window, the skew path down a lightpipe. That is the regime of illumination engineering and stray-light analysis, and it needs a different engine, different statistics, and a different kind of honesty about uncertainty. This page documents all three.
The scope fence, stated on both sides. Rays here carry power, not phase: this is incoherent radiometry. Interference, diffraction and guided modes are real physics that this tool deliberately does not model — they belong to the wave tools (the thin-film simulator, the grating tool, the mode solvers). If your two beams are mutually coherent, your features are wavelength-scale, or your “tube” is a few microns wide, the ray answer is the wrong answer, and no Monte-Carlo error bar will warn you. The converse also holds: for a 2-meter room, a baffle stack or a condenser, wave optics is intractable and ray radiometry is the correct tool.
2. How the engine works
Each run launches rays in batches from the source. A ray carries a position, a direction, and a power weight w (watts). At every surface hit the surface property decides what happens: absorption (the weight is tallied and the ray ends), mirror or Lambertian reflection, Fresnel refraction (the ray splits deterministically into a reflected and a transmitted child — see §14), or scatter. Rays that hit nothing escape and are binned into the far-field histogram. Everything is computed in double precision, and every intersection is exact against the geometry as stated: the element set is analytic, so a sphere is a sphere to machine precision, which is what lets the energy ledger close to ~1e-13 instead of ~1e-4. The one non-analytic element is the imported mesh (§8), and it keeps the same discipline at one remove: every intersection is exact against its triangles — the triangles’ faithfulness to your part is the stated, separate question.
Determinism. The random number generator is Philox4x32-10, a counter-based RNG whose reference vectors are published and which the test suite validates bit-for-bit. Every random draw is keyed by which ray, which bounce and which decision it feeds, so a run with a given seed is exactly reproducible — not “statistically similar”, identical to the last bit. This is why a share link restores the exact scene, seed and ray stream (§19), and why the engine can be regression-tested exactly.
What the preview deliberately does not do: spectral evaluation — the preview is monochromatic, one set of optical constants per scene, and an imported glass is evaluated at exactly 550 nm (§9); absorption without data — both surfaces trace imported glasses as real volume media, and catalog glass carries no absorption data, so glass is lossless on both with the advisory displayed, and the preview loses power only at surfaces; your custom materials as media — a material is evaluated once, by the server’s materials engine, so a scene with a custom-material gap refuses the preview by name and traces on the Run (§9); importance sampling toward detectors — estimates are unbiased analog Monte Carlo with the splitting and roulette rules of §11, nothing else redistributes samples.
3. The preview and the Run
This tool traces a scene on two surfaces, deliberately separated.
The preview (browser, free). The detector preview panel follows every edit: it traces the scene in your browser, on coarse bins, under a wall-clock compute budget (default 800 ms, adjustable from 200 ms to 10 s) and the design’s own convergence target σ (default 5%, adjustable 1–20%). Whichever it reaches first stops it, and the σ it actually achieved is printed on the picture either way — a stop at the budget is named as a budget stop, and a preview that collected no samples says so with the ray count rather than inventing an estimate. Editing the scene dims the preview and it retraces. Because the budget is wall-clock, how far the preview gets depends on your machine: a slower machine completes fewer batches in the same 800 ms, and the printed σ is the honest statement of what those batches bought — see §19 for what that means for a shared scene.
Beyond the map, the preview panel carries its own energy ledger in a collapsible section — the full row structure of §6, at the preview’s statistics, with the closure residual printed — and the preview map downloads as a CSV (signed in) whose header states its provenance: bins, batches, achieved σ, and the compute budget. Two things the preview deliberately does not compute: the far-field histogram and the path list. Both are results of the Run.
The Run (server, a Pro feature). One button: Run. It submits the scene to the server, which traces it at full resolution — monochromatic or spectral, set in the spectral section: a single wavelength, a flat band, a blackbody, or your own tabulated spectrum. Left at its default, the spectral section gives an ordinary source a monochromatic run at 550 nm; a luminaire-file source runs in its file’s own photometric terms, weighted by CIE photopic V(λ). The Run has its own options — seed, batch count, an optional target σ of its own, depth — and the results land in the Irradiance, Far field and Energy ledger tabs, labeled with what the run actually traced: “monochromatic” or “spectral” with the bin count, the ray count, and its σ.
Two σ owners, never two results on one surface. Every uncertainty on screen names its owner: “preview σ” belongs to the preview panel, “server σ” to a server result. A tab shows the server result when one exists, or states what the Run delivers when none does — never a mixture — and the preview keeps its own numbers on its own panel while you edit. A server result is a record of the scene it traced: editing the scene afterward never dims or alters it.
4. Quantities and units
Everything is radiometric, in SI units:
| Displayed | Unit | Definition |
|---|---|---|
| Map bins (Irradiance view) | W/m² | bin power ÷ exact bin area |
| Far field | W/sr | bin power ÷ exact bin solid angle |
| Ledger rows, detector totals | W | summed ray weights |
The divisions use exact areas, never approximations: a 32×32 map on a 2 m × 2 m target divides by exactly (2/32)·(2/32) m², and the far-field bins are constructed equal-solid-angle (uniform in cos θ and φ), so every bin subtends exactly ΔΩ = (2/n_μ)(2π/n_φ) steradians. With the default 40×32 binning that is ΔΩ = 9.817e-3 sr.
5. Error bars, and how to read them
A Monte-Carlo result is an estimate, and an estimate without an uncertainty is not a measurement. This tool treats that as a design rule rather than a footnote.
Where σ̂ comes from. The run is organized in batches. Every displayed quantity — each map bin, each ledger row, each detector total — is the mean over batches, and its standard error σ̂ comes from the scatter between batches. This is the batch-means estimator; it is itself validated in the test suite by a coverage test (over many independent seeds, the true analytic value falls inside ±2σ̂ about 95% of the time — measured 0.950).
The convergence target. The target is the median relative σ̂ over the bins your map can draw — the ones carrying at least N_min = 10 rays, which is the same set the hatching rule colors, and the same set on both surfaces (default 5%, adjustable 1–20%). The preview traces toward it inside its compute budget and prints the σ it achieved either way (§3); the server Run takes a batch count and, optionally, a target σ of its own, and prints its achieved σ on the result the same way. A run so short that no bin has reached N_min has no population to take a median over, so it reports no convergence figure at all rather than a number — and a target σ cannot be reached that way: a run with nothing to measure has not met the target, it has not measured it. Refinement never restarts: the ray stream is deterministic (§2), so raising the preview’s budget or tightening its target re-traces the same batches and keeps going — the estimate refines; it never wanders.
Hatched bins. A bin that has collected fewer than N_min = 10 rays renders hatched, not colored. With so few samples its σ̂ is itself unreliable, and coloring it would present noise with the same visual authority as a converged value. Hatching is a display rule only — the underlying tally is unbiased and keeps accumulating; the bin turns on when it earns it.
When a σ̂ exists but is withheld. A map detector that reads by next-event estimation is credited at every scatter event with the light a connection to it would deliver, and a small remainder of its power arrives only with the rays that physically land on it. Until the first such ray lands, the estimate is short by that remainder and the batch-to-batch scatter cannot see it, so a σ̂ printed then would be far smaller than the real error. On either surface the tool therefore withholds that detector’s σ̂ wherever it would have appeared — the run’s headline σ when it belongs to that detector, the detector’s card, the per-bin uncertainty view and its total — and prints a note in its place with the run’s own ray count: the uncertainty is absent, not small. The mean, the map and the ledger are untouched, and more rays close the gap. The missing remainder is the detector’s own direct-hit share of the estimate, of the order of the fraction of the scattered light that reaches it by a direct hit, and tiny for exactly the detectors this happens to; when the scattering spot is an illuminated patch rather than a pinpoint, that remainder is already smaller than the σ̂ the batches report, and that σ̂ was measured within 3% of the true value — the note still appears until a ray lands, and is then a caution rather than a correction. The automatic stop keeps to the same rule. While the detector that supplies the run’s headline σ has had no physical hit, neither surface counts its target as reached, whatever the withheld σ̂ would have said: the preview keeps tracing to its compute budget and names the budget stop, and a Run given a target σ of its own keeps going to its requested batches or its time limit and reports that instead. A next-event map reaches its target only after a ray has physically landed on it; until then a stop reports the ceiling the run hit, never the target.
No smoothing. The maps are never smoothed, interpolated or denoised. A smoothed Monte-Carlo map looks converged at any sample count, which is exactly the lie this tool is built not to tell. What you see is the estimator, bin by bin, with its honest noise.
6. The energy ledger
The Energy ledger view is the run’s conservation statement. Every watt the source emits ends in exactly one of these rows:
- Absorbed, per element — weight tallied at absorbing surfaces and at lossy reflectors (a ρ = 0.9 wall absorbs 10% of each arrival).
- Escaped — rays that left the scene (these also feed the far-field histogram; the two agree to 1e-12 by an engine invariant).
- Roulette balance — the Russian-roulette rule of §14 kills some low-weight rays and boosts the survivors; both sides are ledgered so the expectation is exactly preserved.
- Depth-capped — weight still alive when a ray hits the generation cap. It is reported, never silently dropped. If this row is more than a rounding artifact, raise the cap (§14).
The closure residual — emitted minus the sum of everything above — is displayed and is typically at the 1e-13 level: floating-point summation error, nothing physical. A ledger that closes does not prove the physics is right (a biased scatter model conserves energy too — the statistical gates in the test suite exist for that), but a ledger that fails to close proves something is wrong. It is the cheapest, strongest invariant a transport code has, and it runs on every batch of every run you do.
Where the ledger lives. In two places, each labeling its owner (§3). The preview panel carries its own ledger in a collapsible section — the same rows, at the preview’s statistics, with its closure residual printed beneath: a floating-point sum check on the preview’s own accumulation, stated as what it is, never asserted to be zero. The Energy ledger tab shows the server Run’s ledger at the server’s own depth and precision. The two are the same bookkeeping at two stopping points, and neither ever stands in for the other.
7. Elements and geometry
A scene is built from these element kinds, each carrying exactly one optical property — the imported lens design is the one exception, a single read-only element carrying a whole mapped system (§9):
| Element | Geometry | Typical use |
|---|---|---|
| Rectangle | flat rect, half-width × half-height | targets, walls, apertures |
| Disk | flat disk, radius | catchers, stops |
| Sphere with port | full sphere, optional circular port by area fraction | integrating spheres |
| CPC | compound parabolic concentrator wall, exit radius a′ + acceptance θₐ | nonimaging collection |
| Baffled square tube | square section, length, 0–N interior baffles with clear apertures | stray-light control, lightpipes |
| Plano-convex lens | spherical cap + plane, derived exactly | condensers |
| Biconvex solid | intersection of two spheres (CSG) | thick-lens transport |
| Box | closed rectangular solid, center + half-sizes | enclosures, occluders, glass blocks |
| Cylinder | closed capped cylinder, base + axis + radius + height | rods, pillars, drums |
| Cone | closed capped frustum, two radii (tip allowed) | hoods, tapered baffles |
| Annulus | flat ring, inner + outer radius | ring baffles, apertures with a hole |
| Tube wall | open cylinder side, no caps | lightpipes, shrouds |
| Baffled round tube | round tube with 0–N annular baffles | stray-light control, the round twin of the square tube |
| Combined solid | two closed solids joined by union, intersection or difference | drilled plates, cut lenses, compound enclosures |
| Imported mesh | watertight triangle mesh from an STL file in your library, placed by position + rotation (§8) | housings, brackets, reflector shells |
| Imported lens design | a saved Ray-Optics Designer design, mapped once on the server to glass solids with barrels and mount shoulders (§9) | ghost and stray-light analysis of your own lens |
Orientation is exact by construction. Elements point along one of the six coordinate axes, with an optional tilt. This is deliberate: the six axis vectors are exact (0, ±1), whereas a general rotation matrix built from angles carries rounding (sin 180° = 1.22e-16, not 0), and the engine’s verification fixtures demand bit-exactness. The tilt path is only engaged when the tilt is nonzero.
One property per element. A “room” is not one element with six materials — it is six rectangles, each with its own reflectance. This keeps every element’s physics readable at a glance and matches how the energy ledger reports absorption (per element).
The port fraction. The integrating-sphere port is specified as the fraction f of sphere area removed, not as a half-angle, because f is the quantity the sphere-multiplier theory is written in (§18) and because the conversion cos θ_port = 1 − 2f is exact in that direction. A 5% port is exactly cos θ_port = 0.9.
Why no cylinder at P1. The P1 solid set is ruled: analytic surfaces whose intersections the engine computes exactly, with the full sphere as the only CSG leaf. A round tube is a P2 element; the baffled tube is square-section, which for baffle logic is the same physics (successive apertures clipping the skew-ray population) with exactly computable geometry today.
Combining solids. The combined-solid element joins two closed solids — sphere, box, cylinder or cone — by union, intersection, or difference. The result is traced exactly: the engine intersects the ray with each solid’s analytic surface and resolves the boolean per crossing, so a drilled plate or a lens cut from two spheres is machine-precision geometry, not a mesh approximation. The builder refuses degenerate combinations (two coincident canonical surfaces in one boolean) at build time rather than producing a shape whose surface is ambiguous.
8. Imported meshes
The elements above are analytic on purpose (§21). Real housings, brackets and reflector shells live in CAD, and this tool imports them the honest way: as the watertight STL your CAD package exports, stored in your account’s mesh library (a Plus feature — everything else in this tool stays free), and placed in scenes like any other element. Sign in and the library panel lists your parts; viewing and deleting them needs only the sign-in, importing new ones is the Plus half.
What a mesh is here, said plainly. A triangle mesh is a faceted boundary: surface positions are exact to the mesh’s chords, and normals are facet normals — the engine reads the geometry your file states and interpolates nothing. A specular reflection off a facet leaves at the facet’s angle, so a chord-normal error δθ deflects a specular child by 2δθ, at full strength in ghost paths; results converge to the smooth-surface answer only as the mesh is refined. Two statements belong together here, and the second is the one that is easy to miss:
- The energy ledger still closes on mesh scenes — bookkeeping is exact on whatever geometry is loaded, and the test suite gates it at 1e-11.
- Ledger closure is therefore not evidence of geometric fidelity. It proves no ray was lost, not that the mesh resembles the intended part. Check fidelity against refinement, or against an exact-primitive twin where one exists.
Refused or repaired, never patched silently. The import validates by building the mesh, and the split is strict. Refusals name their counts: a file with open edges, non-manifold edges or inconsistently wound triangles is refused with each count stated — an open shell leaks rays behind it and every downstream tally would be quietly wrong, so there is no best-effort trace. Exactly two repairs are allowed, both stated on the row: an inward-wound file has its orientation flipped (detected by signed volume — deterministic and exact), and exact-zero-area triangles are dropped with their count (they carry no geometry). Vertices are welded by exact bit-equality, never by a distance threshold: moving vertices to close a shell would be a silent geometry repair, and a clean CAD-kernel export of a single solid body welds closed as written. That last phrase is the practical guidance: export one solid body per file. A whole assembly written into one STL arrives with non-manifold edges where the parts meet and is refused with the counts; export the parts separately and place them as separate elements. One mesh element is one closed shell, and a multi-shell file is refused naming its shell count. Self-intersection is not checked — a stated limitation. Both binary and ASCII STL parse (binary is detected by size arithmetic, so a binary file whose header happens to begin with the text “solid” still parses correctly), and the engine reads geometry only: the attribute bytes some packages use for color are ignored.
Units are a declaration, not a preference. STL carries no units, so the import requires one: mm, cm, m, µm or in (the inch is exactly 0.0254 m, by definition). There is no default and no “auto” — a wrong unit is a scene at the wrong scale that traces perfectly and answers nothing. The declaration is part of the part’s identity: a mesh is stored by its file hash and its declared unit, so the same bytes declared in mm and in inches are two different parts, and re-uploading a file already in your library under the same unit is the same import, not a second copy.
Placement. The part is traced in the file’s own coordinates, moved and turned by the element’s placement: a position (in meters) and a rotation stated as an axis and an angle (in degrees). Zero position and zero angle trace the file exactly as it was exported — bit for bit, by construction. The same library part may be placed as several elements, each with its own placement.
Limits, all stated. The engine traces at most 300,000 mesh triangles per scene, summed over mesh elements — two placements of one part count twice, because both are traced. One file is at most 16,000,000 bytes; your library holds at most 50 meshes and 250,000,000 bytes in total. A mesh carries one surface property, chosen from the same list as every element — mirror, absorber, Lambertian, a scatter lobe (§10), or the Fresnel/coating ladder. What a mesh cannot be, each refused by name: a detector (a map needs a canonical binning a mesh does not have), a source, or an operand of a combined solid.
Tracing, and which engine is fast at it. Ray–triangle intersection uses a watertight algorithm (Woop, Benthin & Wald 2013 — reference below): a ray cannot slip through a shared edge or vertex of a closed mesh, which is exactly the property containment depends on. Each mesh gets its own acceleration structure, and — the standing discipline of this engine — it is gated to be bit-identical to the brute-force intersection scan, in both engines, so speed never changes an answer. The browser preview traces mesh scenes directly. One honest asymmetry to know: on mesh scenes the browser’s worker pool is measurably the faster tracer of the two engines, so the reason to press Run on a mesh scene is what only the Run gives — the spectral machinery, the CSV exports, the full path list, and a converged record that does not depend on whose browser is open — not raw speed.
Where meshes travel. Share links refuse a mesh scene with the reason stated: a link carries the scene, and a mesh is a file in your library, not a scene value. Meshes travel in saved designs, which reference your library row. Two consequences, both stated in the tool: deleting a mesh from the library means a saved design that uses it refuses to run until the file is imported again; and a mesh’s parsed geometry lives in the browser tab that imported it, so a scene reopened in a later session stops with the asset named until you import the file again — same bytes, same declared unit, the same library row.
9. A lens design in the scene
Ghost and stray-light analysis is most useful on your lens. The import panel’s Import from a lens design entry (a Plus feature) lists your saved Ray-Optics Designer designs; pick one and its prescription is read from the design and mapped to scene geometry — once, on the server, at import. What arrives in the editor is one read-only element carrying the whole mapped system. To change it, change the design in the Ray-Optics Designer, save it, and import it again.
What imports, and what refuses by name. Centered, refractive, spherical or plano systems import: curvatures of either sign, thicknesses, per-gap glasses, semi-diameters, the stop. Everything else refuses by name, with the surface index: conic surfaces, polynomial aspheres, mirrors, tilts, decenters and bends, toroids. The refusal posture is the platform’s — name the first offending surface and stop. The import never silently simplifies a system, and the engine never traces a partial one: a “close enough” import that dropped a surface would fabricate an optical system under your design’s name.
Glasses are the Ray-Optics Designer’s own. Catalog glass resolves through the shared 399-glass catalog — the same evaluator the lens tool itself uses, one implementation, so the index this tool traces is bit-for-bit the index that tool displays. Model glass (n_d/V_d) imports with its provenance stated; a constant index imports with a displayed “no dispersion model” advisory; and a gap made of your own custom material resolves from your account at import — if the material is not yours to read, the import blocks naming it rather than substituting anything. Two refusals worth knowing: inline tabulated (λ, n) media refuse by name — the lens tool’s discrete lines are compared, never interpolated, and this engine evaluates at wavelength-bin centers, so importing them would manufacture dispersion the data does not state; re-state the data as a custom material (which carries stated interpolation rules and a validity window) and import that. And bulk absorption of catalog glass is not modeled — internal-transmittance data is not in the catalog, so imported glasses are lossless with the advisory displayed. No absorption is ever invented.
Apertures are your numbers. Every surface needs a semi-diameter; where the prescription omits one, the import asks — your numbers, in millimeters — and will not guess. Behind that rule is the mapping’s honesty: each glass gap becomes one solid with its two optical faces at their stated semi-diameters, a barrel, and — where one face is smaller — a flat annular shoulder at the stated aperture, the standard element drawing convention. The alternative (extending the optical surface out to the barrel) would fabricate optical aperture your prescription never stated, so it is not what the mapping does. Barrels and shoulders are absorbers by default — the conservative stray-light assumption, editable per element, because a polished or painted barrel is your call, not ours. The stop imports as an annulus absorber at the stop plane (a displayed default size, editable — it is a baffle you own). The import offers a detector at the image plane, and imports no sources: a prescription has no illuminant.
Two run settings move with the import. The moment a scene gains an imported lens, two of the design’s run settings are bounded, because every face of every element splits the rays that reach it and the split tree grows geometrically with the generation cap: the roulette floor is set to 1e-6 of the launch weight if it was off, and the preview’s rays per batch are reduced to 100 if they were larger. Both are caps, never assignments — a floor you had already set and a smaller batch you had already chosen are kept — and they are applied at the import itself, so a saved design that holds a lens reopens with the settings it was saved with. A Run on the server opens its own options on the same floor for a scene that holds a lens; switch the floor off on either surface and a note under the control says so.
Cemented interfaces are one interface. A cemented doublet maps to two glass solids in certified contact, and a ray crossing the cement pays exactly one Fresnel event on the glass-to-glass index step — never a fictitious glass–air–glass pair at zero thickness. The difference is the whole reason cemented achromats exist: at the d line, the cemented interface of an N-BK7/N-SF5 doublet reflects 0.24 %, where its two glasses would pay 4.2 % and 6.3 % apiece at a glass–air face — the fictitious air film would overstate the cement’s ghost reflectance roughly forty-fold. Both engines’ numbers are gated against the closed-form Fresnel value computed from the same glass evaluators.
Where it traces — the boundary to know. Both surfaces trace an imported lens. The preview traces the mapped system in your browser as you edit: the glasses arrive as media solids, each evaluated at exactly 550 nm — one refractive index per glass per scene — so ghost paths, stray light and the stop’s shadow appear at preview statistics (coarse bins, the compute budget, the printed σ; §3). A Run (a Pro feature, §3) traces the spectrum you set; where the two differ on a lens scene, the glasses’ own dispersion is the difference. Three lens scenes trace only on the server, each refused by the preview by name rather than traced partially: a system with a gap bound to one of your custom materials (a material is evaluated once, by the server’s materials engine — the preview evaluates catalog glasses, model glasses and constant indices); a scene holding more than one imported prescription (each import is certified on its own, and whether two imported systems occupy the same space is a question the server engine answers); and a prescription mapping to more than 32 lens elements (the preview’s per-scene ceiling, stated in the refusal). A refusal is always whole-scene: the preview never traces a lens-bearing scene without its glass, because tracing around an absent medium would fabricate an optical system under your design’s name. The 3D view still shows you the lens: an outline drawn from the prescription’s own surface equations at each face’s own semi-diameter. The outline omits the mount geometry (shoulders, barrel) that the traced solids carry, and the inspector says so. Advisories from the mapping — the constant-index note, the lossless-glass note, a plano face whose stated semi-diameter records clear aperture only — are captured at import and shown on the element, because they are facts about this mapping, not about any later run. Share links refuse a lens-bearing scene with the reason stated (the fragment is mapped on the server from a saved lens design; a recipient imports from their own account); saved designs carry the imported system and reopen with it.
One boundary in the other direction, stated on both tools’ pages: results never flow back. This tool traces your lens non-sequentially for illumination and stray light; it does not return sequential analysis to the Ray-Optics Designer.
10. Surface scatter models
Real surfaces are neither ideal mirrors nor ideal diffusers. Between the two sit measured scatter lobes, and this tool carries the three models a stray-light analysis actually uses. All of them are directional scatter: the scattered ray leaves in a random direction drawn from the model’s lobe about the specular direction, with the randomness handled by the same deterministic, reproducible machinery as everything else (§2).
Harvey–Shack lobe. The workhorse empirical model of stray-light engineering. Its BSDF is shift-invariant in direction-cosine space:
BSDF(Δβ) = b₀ · (1 + (Δβ/l)²)^(s/2)
where Δβ = |β − β₀| is the distance from the specular direction in direction cosines, b₀ [sr⁻¹] sets the height, l the shoulder, and s < 0 the falloff slope. The parameters come from cited literature or from your own fit — the tool never invents them. The model’s defining property, and the reason the community uses it, is that one parameter set predicts the lobe at every incidence angle: oblique incidence shifts the lobe in β-space rather than reshaping it (Krywonos, Harvey & Choi 2011; Fest ch. 4).
ABg lobe. The classic three-parameter cousin (A, B, g), kept from the first release: same directional-scatter machinery, different lobe shape.
Your own tabulated BSDF (a Plus feature). Upload a two-column text file (Δβ, then BSDF in sr⁻¹; # comment lines and blank lines are ignored, and a downloadable template shows the exact format) and measured scatter data becomes a surface. The upload validates before anything is applied: a file that breaks a rule is refused with every broken rule named, and a valid one shows a summary — rows found, the Δβ range, the tail semantics — with an explicit Apply beneath it. Between your points the model interpolates log-log — each segment is a power law, the scatter community’s convention. Below your first point it holds the first value; beyond your last point it is zero: your data’s range is its validity window, and the tool will not fabricate a tail you did not measure. The truncation is stated on the surface and is included in the scattered-energy accounting. The preview and the Run apply the rule identically: no scattered direction is ever drawn beyond your last point, no next-event connection is made there, and the scatter a real surface would have sent there is counted as absorbed. The region your table does not cover grows with the angle of incidence: the tool’s own template, which ends at Δβ = 1, covers the whole outgoing hemisphere at normal incidence and leaves 59% of it (by projected area) uncovered at 75°. A tabulated BSDF is also a one-incidence statement: the profile you measured at one angle is shifted, not reshaped, to every other angle, an extrapolation the engine cannot check — and for a surface whose scatter rises toward grazing incidence, a table taken nearer normal under-predicts grazing stray light, an error of known sign whose size only incidence-resolved data could give. Applying a table stores it in your optical-data library, and a saved design references it — reopening the design brings the data back. Sharing does not: a link carrying a tabulated BSDF is refused with the reason stated, because a link is anonymous transport and your data is yours to supply.
Where the scattered energy comes from — two modes. Every lobe model needs to say how much power scatters versus how much is absorbed:
- Absolute (the default for measured data): the scattered fraction is the lobe’s own integral — the total integrated scatter, TIS — computed from your parameters at each incidence angle. Here b₀ (or the table’s values) are absolute measurements, and the scene refuses at build if the lobe integrates to more than 1 (a lobe that scatters more than arrives is bad data, and it is refused by name, not renormalized).
- Lobe-shape: you set the scattered fraction yourself and the model supplies only the shape. Useful for teaching and what-if work when you know roughly how scattering a surface is but have no absolute measurement.
The remainder in both modes is absorbed at the surface and appears in the ledger’s per-element absorption row, as always.
11. Next-event estimation: how dim maps converge
A deep baffle tube feeds its exit detector through many scatter events, and each scattered direction has only a tiny probability of pointing at the detector. Pure analog tracing converges painfully there: most rays die without ever reporting. Next-event estimation (NEE) is the standard remedy, and any irradiance map can opt into it.
With NEE on, every scatter vertex additionally sends a deterministic contribution toward the map: the power that this vertex would deliver to a map bin, weighted by the lobe’s own probability of scattering that way and by whether the straight line to the bin is unobstructed. The physical ray still continues as before. To avoid counting twice, the physical arrivals and the NEE contributions are combined with multiple-importance weights that provably sum to one — the estimate stays unbiased, and the test suite gates exactly that: a run with NEE on and a run with NEE off agree within their error bars on every fixture, while converging at very different rates.
Two honest boundaries: NEE helps scattered light onto maps that opt in; specular ghost paths (§14) are untouched — they arrive with analog weight and no competitor, which is what keeps the ghost ladder exact. And an NEE contribution is blocked by any surface in the way, mirrors included: occlusion is geometric, not optical.
12. Sources
Six source kinds, all specified by total flux Φ in watts:
- Isotropic point — uniform over 4π sr. Intensity I = Φ/4π.
- Cosine point — Lambertian-lobed point source, I(θ) = I₀ cos θ with Φ = π I₀. The classic model for a small flat emitter.
- Lambertian disk — area source of radius r, radiance L uniform; Φ = πL·πr².
- Lambertian sphere — spherical shell emitter; Φ = πL·4πr².
- Collimated disk — a beam: parallel rays over a disk, Φ spread uniformly by area.
- Lambertian rectangle — area source over a rectangle, radiance L uniform; Φ = πL·A with A the rectangle area. The extended-source model for panels and windows, and the source behind the interreflection benchmark family in §20.
The flux conventions matter more than they look: they are what the analytic benchmarks in §18 are exact against, and they are what makes results comparable across sources (“the same 1 W through a different emitter”).
13. Detectors
Any absorbing element can carry a detector: a total-power bucket (W), a power-per-generation ladder (the bucket split by how many surface interactions each arrival had — this is what resolves a ghost sequence, §14), or an irradiance map (nx × ny bins over the element, W/m²).
Detectors are one-sided, and this is physics, not convention. An irradiance detector tallies only rays arriving against its normal — the side it faces. During verification this rule was found the expensive way: a detector inside a ρ = 0.9 integrating sphere that accepted back-side hits read +19% high, because the back of the patch sees the brightly lit far wall. The transport was unbiased; the measurement was wrong, and two independent reference tracers agreed against it. The practical consequence: a detector facing away from the light reads exactly zero. The tool checks for this (a preset-level gate plus a runtime advisory when a detector records nothing while the ledger shows power arriving), but when you build your own scene, check the detector axis first.
Map bins carry two numbers per bin: the power-weighted irradiance estimate, and the count of rays that contributed. The count drives the N_min = 10 hatching rule of §5 — it has to be a true ray count, because with ray splitting and roulette a bin’s power alone cannot tell you how many independent arrivals produced it. The count is never used as a variance formula denominator; σ̂ always comes from batch statistics.
Detectors attach to absorbing surfaces only. A detector on a mirror would be a measurement that perturbs nothing and means nothing; the scene builder refuses it rather than silently ignoring it.
14. Ghost paths, splitting, and roulette
At a Fresnel surface the ray does not randomly choose reflection or transmission — it splits deterministically into both children, each carrying its exact Fresnel share of the weight. Splitting is what makes ghost analysis work: every specular sequence exists in every run, at exactly its analytic power, rather than being an event you hope the sampler visits.
The ghost ladder, exactly. Send a collimated beam through a plane glass plate (n = 1.5, so each surface reflects R = 0.04 exactly at normal incidence, T = 0.96). The emerging families are geometric in R:
| Order | Path | Power (Φ = 1) |
|---|---|---|
| direct transmission | T² | 0.9216 |
| first transmitted ghost | T²R² | 1.47456e-3 |
| second transmitted ghost | T²R⁴ | 2.3593e-6 |
| first reflection | R | 0.04 |
| second reflected ghost | T²R | 0.036864 |
| third reflected ghost | T²R³ | 5.8982e-5 |
The first ghost-to-direct ratio is R² = 1.6e-3 — the number every stray-light course starts from. The engine’s acceptance suite pins all six orders to a relative error below 1e-12, and the Ghost ladder preset (§18) shows them in the per-generation detector view, live.
Splitting needs a brake. Every split doubles the ray count; an enclosure with reflective walls would grow paths forever. Two mechanisms bound the run, both ledgered:
- Generation cap (default 32): a ray ending here has its remaining weight reported in the ledger’s depth-capped row — visible, never vanished. For a high-multiplier integrating sphere the cap must be large because power genuinely survives hundreds of bounces. The cap is set separately on the two surfaces: the preview traces at the design’s own generation cap (the integrating-sphere preset raises it to 4000), while a Run on the server takes the generation cap entered in its own options, which starts at 32 and can be raised to 512 and no further — so whatever survives a Run’s ceiling is exactly what its depth-capped row reports.
- Russian roulette (optional weight floor): below a chosen weight, a ray survives with probability ½ at twice the weight — an unbiased trade of variance for time. Both the killed weight and the boost are ledger rows, so the expectation is preserved and the trade is visible. Whether ghost work wants a floor is a property of the scene, not of the task. Through a single element the tree stays thin, two segments per generation: the plate above costs 29 segments per launched ray at the Ghost ladder preset’s cap of 14 and 65 at a Run’s cap of 32, and the Plano-convex condenser preset is within a few segments of it, so leave the floor off there — every order the cap admits is exact, down to 1e-42 of the launched power at generation 32, and a floor would turn every ghost order below it into a sampled quantity, one fair coin per generation the path spends there (at a floor of 1e-6 the six orders in the table are still exact, and the next one, 9.4e-8 of the launched power, is the first that is sampled). Where the reflected children keep meeting refracting faces, as they do in an imported lens of several elements in front of a cover glass and a reflective sensor, the tree grows about 2.4× every two generations through a cap of 20 and faster past it: at a cap of 32 an imported Cooke triplet (the 1896 design among the Ray-Optics Designer’s presets) does not finish a Run inside its time limit with the floor off — its first two launched rays alone cost 36 million segments, the same count in both engines — and costs 981 segments per launched ray with a floor of 1e-6 of the launch weight, every ghost above the floor still exact, the tail below it sampled, and the ledger’s roulette rows showing the trade. Set the floor to 1e-6 for a lens cavity; importing a lens design sets it. Like the cap, the floor is set on each surface separately: the preview takes the design’s own floor, and a Run on the server takes the floor entered in its own options.
15. The path list: every way the power got there
Any detector can record paths — the switch sits on the detector itself, whatever your tier. The table it feeds is a result of the server Run (a Pro feature): a server run keeps, for each recording detector, a table of every distinct route the power took to arrive. This table is not a sample of routes, it is the routes themselves, because the engine’s deterministic splitting (§14) makes every specular sequence exist in every run at exactly its analytic power. The preview deliberately builds no path table; the product’s own boundary line says why the split falls here: “The preview shows where the light goes. The path list tells you why.”
Reading a signature. A path is written as a space-separated list of surface index + event letter, in the order the ray met them: R reflected at a refractive surface, T transmitted through one, M reflected at a mirror, S scattered directionally, L scattered Lambertian. Emission is not listed; arriving at the detector closes the path. So in the ghost-ladder preset, 0T 1R 0T reads “in through the front face, reflected at the back face, back out through the front face” — the second reflected ghost of §14, and its row carries 3.686e-2 W for the 1 W source, which is exactly the T²R = 0.036864 of the §14 table. The empty signature is real too: it renders as “direct (no surface events)” and is often the dominant row.
“Ranked by power,” honestly. Rows are sorted by mean power, largest first. The mean is a batch mean (§5), and each row carries its own standard error σ̂ from the scatter between batches — printed beside the row, absent (never zero) when the run has only one batch. The printed σ̂ is what keeps the ranking honest: two adjacent rows inside each other’s error bars are a statistical tie, and this run cannot tell you which one is really larger. Treat the order of such rows as arbitrary; run longer if the distinction matters.
The table closes. The rows plus the one overflow row below them sum to the detector’s own total — not approximately, but as a regrouping of the identical arrivals the detector itself tallied, so the two agree to the run’s own floating-point round-off. That closure is machine-checked on every recording fixture in the engine’s test suite; a recorder that dropped anything breaks it at the percent level.
The cap, and what overflow means. The run keeps the first 4096 distinct signatures it encounters, in a deterministic first-encounter order (the same run always keeps the same set — admission never depends on an interim noisy ranking). Everything beyond the cap is collected in a single “unclassified” row, with its power and arrival count displayed. Nothing is dropped: if a strong path shows up late and lands in the overflow, you see its power sitting there, which is your cue that this scene has more distinct routes than the table holds. The whole table — every retained row plus the overflow — exports as a CSV file from the run result.
Ghost ladders, itemized. The per-generation ladder of §14 told you how much power arrived after k interactions; the path list tells you which k interactions. On the ghost-ladder preset the six §14 orders appear as six rows whose powers match the per-generation view row for row — the same tally, regrouped by route instead of by depth.
16. Polarization: why oblique reflections split s from p
Reflection at oblique incidence is polarizing physics. At an interface, the reflectance for light polarized perpendicular to the plane of incidence (s) and parallel to it (p) are two different Fresnel coefficients, and they separate fast with angle. For the §14 teaching plate (n = 1.5, a stated teaching value): at normal incidence both polarizations reflect 4.0%; at 45° the s reflectance is 9.2% while p is 0.85%, so about 92% of the reflected power is s-polarized; and at Brewster’s angle, θ_B = arctan(n) = 56.31°, the p reflectance is exactly zero — a single reflection there is 100% s-polarized, with R_s = ((n²−1)/(n²+1))² = 0.148.
That matters for stray light because ghost paths are built from exactly these oblique reflections. A path whose surfaces keep favoring the same polarization carries more power than an unpolarized average predicts: the engine’s own gate scene, a path with two aligned Brewster reflections, carries exactly twice the power the unpolarized-average model assigns it (the frozen ratio is 2.000000000000000). An engine that ignored polarization would be a factor of 2 wrong on that ghost.
What the engine carries. Every ray carries its power w and its s-excess q = w_s − w_p in the local s/p frame of each specular event. Sources emit unpolarized (q = 0). Scatter — Lambertian or directional — depolarizes: the scattered child restarts at q = 0, the standard treatment for surface scatter. An ideal mirror reflects both polarizations equally (a stated idealization; a real metal surface is not an ideal mirror). On scenes whose specular events are all mirrors or normal-incidence, q stays exactly zero everywhere and the arithmetic is bit-identical to the pre-polarization engine — nothing moved on those scenes when this feature arrived.
The s-polarized column. The path list shows each path’s s share when the scene gives it something to say: the column appears when at least one row’s share is half a percentage point or more away from 50%. A path far from 50% owes its strength (or weakness) to polarization; a scene of mirrors and normal hits would print 50% on every row, so the column stays home.
What the model is, with its measured error. The engine carries the linear power split (w, q) and drops the cross-polarized interference term that a full Mueller-calculus treatment would rotate back in at the third oblique surface of a chain. Through any one or two oblique specular surfaces this is exact (machine-verified against a full Mueller reference to within 2e−16). From the third oblique, out-of-plane surface on, it is an approximation: the worst of five measured three-mirror chains errs by up to 9.8 % of that path's power, where ignoring polarization entirely errs by 18 times that power; on every chain measured the model is the closer of the two. One honest caveat with real numbers: for a path that polarization nearly extinguishes, the relative error can be large even though the absolute error is tiny. In a measured three-surface chain arranged near a polarization null, the engine reports 4.0e−4 of the emitted power where the exact answer is 2.8e−5 — fourteen times the true value, but an absolute error of 3.7e−4 of the source. Read near-extinguished rows as “very small,” not as precise values. Circular polarization and phase retardance (the phase shifts of total internal reflection, metal films) are not carried at all in this model; paths whose power depends on them need the full Mueller treatment, which this stage deliberately does not include.
There are no per-polarization irradiance maps; the s share on the path list is the one polarization-resolved quantity displayed, because a ghost’s polarization is where the physics explains something.
17. Your own R/T data on a surface
A refractive surface normally computes its reflection/transmission split from its two indices (§14). With Use my own R/T data (a Plus feature), you replace that computed split with your own table — measured data, a vendor curve you have digitized, or a table computed elsewhere — and the engine transports power through your numbers instead of its own.
The honesty frame, first. Your data is used exactly as given. At your angles and wavelengths the engine reproduces your values bit for bit; between them it interpolates by a stated rule; beyond them it extends by a stated rule; and where it must assume something you did not supply, the assumption is displayed on the surface. It never checks your data against physics, never corrects it, and never re-derives it.
The block. The upload accepts a photizon_rt_table JSON block:
theta_deg— incidence angles in degrees: at least 2 and at most 256, strictly increasing, from 0 up to (not including) 90.lambda_nm— wavelengths in nm: at least 1 and at most 64, strictly increasing.polarization—"sp"(arraysRs, Ts, Rp, Tp) or"unpolarized"(arraysR, T).sides.front, and optionallysides.back. Each array is indexed[wavelength][angle]: rows are wavelengths, columns are angles.source— free text saying where the data came from; displayed verbatim beside the surface, never parsed.assumed_media— the media the data assumes on each side; display only, never used in computation.
Angles are measured in each side’s own bounding medium. The front table’s 40° column is 40° in the medium on the front side of the surface; the back table’s 40° column is 40° in the back-side medium. The two sides are independent tables on one shared angle axis — there is no conjugate-angle relationship between their columns, and none is needed.
What is refused, and what is quietly tolerated. Malformed data is refused with the exact cell and rule named — refused, not warned, and never corrected. The one tolerance: values may sit up to one part in a million outside [0, 1], because correct physics computed in floating point genuinely produces such values — under total internal reflection the platform’s own thin-film engine returns R = 1.0000000000000004, one bit above exact 1. Accepted values are clamped once to [0, 1] on read (which changes nothing for any physically-sensible datum), and each cell must satisfy R + T ≤ 1 + 1e−6, checked on the values as you gave them. A genuinely out-of-range value — a measured R of 1.002, say — is refused: if your instrument said that, you should see the refusal, not a silent correction.
Between and beyond your angles. The engine interpolates linearly in cos θ between your nodes. Toward normal incidence, below your first angle, it holds your first value constant — exact to second order in θ, because R(θ) is an even function; measured against the engine’s own coatings, a table starting at 5° read at 2° errs by 1.4e−4.Toward grazing, beyond your last angle, it runs a straight line (in cos θ) to the exact grazing limit R = 1, T = 0, which every passive surface obeys; that is a stated model, and its measured error beyond an 85°-end table is up to 4.0e−2 at 87–88.5°. If near-grazing behavior matters to your scene, supply data closer to 90°. In wavelength, values interpolate linearly between your nodes and hold the end value beyond them, with a displayed advisory whenever the run’s wavelength leaves your table’s range.
One side or two. If you supply only the front side, the engine fills the back in two steps that are deliberately different in kind:
- Transmission is filled by reciprocity: T from the back at the conjugate angle equals T from the front. That is a theorem (it holds even for absorbing coatings), and the fill reproduces the engine’s own reversed-stack truth to ~1e−15.
- Reflection is filled by the assumption R_back = R_front. For a lossless coating this is exact; for an absorbing one it is genuinely wrong — measured at 1.9e−2 on a 40 nm silver film. Because the engine cannot know whether your data is lossless, every single-sided surface displays the advisory: reverse-side reflectance assumed equal to forward, exact only if the coating is lossless; supply back-side data to remove the assumption.
Unpolarized data stays unpolarized. A table with plain R and T is applied equally to both polarizations; every path through it keeps a 50% s share, exactly. That is the honest reading of data that carries no polarization information — an unpolarized table cannot polarize a beam, and the s-polarized column (§16) will not pretend otherwise.
Where tables live. A saved design carries its tables. Share links do not — a table is far too large for a URL — so a scene with a user table is excluded from link sharing with the reason stated, and a restored link that referenced one asks you to upload it again. The thin-film tool’s angle-sweep view computes exactly this block onto the design itself — both sides, both polarizations, with the back side computed from the reversed layer stack — and the coating panel here imports it from your saved thin-film designs.
18. The presets, and the numbers they pin
Every preset is one of the engine’s own verification fixtures — geometry and optical values are spec-printed test scenes with known answers, not illustrations. Nothing in this table was hand-tuned to look good; each row states the analytic anchor the scene is gated against.
| Preset | The anchor |
|---|---|
| Cosine-fourth falloff | E(θ) = (Φ/π h²) cos⁴θ for a cosine point source over a plane. At Φ = 1 W, h = 1 m: center 0.3183 W/m²; the corner of the 2 m target sits at cos θ = 1/√3, exactly 1/9 of the center |
| Lambertian sphere over a plane | E = πL R² h/r³ from an extended spherical source; on-axis at R = 0.2 m, d = 1 m: πL(R/d)² = 0.1257 W/m² |
| Integrating sphere with a port | the sphere-multiplier family (Goebel 1967): with wall ρ = 0.9 and port fraction f = 0.05, total wall irradiation = Φ(1−f)/(1−ρ(1−f)) = 6.552Φ, wall absorption 0.6552Φ, escape 0.3448Φ — a closed geometric series the run reproduces within its stated σ̂ |
| Ghost ladder — plane plate | the §14 table, six orders exact in R-products |
| CPC concentrator | a′ = 25 mm, θₐ = 20° ⇒ entrance a = a′/sin θₐ = 73.10 mm, length (a+a′)/tan θₐ = 269.5 mm, geometric concentration 1/sin²θₐ = 8.549 — all derived, not typed. The angular cutoff at θₐ is the defining CPC property (Winston, Miñano, Benítez ch. 4): the engine’s measured 3D transmission falls from 0.865 at 19° to 0.135 at 21° |
| Box room | 2 m cube, ρ = 0.6 walls, downlight: the interreflection class of the standard room-photometry test cases; the floor map’s direct-plus-reflected structure |
| Plano-convex condenser | the exact refraction chain through a derived spherical cap — the fixture behind the engine’s lens-transport gate |
| Baffled tube | three absorbing baffles in front of a detector — successive apertures clipping the stray population; also the worked example of the detector-sidedness rule (§13) |
| Square lightpipe | the same square-section element, mirror walls: a Lambertian disk homogenized over 400 mm, uniformity read directly off the exit map |
| Baffled tube (round) | a 100 mm-bore round tube, 0.5 m long, with three annular baffles of 60 mm clear aperture — one baffled-round-tube element. The wall scatters with a Harvey–Shack lobe at the engine’s own gate-fixture parameters — illustrative values, not a measurement of any real tube. The exit map runs next-event estimation (§11), which is what makes a deep enclosure converge interactively; the square-section twin from the first release stays, and the pair shows what corner geometry does to a baffle stack |
20. How this engine is verified
Summarized because it is the reason to trust the numbers above; the complete gate-by-gate record is part of the engineering record behind the tool.
- Exact gates: energy-ledger closure on every scene (~1e-13); solid-angle fractions, view-factor reciprocity, étendue invariance through a lens (1e-13 on the per-ray skew invariant); the six-order ghost ladder below 1e-12; CPC geometry derived vs constructed at machine precision.
- Statistical gates: every analytic irradiance law in §18 as a z-gate against the run’s own σ̂; sampling distributions by χ²; the −1/2 convergence order fitted and pinned; the σ̂ estimator itself coverage-tested (0.950).
- Adversarial discipline: every gate was first run against deliberately broken engines — a missing cosine factor, a double-counted Fresnel coefficient, biased roulette, correlated random streams, wrongly-shaped scatter — and the suite records which gate kills which defect. A gate that cannot catch the bug it exists for is not a gate.
- Independent oracles: the same scenes run in two unrelated open-source Monte-Carlo tracers (Mitsuba 3, Raysect) agree with this engine within combined statistical error — 28/28 comparisons, and again on five fresh scenes at the engine-verification gate, 49/49.
- The browser engine — the preview’s — specifically reproduces the server engine’s frozen results and passes the identical gate suite in TypeScript; its acceleration structure (BVH) is gated to be bit-identical to the brute-force intersection scan, so speed never changes an answer.
- Published benchmark family: the interreflection test cases of CIE 171:2006 (“Test cases to assess the accuracy of lighting computer programs”) are run against the engine, with the corrections from the Lighting Analysts correction list (Ian Ashdown, the report’s technical reviewer) applied — each correction attributed and independently re-derived before use, and in two cases the correction list itself was found and recorded to need correcting. Where the published values are wrong, the gate value is the independently re-derived one, stated as such. This is a third-party correction list, not a CIE publication, and it is cited that way.
- The mesh path specifically: vertices weld by exact bit-equality and the validator demands a closed shell with every edge shared exactly twice; on a real CAD-kernel export — a 120,410-triangle housing — the weld closed the shell with zero repairs, both engines produced bit-identical hit distances on a pinned ray set, each mesh’s acceleration structure is gated bit-identical to the brute-force scan, and the energy ledger closes to 1e-11 on every mesh fixture.
21. What this page does not claim
- Numerical precision is not model accuracy. The ledger closing at 1e-13 says the arithmetic is right, not that a ρ you guessed is. The output is only as good as the reflectances and indices you put in.
- Every estimate is statistical. A bin’s value is estimate ± σ̂; treat differences smaller than a few σ̂ as unresolved. The hatching and lit-fraction warnings (§5) are there to be believed.
- Surface models are stated models. Mirrors are ideal polarization-neutral reflectors with scalar ρ. Refractive surfaces carry the full s/p Fresnel split (§16) or your own tabulated R/T data (§17). Scatter surfaces are Lambertian or the §10 lobe models with their stated energy-split modes (the tabulated-BSDF model takes your own uploaded data and is a Plus feature; ABg and Harvey–Shack are in the free tool) — and a lobe model is a fit, not a first-principles surface: its validity is the validity of the parameters you gave it. Scatter is depolarizing here; polarized scatter and full Mueller surface models are not in this engine.
- The preview is monochromatic. The preview traces one set of optical constants per scene — for an imported lens, every glass at exactly 550 nm — no spectra, no dispersion in a single preview. A server Run is monochromatic or spectral (§3); a spectral Run traces each wavelength bin with that bin’s own constants, so dispersion between bins is carried. Where a preview and a spectral Run of one scene differ, dispersion is physics, not disagreement.
- Bulk absorption needs stated data. Catalog and model glasses state no absorption data, so imported glass is lossless on both surfaces — power is lost at its surfaces only, with the advisory displayed — and no absorption is ever invented. The one media class that can state absorption is a custom material, and that is a server determination (§9): such a scene refuses the preview by name, and a Run removes the stated absorption along each path into the ledger’s volume row.
- Incoherent only. No interference, no diffraction — see the scope fence in §1. Wavelength-scale structures are outside the model’s validity whatever the error bar says.
- The analytic element set is exact; the imported kinds carry their own labels. Fourteen exact-geometry element kinds (§7), including closed solids that can be combined by union, intersection and difference — all traced at machine precision. The two imported kinds state their own accuracy instead of borrowing that claim: an imported lens design (§9) compiles to the same analytic solids, so it keeps machine precision; an imported mesh (§8) arrived with exactly the accuracy label this page once promised it would — its triangles are traced exactly and watertight, and they are chords of the part they approximate, so its results converge to the true surface only as the mesh is refined. For a mesh, the label — not the closing ledger — is the accuracy statement.
- Radiometric output only, for now. See §4.
- The tool analyzes; it does not design. It traces, finds and shows where the power goes. It will not synthesize a reflector or optimize a baffle for you — stating what a scene does, with an error bar, is the whole contract.
- User data is user data. An uploaded R/T table is reproduced, not validated: the tool states its interpolation, its edge rules and its single-side assumption, and nothing else about your data is checked. The
sourceline on the surface is your provenance trail, displayed verbatim.
Try it: the Illumination Analyzer — open the ghost-ladder preset, switch on path recording for its detector, and check the 0T 1R 0T row against T²R = 0.036864 yourself.
References
- R. Winston, J. C. Miñano, P. Benítez, Nonimaging Optics, Elsevier (2005), ch. 4 — CPC geometry, acceptance angle, edge-ray principle. [textbook]
- D. G. Goebel, “Generalized Integrating-Sphere Theory,” Applied Optics 6, 125 (1967). doi:10.1364/AO.6.000125.
- E. Fest, Stray Light Analysis and Control, SPIE Press (2013), ch. 2 — ghost analysis and baffle design context; path signatures and power-sorted path lists as the working method of stray-light analysis; ch. 4, scatter models and BSDF practice. [textbook]
- J. K. Salmon, M. A. Moraes, R. O. Dror, D. E. Shaw, “Parallel random numbers: as easy as 1, 2, 3,” SC’11. doi:10.1145/2063384.2063405 — the Philox RNG.
- E. Veach, “Robust Monte Carlo Methods for Light Transport Simulation,” PhD thesis, Stanford (1997); M. Pharr, W. Jakob, G. Humphreys, Physically Based Rendering — splitting and Russian-roulette unbiasedness; ch. 9, multiple importance sampling, the §11 weighting. [textbook]
- R. A. Chipman, W.-S. T. Lam, G. Young, Polarized Light and Optical Systems, CRC (2018) — the Mueller-calculus reference model against which the pair model’s error numbers above were measured. [textbook]
- G. G. Stokes, Cambridge and Dublin Math. J. 4, 1 (1849); modern statement in H. A. Macleod, Thin-Film Optical Filters, 4th ed., §2 — the reciprocity theorem behind the single-side transmission fill.
- A. Krywonos, J. E. Harvey, N. Choi, “Linear systems formulation of scattering theory for rough surfaces with arbitrary incident and scattering angles,” JOSA A 28, 1121 (2011). doi:10.1364/JOSAA.28.001121 — the generalized Harvey–Shack model.
- CIE 171:2006, Test Cases to Assess the Accuracy of Lighting Computer Programs, CIE (2006) — the §20 benchmark family; corrections per the Lighting Analysts correction list (I. Ashdown), independently reproduced.
- S. Woop, C. Benthin, I. Wald, “Watertight Ray/Triangle Intersection,” Journal of Computer Graphics Techniques 2(1), 65–82 (2013) — the watertight intersection algorithm of §8, both engines.