These additions are opt-in. Existing scenes, material laws, legacy branches and the default rendering remain available. This is a comparison bench for specific numerical and presentation choices, not a claim that the whole sandbox now predicts physical heat transfer.
The recording presets now also start with the familiar fire transfer. Material beauty explicitly restores it after using the temperature diagnostic. Enable Appearance → Thermal capture & comparison → Alternate thermal display to try the alternative exposure/threshold mapping; Temperature still enables its diagnostic palettes. This corrects the original recording preset defaults, which enabled the alternative automatically despite the otherwise opt-in design.
Capture without changing the experiment
The old gas drawing starts at 305 K by default. It maps a broad temperature range to additive orange/yellow light. A room full of mildly heated gas can therefore look like a room full of flames, independently of what its particles are doing. The original Fire Start Temp / Temp Range / Softness controls remain.
The optional Thermal capture route in SDFRenderer::thermal adds:
- A separate emission threshold (initially 480 K), exposure in stops, and color
saturation.
exposure_ev = -1halves the display signal. - A bounded screen transfer,
1 - exp(-signal), so gas does not simply clip to solid gold. The terrain-light gather uses the same threshold/exposure. - An optional smoke-weighted emission mask,
1 - exp(-2 smoke). This is an authored soot proxy. Smoke is not a calibrated soot concentration; hot clean gas disappearing from the flame view is intentional, not missing heat. - Ironbow, cool/warm and spectral temperature views, with explicit minimum and maximum temperatures and optional temperature contours. Select Air View: Temperature. These colors are diagnostics, not blackbody spectra.
All of this is display-only. Threshold, exposure and palettes never modify the thermal buffers. The gas/SDF field overlay is covered; MPM particle appearance, explosion decals, bloom and heat-haze passes have their own controls. These visuals are not calibrated radiometry. Even the 480 K threshold is a convenient capture parameter, not a claim that air visibly incandesces there.
For a restrained rocket recording: enable the route, try 480–650 K, -1 to -2 EV, and 0.5–0.8 saturation. First compare the temperature field. Do not cool the simulation merely to hide a bright color ramp.
A shared thermal face, rather than two separately clipped updates
For fixed cell volume, the heat-like quantity used by the scalar diffusion
model is E_i = C_i T_i. Its pair exchange should satisfy
C_i (T_i' - T_i) = K_ij dt / dx² (T_j - T_i)
C_j (T_j' - T_j) = K_ij dt / dx² (T_i - T_j)
The equal and opposite terms cancel if both cells use the same K_ij.
The previous shader individually clips K_ij dt / (dx² C_i) to 0.24.
At unlike heat capacities, one side can clip while the other does not. This
keeps each temperature bounded but no longer cancels capacity-weighted energy.
The optional Symmetric thermal flux route limits the shared face first:
q_ij = min(max(K_ij, 0) dt / dx², 0.24 min(C_i, C_j))
T_i' = T_i + sum_j q_ij / C_i (T_j - T_i)
The same q_ij is used by both sides. Four nonnegative coefficients each at
most 0.24 make the step a convex combination, and each interior face cancels
in sum C_i T_i. There are no extra GPU passes or new storage. This follows
the shared-face conservation principle of cell-centered finite volumes.
Limits: limiting still reduces the effective conductivity at an excessively large timestep. It preserves a local ledger; it does not make that slowed diffusion physically time-accurate. Substepping or an implicit solve would be the next comparison. Existing interface coefficients remain authored, including air/solid contact transfer. Domain borders are pinned to ambient and exchange energy with an implicit reservoir. Cooling, sources, temperature clamps, semi-Lagrangian advection and MPM/SPH transfers are separate from this invariant. Do not describe the full simulation as globally energy-conserving.
The existing physically_based_heat flag is currently an inactive shader uniform:
its older two-path distinction had already been removed before this pass. The
new symmetric toggle is the explicit, functioning comparison.
Cold-air reservoir with an explicit time constant
The existing air cooling already uses exponential relaxation. Its effective rate is reduced by an authored smoke/fresh-vapor/combustion-hold term. That is a useful look-development choice but makes a requested cooling rate depend on what is burning in the cell.
The optional Thermal bath replaces that air cooling term with
T' = T_bath + (T - T_bath) exp(-dt / tau)
This is the exact local update for the frozen-reservoir ODE
dT/dt = -(T - T_bath)/tau, a Newton cooling model.
One tau leaves about 37% of the initial temperature gap; three leave about 5%.
It is monotonic, works for heating as well as cooling, and cannot overshoot the
reservoir merely because the frame timestep is large. Smoke does not protect a
cell from this selected route.
Limits: this represents an external thermostat or unresolved exchange with
surroundings, not a conservative gas model or a resolved fan. It acts on air;
SDF solids respond through conduction and MPM/SPH matter through existing
coupling. Strong combustion sources can defeat a weak bath. Bath temperature is
clamped to the existing solver safety band [max(ambient - 100, 0), 2000] K;
tau has a 0.001 s numerical floor. Changing the display exposure is preferable
when the goal is only a less saturated recording.
Recording comparisons
- Warm room versus flame: pause a heated scene and toggle Thermal capture. Show the temperature palette beside the natural drawing. The state is unchanged; only the meaning of the color changes.
- Quench after a heat pulse: stop the heater, select a bath near ambient,
and compare
tau = 0.2, 0.5, 2.0s. Keep the scene and camera identical. This demonstrates an external cooling model rather than new chemistry. - Unequal heat capacities: place a hot solid beside cool air; use a large enough conductivity/timestep to exercise the limiter. Compare symmetric flux OFF/ON. The strongest evidence here is the numerical ledger below; the visual difference can be subtle under low, already-stable coefficients.
Crust, burning, annealing and melting remain the existing material behaviors. The added routes do not turn their authored thresholds, damage and shrinkage rules into calibrated constitutive models. Their value is to expose which layer changed: scalar transport, environmental cooling, or only the picture.
Retained verification
tests/thermal_comparison_test.cpp dispatches the actual production compute
shader and draws the actual production display shader in a hidden OpenGL
context. It checks:
- Capacity-weighted interior exchange and the maximum principle in an unlike-capacity case that exposes the old limiter's energy loss.
- The cooling time constant, time-step partition independence with frozen sources, and bounded warming/cooling for a large timestep.
- Visible exposure response, bounded gas display and distinct field palettes.
- Optional exact OFF-route comparisons against the pre-change compute and fragment shaders supplied in a baseline directory.
The display comparison now covers eight flame/terrain fixtures: RGBA32F and RGBA8 targets, terrain lighting off/on, and constant/varying smoke. It checks that flames and temperature-driven terrain lighting are actually present, and that changing the dormant alternate exposure has no effect. These are direct production SDF/fire shader draws, not the complete particle, bloom, heat-haze and final grading pipeline. The original comparison covered one RGBA32F fixture with terrain lighting off.
Run from the source root: thermal_comparison_test.exe [baseline_directory].
The optional directory contains euler_diffuse_temp.comp and sdf_draw.frag
from checkpoint 8baca7b. It tests the local changes; it is not a certificate of
the sandbox's complete thermal, chemistry, contact or fracture behavior.
Measured on the local RTX 4080 Laptop GPU on 2026-09-19: 871 checks passed.
This is the retained original test result, before the expanded display fixtures.
For the controlled single hot solid cell with capacity 8 beside air of capacity
1, the initial excess sum C(T-300) is 2400. The old individually clipped step
loses 912 units; the shared-face step loses 0 at the reported precision. Both
the disabled thermal update and disabled capture image matched checkpoint
8baca7b exactly in the retained GPU cases. These numbers describe the test
fixture, not the whole scene's energy balance.
On 2026-09-20 the expanded suite passed 950 checks, including exact matches
in all eight fire/terrain fixtures. The retained local energy result remains
unchanged. See regression_review_20260920.md for the separate native heat-tool
false-blast reproduction and the limits of the comparison with older binaries.