| Flame march | A box per flame (a bent slab for arc sectors, a tube otherwise) drawn back-face with no depth test into the flame target. Steps min(32 arcs / 38 others, max(14, 12 + px/12)), px the flame's size on screen; the start is dithered per pixel. On entering the flame, 4 bisection steps find the surface, so every pixel starts on it (a dithered start came out as grain). Light saturates within a third of the radius: σ = 6/R, edge band 0.07R + 3 mm. Cheap bounds first: a cylinder clip, a skip-ahead outside the reach, a top cut at 1.75H (2.6H for arcs), and the ground. Every bound fades the gas before it cuts it (r/reach 0.8–1, h/top 0.82–1). Coverage goes into alpha as 0.85 (plumes) or 0.4 (arcs) × the share of hot gas (T 0.35–0.65), so a flame in front of another layers instead of adding a seam. |
| Buoyancy and turbulence | Height coordinate y0·ln(1 + h/y0) with y0 = 0.45H + 3 cm, rising at 0.95 (the Scrolled switch uses h at 1.7). A low-frequency read at 0.34× the noise frequency sways the whole flame by R(0.08 + 0.85h/H) and warps the noise by 0.9. Octaves are staged: the first decides whether a tongue can reach the sample at all, a ridged second octave `0.42 − 1.3 |
| Licks and sheets | Broad brighter licks: noise at (1.6, 0.55, 1.6) × the frequency, `1 − smoothstep(0, 0.45, |
| Walls of fire | Arc sectors with partition-of-unity shoulders (30% of a sector) applied to each sector's light, not its density. Clumps: noise round the ring at 1.3 × R plus slow lobes at min(0.42R, 1.3), k = smoothstep(0, 0.55, E + 0.5(vig − 0.5) + 0.34 − 0.25·hvar); about a third of the front is gap, where the gas fades out over k 0.06–0.24. The radius wobbles by 1.4 × wobble × R, and the proxy box follows it. Clump height H(0.3 + 0.7k^0.6). Tongues inside a clump: cells clamp(0.62w, 6 cm, 30 cm) apart, each centred off-cell by ±22%, its height Hk(0.34 + 0.95h²) for a hash h (mostly short, a few 2–3× taller), pointed as (0.07 + 0.93t^0.62), snaking sideways by (0.26 sin(7.5y/Hk − 4.4t) + 0.1 sin(13y/Hk − 6.1t)) cells, flank tongues leaning out by up to 0.28. Licks: for 65% of tongues the tip breaks off once a 1.2–2 Hz cycle, a blob of 0.42 × the cell width rising to 1.6 × the tongue height and shrinking, burning at T 0.6 → 0.28. |
| Temperature and colour | T = temp × (1 − smoothstep(−0.18, 0.86, y + 0.25n + 0.1n_f)) × (0.38 + 0.62·depth) × (0.42 + 0.58·smoothstep(0, 0.2H, h)): the brightest gas sits a little above the fuel, not on it as a line. White heat adds (0.5 plume, 0.34 arc, 0.36 whirl, 0.12 ball) × depth² over y 0.02–0.14 to 0.22–0.62. A burst multiplies T by mix(1, 0.06 + 3·smoothstep(0.45, 0.85, n₁), billow): it cools unevenly into a few hot pockets. Ramp (linear HDR): (0.3, 0.024, 0.003) from T 0.02–0.2, (1.5, 0.15, 0.014) at 0.16–0.42, (3.4, 0.7, 0.09) at 0.4–0.66, (6, 1.9, 0.45) at 0.64–0.86, (9, 4.6, 1.9) at 0.85–1.05, (18, 13, 7) at 1.02–1.3: blue stays well under the red, so highlights roll to warm yellow-white, never cream. The ray's colour is mixed 38% toward the hottest gas along it. Blue root: accumulated over the bottom 10% at the skin, then smoothstep(0.3, 0.55) replaces the colour with (0.05, 0.26, 1.2) (added, blue and orange read as pink). |
| Band limit and upsample | Pixels per noise cycle at each sample: ppc = 1 / (distance × metres-per-pixel × frequency), metres-per-pixel = 2tan(fov/2) / the flame target's height. The second octave fades in over ppc/2.07 = 4–9, the third over ppc/3.4 = 5–11, licks over ppc/1.6 = 6–14, whirl sheets over 1.2·ppc = 5–12. The flame target runs at flameScale × the buffer (demo: ignition 0.8, wave 0.85 then 0.72, pillar 0.72, impact 0.64, aftermath 0.8) and is laid over the frame with a 4-tap cubic B-spline, no sharpening. |
| Smoke | Instanced camera-facing sprites (900), sorted far to near, premultiplied "over" (the Additive switch shows the grey-haze failure). Lit per corner by the 8 firelights at 1/(1 + 1.9d²), compressed as L/(1 + 0.45·lum(L)) and 45% desaturated, wrapped 0.55N·L + 0.45 with a floor of 0.22; ambient (0.018, 0.019, 0.024); albedo (0.024, 0.022, 0.021). Each fades in over 2.5% (billow) / 7% (curtain) / 10% (others) of its life, out from 45%, and into the ground over 0.25 m and the lens over 0.15–0.9 m. Motion: buoyancy × (1 − 0.7u), drag e^(−drag·dt), curl × (0.3 + u). Billow (type 0): cauliflower lobes as a soft union of domes (log-sum-exp, k = 8), two scales for the outline at 0.55 and 1.3 × lf (lf = clamp(1.1 × size, 3.2, 7) per sprite unit) and ridged noise at 0.75 / 2.1 / 4.3 × for the texture; density (dome^0.6 + 1.25(B − 0.6) + 0.55(Bd − 0.6)) cut at 0.4 + 0.08·age over 0.36–0.46; creases darkened to 0.58–1 × 0.72–1. Curtain (type 3) for a fire front: noise stretched (2.1 across, 1.25 along), curled by 0.55 and drifting up through the sprite, alpha from the streaks only, no edge and no underside of its own; lit warm by its height above the front (smoothstep(2.4, 0.7, y) × 0.75) and faint grey above 1.2–3.5 m. Wisp (type 2): the fibrous strand for small fires, 0.18–0.3 opacity. Hot pockets: sprites with fibre ≥ 4.5 add ridged cracks only in the creases, (1, 0.36, 0.07) × 4, fading out over 20–75% of their life. |
| Smoke emission | Rate = smoke × env × min(1.4, intensity) per second; a front multiplies it by min(5, arc length / 0.35 m) and thins it along the front by 0.3 + 0.7(0.5 + 0.5 sin(4.3a + seed))^1.5. Size s = min(2.2, 3.6w (arcs) or 2.6R × smokeSize) × 0.75–1.25. Fronts: curtain sprites at 0.8s from 45–95% of the flame height, rising 0.55–0.85 × min(1.5, 0.6 + 0.4H) m/s, grow 2.4–3, life 2.8–4 s, opacity 0.4–0.55, glow 0.6–0.9, aspect 2–2.5. Plumes and balls: billows at 0.45s, grow 3.4–4.6 (2.2–2.9 for a whirl), opacity 0.75–0.95, glow 0.5–0.8 (whirl 1.1–1.6), buoyancy 0.45–0.75; a whirl's smoke leaves its top (82–105% of the height) and spreads outward at 0.3–0.7 m/s. Burst (demo): 95/s billows of 0.55–0.95 m within 0.6–1.3 m of the axis, from 0.35 m up past the burst, near-black (0.019, 0.018, 0.017), 32% hot pockets early. |
| Embers and sparks | Embers from a flame's body: life 1.4–3.2 s, 3–7 mm, heat 0.75–1 cooling at 0.35–0.7/s, buoyancy 1.2–2.2, drag 0.9–1.5, curl 0.8–1.6, rate 2.5 × embers per second; a whirl's ride the air outside it (0.95–1.7R) with tangential speed 0.75·spin and a 0.25 inward pull. Drawn additive as dots and short dashes: width clamp(size on screen, 1.5, 4.2) px × dpr, length ≤ min(2.5 × width, 7 px × dpr), the head brighter than the tail (0.55–1). Ramp by heat: (0.08, 0.006, 0.001) → (1.1, 0.16, 0.025) → (3, 1.05, 0.22) → (4.2, 2.1, 0.7). A spark that cools out falls as an ash flake with p = 0.6 × flake (6–14 mm, glowing 0.35 at first). Drift on an analytic curl field (12 travelling sine waves). Pools: 1,200 sparks, 900 smoke sprites, 96 flames, 12 glints. |
| Light | 8 slots. Slot 0 takes flashes and decays at the flash's rate; slots 1–7 follow flames that ask for light (light × intensity × the flame's envelope). The demo's own stage lights (a light high in the fire tornado, its base pool) claim slots too, and may take the dimmest flame's. Energy swells with τ 0.09 s and settles with τ 0.38 s, drifting × (1 + 0.07 sin 2.3t + 0.04 sin 3.7t); colour (1, 0.4, 0.12) × lightGain 2.4, (1, 0.6, 0.3) for hot sources and flashes. On the ground: wrapped 0.8N·L + 0.2, diffuse 1/(1 + fall·d²) with fall 2.4 per light (0.35 for the pillar's base pool), the specular 1/(1 + 0.22d²) at roughness 0.97 (none, in effect). |
| Heat map and ground | A 256² HalfFloat world-space map over a 26 m square (heatUniforms.uHeatRect, centred on the demo's action), up to 320 patches redrawn every frame: lasting ones (addHeat) cooling as e^(−t/τ) with a burn mark that stays, and live ones each burning flame re-asserts. A front's band is ragged and interrupted, never a fuse. The floor (demo): the scan at 1.6 m a tile with a second sample turned and at 1.3 m under a macro mask; neutral charcoal at albedo 0.03 × (0.45 + 1.1 × the scan's lightness), its needles darker (0.018), grey ash drifts (0.055) and sparse ash flecks (0.12, 3.5% of 2.6 cm cells); a lump height field at 7 / 15 / 31 per metre (each fading by the pixel's footprint) for pebble relief; ambient (0.06, 0.062, 0.07) × albedo. Emission: the scan's dark pockets breathe slowly, needles burn as thin hairlines in runs where heat passes 0.05–0.5, ember specks on a 13/m grid at least a pixel wide (density 0.03 + 0.22·burn + 0.5·heat + 0.22·smoulder), colour (0.75, 0.1, 0.012)g + (1.3, 0.38, 0.07)g² × 0.45. Fog 1 − e^(−0.035d). |
| Sky | Horizon (0.017, 0.0158, 0.016) to zenith (0.0034, 0.0036, 0.0047), a slate cloud deck of + (0.0024, 0.0026, 0.0033), and each firelight's glow, pow(c, 24) × 0.0018 + pow(c, 4) × 0.0007, only low over the fire: the warmth belongs to the fire, not the air. |
| Heat haze | A taller box per hero flame, 4 dithered steps through a Gaussian profile of the hot air (radius R(1.4 + 0.5h/H) + 4 cm, over 2–45% of 2.4H), a shimmer at (7, 4.5, 7) per metre rising 4.8/s, warped, written as a signed offset × 0.0032 × haze into the half-resolution distortion buffer. Pressure rings are refraction only: radius × (1 − 2^(−10x)), amplitude ∝ (1 − x)^1.2. |
| Impact frame | As in lightning-energy-skill-vfx, after ACES: a jagged starburst R(1 + 0.34·spikes + 0.05 sin 61θ + 0.03 sin 113θ) (radius 0.36 in the demo) in which luminance through smoothstep(0.2, 0.3) becomes ink (0.03, 0.012, 0.006) on warm paper (1, 0.94, 0.84), with 150 tapered speed-line slots and 380 finer ones. Hold 0.08 s, axial shake −a·e^(−7t)·cos(2π·5.5t) at a = 0.016, fisheye 0.14 decaying as e^(−2.2t); the demo runs shake at 0.6 and fisheye at 0.65. At most 3 inverting flashes a second, then a 0.32 dim. |
| Bloom and composite | Dual-Kawase bloom, 5 levels from half resolution, threshold 2.2, knee 0.6, × 0.16, tinted (1, 0.78, 0.6): a low threshold softened every flame into a glow. One ACES pass, sRGB, a 0.0022 dither (not film grain) and a 0.4 vignette. |