paperlab 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/FxPostPass-M66QMVVC.js +166 -0
- package/dist/FxPostPass-M66QMVVC.js.map +1 -0
- package/dist/Grade-SBGYELKV.js +49 -0
- package/dist/Grade-SBGYELKV.js.map +1 -0
- package/dist/chunk-7TLIWPGM.js +36 -0
- package/dist/chunk-7TLIWPGM.js.map +1 -0
- package/dist/chunk-GB6BMHC3.js +811 -0
- package/dist/chunk-GB6BMHC3.js.map +1 -0
- package/dist/{chunk-E22ILEDO.js → chunk-NTDVOOBA.js} +1459 -197
- package/dist/chunk-NTDVOOBA.js.map +1 -0
- package/dist/damageContract-DPF1YFLZ.d.cts +129 -0
- package/dist/damageContract-DPF1YFLZ.d.ts +129 -0
- package/dist/fx.cjs +4276 -0
- package/dist/fx.cjs.map +1 -0
- package/dist/fx.d.cts +1796 -0
- package/dist/fx.d.ts +1796 -0
- package/dist/fx.js +3194 -0
- package/dist/fx.js.map +1 -0
- package/dist/index.cjs +2847 -914
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/stage.cjs +3063 -1091
- package/dist/stage.cjs.map +1 -1
- package/dist/stage.js +31 -51
- package/dist/stage.js.map +1 -1
- package/package.json +16 -3
- package/dist/chunk-E22ILEDO.js.map +0 -1
package/dist/fx.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fx/particles.ts","../src/fx/fire.ts","../src/fx/FxParticles.tsx","../src/fx/FxPost.tsx","../src/fx/afterglow.ts","../src/fx/FxFlames.tsx","../src/fx/flameShader.ts","../src/fx/FxWisps.tsx","../src/fx/FxFireFluid.tsx","../src/fx/fluid/FireFluid.ts","../src/fx/fluid/passes.ts","../src/fx/fluid/params.ts","../src/fx/FxFireLight.tsx","../src/fx/FxMatchFlame.tsx","../src/fx/sfx/graph.ts","../src/fx/sfx/fire.ts"],"sourcesContent":["import { emitHex } from './emission'\n\n/**\n * Particles: one pool, and presets that are only parameter sets.\n *\n * Embers, smoke and ash are not three systems. They are the same particle —\n * a position, a velocity, an age — told different things about how long it\n * lives, which way the air takes it and what it looks like. So there is one\n * pool, and a preset is a row of numbers; the next effect's droplet or dust is\n * another row, not another file. That is the P3 gate stated early: each effect\n * after the second should cost a preset, not a subsystem.\n *\n * **Simulated on the CPU,** for the same reason the damage field is: what\n * spawns them is read on the CPU. Ash leaves the texel that just burnt\n * through, and that texel is in a `Float32Array`, not a texture. A GPU\n * particle system would have to be told every spawn through an upload anyway,\n * and the counts here — hundreds, not hundreds of thousands — are ones a CPU\n * steps in a fraction of a millisecond.\n *\n * **A fixed capacity, from the tier,** allocated once and never grown. When\n * it is full, the particle closest to the end of its life gives up its slot:\n * dropping the NEW one instead would starve the burn of embers the moment the\n * room filled with old smoke, which is exactly backwards — the newest\n * particle is the one leaving the fire you are looking at.\n *\n * Drawing is `FxParticles`. Nothing in this file touches three.\n */\n\nexport interface ParticlePreset {\n /** Seconds a particle lives, drawn uniformly between the two. */\n readonly life: readonly [number, number]\n /** Launch speed in world units a second, drawn between the two. */\n readonly speed: readonly [number, number]\n /** Launch direction before spread, in world space. */\n readonly direction: readonly [number, number, number]\n /** 0 launches along `direction` exactly; 1 anywhere in the hemisphere around it. */\n readonly spread: number\n /** Acceleration straight up, world units a second squared. Hot air rises; ash is heavier than it. */\n readonly lift: number\n /** How fast the air brings a particle to its own speed, per second. */\n readonly drag: number\n /** A random acceleration, world units a second squared, drawn afresh every step — the flutter. */\n readonly jitter: number\n /** How much of the wind's velocity it takes on, 0..1. */\n readonly windCatch: number\n /** Diameter in world units, at birth and at death. */\n readonly size: readonly [number, number]\n /** Linear RGB at birth and at death. Above 1 for anything that is light rather than matter. */\n readonly color: readonly [readonly [number, number, number], readonly [number, number, number]]\n /** Opacity at birth and at death. Every particle also fades IN over the first tenth of its life. */\n readonly alpha: readonly [number, number]\n /** Spin in radians a second, drawn either way up to this. */\n readonly spin: number\n /** Added to what is behind it — light — or drawn over it — matter. */\n readonly blend: 'additive' | 'normal'\n /** A soft disc, or a jagged flake. */\n readonly shape: 'soft' | 'flake'\n /** Brightness wobble, 0..1: an ember flickers, a puff of smoke does not. */\n readonly flicker: number\n}\n\n/**\n * The fire's three. World units are the sheet's: a default sheet is one unit\n * across, about the width of A4, so 0.01 is two millimetres.\n */\nexport const particlePresets = {\n /**\n * A spark off the burning line. Short-lived, shrinking as it cools, carried\n * up and sideways by the heat.\n *\n * Authored through `emission.ts`, in multiples of paper white, which is what\n * caught the bug in the version before this one. That version said \"the hot\n * end is now ~5.6, well over the bloom threshold\" — and 5.6 was an ABSOLUTE\n * luminance, while paper white under `window` is 1.6. So the hottest spark\n * in the frame was **3.5× paper, under the spec's own 4× floor**, and its\n * own comment said otherwise. A number that cannot be compared to anything\n * is a number nobody can check.\n *\n * It was also too red. `[12, 4.2, 0.9]` is a saturated red at high\n * intensity, and the tone curve takes a bright red-dominant colour to\n * SALMON — which is `Never_this.png`. The hue is now a blackbody's: a spark\n * leaving the fire is yellow-orange, about 2200 K, and cools to a deep\n * orange-red as it dies. Hue and brightness are separate arguments now, and\n * `emit` keeps them that way.\n */\n ember: {\n life: [0.5, 1.4],\n speed: [0.15, 0.5],\n direction: [0, 1, 0],\n spread: 0.6,\n lift: 0.6,\n drag: 0.8,\n jitter: 1.5,\n windCatch: 0.8,\n // 0.3-1 mm of core (§5). A sheet is one world unit across, 210 mm, so\n // 0.0035 is 0.7 mm. It was 0.005 — a full millimetre wide before the\n // streak stretched it, which is where \"wide bars\" started.\n size: [0.0035, 0.0012],\n color: [\n // 6× paper white: inside §4.3's 4–8 band, so it blooms.\n emitHex('#FFC271', 6),\n // 0.7× — UNDER the bloom threshold on purpose, so a dying spark stops\n // glowing rather than merely fading.\n emitHex('#FF5512', 0.7),\n ],\n alpha: [1, 0],\n spin: 0,\n blend: 'additive',\n shape: 'soft',\n flicker: 0.35,\n },\n /**\n * What a paper fire mostly makes. Thin, slow, growing as it rises and\n * spreads, and gone well before it could read as a volume — no fluid\n * simulation, on purpose; see the fx plan.\n */\n smoke: {\n life: [1.8, 3.5],\n speed: [0.05, 0.15],\n direction: [0, 1, 0],\n spread: 0.3,\n lift: 0.25,\n drag: 0.9,\n jitter: 0.3,\n windCatch: 1,\n size: [0.05, 0.35],\n // #6B6560 grey-brown, linear, and thin — the background stays clear.\n color: [\n [0.147, 0.13, 0.117],\n [0.2, 0.19, 0.18],\n ],\n alpha: [0.08, 0],\n spin: 0.4,\n blend: 'normal',\n shape: 'soft',\n flicker: 0,\n },\n /**\n * Burnt-through paper, leaving. Lifted a little by the heat it came from,\n * then heavier than the air: it tumbles, flutters and falls — the part of a\n * burn that proves paper was there.\n */\n ash: {\n life: [2.5, 5],\n // Thrown UP by the heat it came off, before it is heavier than the air.\n // It used to leave at almost nothing (0.02–0.1) with a lift of −0.35, so\n // every flake began falling the moment it was born — straight through the\n // hole it came from, onto the black stage, dark on dark.\n speed: [0.14, 0.3],\n direction: [0, 1, 0],\n spread: 0.55,\n lift: -0.22,\n drag: 1.6,\n jitter: 1.2,\n windCatch: 1,\n // 5–10 mm across (§8.3). A sheet is 210 mm, so 0.03 is 6.3 mm. They were\n // 3.8 mm and there were far too many of them: a shower of specks rather\n // than a few flakes you would notice.\n size: [0.03, 0.026],\n color: [\n [0.07, 0.065, 0.06],\n [0.12, 0.115, 0.11],\n ],\n alpha: [0.95, 0],\n spin: 4,\n blend: 'normal',\n shape: 'flake',\n flicker: 0,\n },\n} as const satisfies Record<string, ParticlePreset>\n\nexport type ParticlePresetName = keyof typeof particlePresets\n\nconst PRESET_NAMES = Object.keys(particlePresets) as ParticlePresetName[]\nconst PRESETS: readonly ParticlePreset[] = PRESET_NAMES.map((name) => particlePresets[name])\n\n/**\n * Where `write` puts one blend mode's particles, as flat arrays sized for the\n * pool's capacity: xyz, rgba, and four extras — size, spin angle, shape (0\n * soft, 1 flake) and a per-particle seed the sprite uses for its outline.\n */\nexport interface ParticleTarget {\n readonly position: Float32Array\n readonly color: Float32Array\n readonly extra: Float32Array\n /** Velocity, xyz, if the target wants it — an ember is drawn stretched along its own. */\n readonly velocity?: Float32Array\n}\n\n/** A small, fast, seeded generator — the same seed makes the same fire. */\nfunction mulberry32(seed: number): () => number {\n let a = seed >>> 0\n return () => {\n a = (a + 0x6d2b79f5) >>> 0\n let t = a\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nexport class ParticlePool {\n readonly capacity: number\n /**\n * The air's own velocity, world units a second. A blow on `/hands` is a\n * wind, and smoke that ignores it is smoke painted on the glass.\n */\n readonly wind: [number, number, number] = [0, 0, 0]\n private readonly kind: Uint8Array\n private readonly position: Float32Array\n private readonly velocity: Float32Array\n private readonly age: Float32Array\n private readonly life: Float32Array\n private readonly angle: Float32Array\n private readonly spin: Float32Array\n private readonly seed: Float32Array\n private live = 0\n /** Live particles of each preset, kept as they come and go, so a cap costs nothing to check. */\n private readonly perKind = new Int32Array(PRESET_NAMES.length)\n /** Embers that popped in the air since the last `takePops` — the sound follows the picture. */\n private pops = 0\n private readonly random: () => number\n\n constructor(capacity: number, seed = 1) {\n this.capacity = Math.max(0, Math.floor(capacity))\n const n = this.capacity\n this.kind = new Uint8Array(n)\n this.position = new Float32Array(n * 3)\n this.velocity = new Float32Array(n * 3)\n this.age = new Float32Array(n)\n this.life = new Float32Array(n)\n this.angle = new Float32Array(n)\n this.spin = new Float32Array(n)\n this.seed = new Float32Array(n)\n this.random = mulberry32(seed)\n }\n\n /** Particles in the air. */\n get count(): number {\n return this.live\n }\n\n /** How many live particles are of one preset. For tests and for a HUD. */\n countOf(name: ParticlePresetName): number {\n return this.perKind[PRESET_NAMES.indexOf(name)]!\n }\n\n /** Launch one particle from a point. Never allocates; at capacity it takes the most-spent slot. */\n spawn(name: ParticlePresetName, x: number, y: number, z: number): void {\n if (this.capacity === 0) return\n const preset = particlePresets[name]\n let i = this.live\n if (i < this.capacity) {\n this.live++\n } else {\n // Full: the particle nearest the end of its life gives up its slot.\n i = 0\n let spent = -1\n for (let j = 0; j < this.live; j++) {\n const f = this.age[j]! / this.life[j]!\n if (f > spent) {\n spent = f\n i = j\n }\n }\n this.perKind[this.kind[i]!] = this.perKind[this.kind[i]!]! - 1\n }\n const r = this.random\n this.kind[i] = PRESET_NAMES.indexOf(name)\n this.perKind[this.kind[i]!] = this.perKind[this.kind[i]!]! + 1\n const i3 = i * 3\n this.position[i3] = x\n this.position[i3 + 1] = y\n this.position[i3 + 2] = z\n\n // A direction around the preset's, by as much as its spread allows: a\n // random unit vector mixed in, and folded into the preset's hemisphere.\n const [dx, dy, dz] = preset.direction\n let rx = r() * 2 - 1\n let ry = r() * 2 - 1\n let rz = r() * 2 - 1\n const rl = Math.hypot(rx, ry, rz) || 1\n rx /= rl\n ry /= rl\n rz /= rl\n let ox = dx * (1 - preset.spread) + rx * preset.spread\n let oy = dy * (1 - preset.spread) + ry * preset.spread\n let oz = dz * (1 - preset.spread) + rz * preset.spread\n if (ox * dx + oy * dy + oz * dz < 0) {\n ox = -ox\n oy = -oy\n oz = -oz\n }\n const ol = Math.hypot(ox, oy, oz) || 1\n const speed = preset.speed[0] + (preset.speed[1] - preset.speed[0]) * r()\n this.velocity[i3] = (ox / ol) * speed\n this.velocity[i3 + 1] = (oy / ol) * speed\n this.velocity[i3 + 2] = (oz / ol) * speed\n\n this.age[i] = 0\n this.life[i] = preset.life[0] + (preset.life[1] - preset.life[0]) * r()\n this.angle[i] = r() * Math.PI * 2\n this.spin[i] = (r() * 2 - 1) * preset.spin\n this.seed[i] = r()\n }\n\n /** Advance every particle by `dt` seconds; the dead leave the pool. */\n step(dt: number): void {\n if (dt <= 0) return\n const r = this.random\n const [wx, wy, wz] = this.wind\n for (let i = 0; i < this.live; i++) {\n const before = this.age[i]! / this.life[i]!\n this.age[i] = this.age[i]! + dt\n // The one ember in ten that dies in the air flashes as it crosses 0.86\n // of its life (see `write`); count it as it does, for the sound.\n if (this.seed[i]! < 0.1 && before < 0.86 && this.age[i]! / this.life[i]! >= 0.86) {\n if (PRESETS[this.kind[i]!]!.blend === 'additive') this.pops++\n }\n if (this.age[i]! >= this.life[i]!) {\n this.remove(i)\n i--\n continue\n }\n const preset = PRESETS[this.kind[i]!]!\n const i3 = i * 3\n const drag = Math.min(1, preset.drag * dt)\n const catchWind = preset.windCatch\n const jitter = preset.jitter\n // Toward the air's velocity, not toward rest: in a wind, \"drag\" is the\n // wind carrying it.\n let vx = this.velocity[i3]!\n let vy = this.velocity[i3 + 1]!\n let vz = this.velocity[i3 + 2]!\n vx += (wx * catchWind - vx) * drag + (r() * 2 - 1) * jitter * dt\n vy += (wy * catchWind - vy) * drag + (preset.lift + (r() * 2 - 1) * jitter) * dt\n vz += (wz * catchWind - vz) * drag + (r() * 2 - 1) * jitter * dt\n this.velocity[i3] = vx\n this.velocity[i3 + 1] = vy\n this.velocity[i3 + 2] = vz\n this.position[i3] = this.position[i3]! + vx * dt\n this.position[i3 + 1] = this.position[i3 + 1]! + vy * dt\n this.position[i3 + 2] = this.position[i3 + 2]! + vz * dt\n this.angle[i] = this.angle[i]! + this.spin[i]! * dt\n }\n }\n\n /**\n * Write every live particle into the target for its blend mode. Returns how\n * many went into each — the draw ranges.\n */\n write(\n additive: ParticleTarget,\n normal: ParticleTarget,\n /**\n * Where flakes go, if they are to be drawn apart from the smoke — ash is\n * a tumbling plane and smoke is a soft sprite, and one blend mode is not\n * one way of drawing. Omitted, flakes go with `normal` as they always did.\n */\n flakes?: ParticleTarget,\n ): { additive: number; normal: number; flakes: number } {\n let a = 0\n let n = 0\n let f = 0\n for (let i = 0; i < this.live; i++) {\n const preset = PRESETS[this.kind[i]!]!\n const flake = flakes !== undefined && preset.shape === 'flake'\n const target = preset.blend === 'additive' ? additive : flake ? flakes : normal\n const k = preset.blend === 'additive' ? a++ : flake ? f++ : n++\n const t = this.age[i]! / this.life[i]!\n const i3 = i * 3\n const k3 = k * 3\n target.position[k3] = this.position[i3]!\n target.position[k3 + 1] = this.position[i3 + 1]!\n target.position[k3 + 2] = this.position[i3 + 2]!\n if (target.velocity) {\n target.velocity[k3] = this.velocity[i3]!\n target.velocity[k3 + 1] = this.velocity[i3 + 1]!\n target.velocity[k3 + 2] = this.velocity[i3 + 2]!\n }\n const [c0, c1] = preset.color\n const fadeIn = Math.min(1, t / 0.1)\n const flicker =\n preset.flicker > 0\n ? 1 - preset.flicker * (0.5 + 0.5 * Math.sin(this.age[i]! * 41 + this.seed[i]! * 97))\n : 1\n const k4 = k * 4\n // One ember in ten dies in the air with a flash — a tiny pop at the end\n // of its life (spec §8.1), which the sound can follow.\n const pop = preset.blend === 'additive' && this.seed[i]! < 0.1 && t > 0.86 ? 2.6 : 1\n target.color[k4] = (c0[0] + (c1[0] - c0[0]) * t) * pop\n target.color[k4 + 1] = (c0[1] + (c1[1] - c0[1]) * t) * pop\n target.color[k4 + 2] = (c0[2] + (c1[2] - c0[2]) * t) * pop\n target.color[k4 + 3] = (preset.alpha[0] + (preset.alpha[1] - preset.alpha[0]) * t) * fadeIn * flicker\n target.extra[k4] = preset.size[0] + (preset.size[1] - preset.size[0]) * t\n target.extra[k4 + 1] = this.angle[i]!\n target.extra[k4 + 2] = preset.shape === 'flake' ? 1 : 0\n target.extra[k4 + 3] = this.seed[i]!\n }\n return { additive: a, normal: n, flakes: f }\n }\n\n /**\n * How many embers popped in the air since the last call, and reset — one\n * `FireSound.pop()` each keeps the sound on the frame the flash is on.\n */\n takePops(): number {\n const n = this.pops\n this.pops = 0\n return n\n }\n\n /** Empty the air — a fresh sheet. */\n clear(): void {\n this.live = 0\n this.perKind.fill(0)\n }\n\n /** Swap the last live particle into slot `i`. Order does not matter; density does. */\n private remove(i: number): void {\n this.perKind[this.kind[i]!] = this.perKind[this.kind[i]!]! - 1\n const last = --this.live\n if (i === last) return\n this.kind[i] = this.kind[last]!\n this.age[i] = this.age[last]!\n this.life[i] = this.life[last]!\n this.angle[i] = this.angle[last]!\n this.spin[i] = this.spin[last]!\n this.seed[i] = this.seed[last]!\n this.position.copyWithin(i * 3, last * 3, last * 3 + 3)\n this.velocity.copyWithin(i * 3, last * 3, last * 3 + 3)\n }\n}\n","import { HEAT, type DamageField } from './field'\nimport type { ParticlePool } from './particles'\n\n/**\n * Where a point of the sheet is in the world, given its UV — `v = 0` at the\n * bottom edge, as the damage grid has it. Null when it cannot say (the sheet\n * has not mounted yet).\n *\n * On `<Paper>` this is `(u, v) => handle.surfacePoint(u, v, scratch)`: the\n * drawn surface this frame, after the cloth and the deformers. A callback and\n * not the handle, because `paperlab/fx` cannot name a type from the main\n * entry without reaching into it — see `fx/boundary.test.ts`.\n */\nexport type SurfaceLocator = (\n u: number,\n v: number,\n) => { readonly x: number; readonly y: number; readonly z: number } | null\n\nexport interface FireEmitterOptions {\n /** Embers a second for every texel on the burn front. */\n embers?: number\n /** Puffs of smoke a second for every texel on the burn front. */\n smoke?: number\n /** The chance a texel that burns through leaves a flake of ash. */\n ash?: number\n /** Seed for which cells are chosen, so a replayed burn throws the same sparks. */\n seed?: number\n /** The most of each kind in the air at once — `fxQualityFor(tier).caps`. Uncapped if omitted. */\n caps?: { ember?: number; smoke?: number; ash?: number }\n}\n\n// Smoke kept light: a clean fire makes little, and a frame full of it hides\n// the burn — more only where burning struggles (see `struggle` below).\n/**\n * How much each kind leaves the front, per texel per second.\n *\n * `smoke` is 0 on purpose. There were three smoke systems running at once —\n * the fire simulator's own, these sprite puffs at 8% alpha, and the smoulder\n * wisp — and the review's note was that only one of them should exist. The\n * simulator's is the one that belongs to a burning sheet: it is the same\n * fluid the flames are made of, so it rises with them instead of beside them.\n * `FxWisps` still carries the thread after the flames are out, which is the\n * one moment the simulator has nothing left to make. The preset stays: it is\n * a parameter set, and anything may still ask the pool for smoke.\n *\n * Embers and ash are both down. Ash at 0.86 a texel was a flake for nearly\n * every one that burnt through — a dust shower. Fewer and larger is the note.\n */\nexport const fireEmitterDefaults: Required<FireEmitterOptions> = {\n embers: 0.35,\n smoke: 0,\n ash: 0.12,\n seed: 7,\n caps: {},\n}\n\n/**\n * The most of each kind one `update` may emit.\n *\n * A caller's `dt` is not a promise: this is public API, and a tab returning\n * from the background can hand it a delta of minutes. Without a ceiling the\n * debt loop below runs once per whole unit of it — which is a frozen page for\n * a shower of particles the pool would immediately throw away, since it only\n * holds `capacity` of them. An infinite delta never left the loop at all.\n */\nconst PER_UPDATE = 24\n\n/**\n * What a burn throws into the air, read off the field that is burning.\n *\n * Nothing here decides where a fire IS — the field does. Embers and smoke\n * leave the front, at a rate set by how long the front is (a fire gets\n * busier as its edge gets longer, the same number its sound follows). Ash\n * leaves exactly the texels that burnt through this frame, so a hole opening\n * is a hole shedding. On a sheet nothing is happening to, `update` reads two\n * zeros and returns.\n */\nexport class FireEmitter {\n private readonly field: DamageField\n private readonly pool: ParticlePool\n private readonly locate: SurfaceLocator\n private readonly o: Required<FireEmitterOptions>\n private emberDebt = 0\n private smokeDebt = 0\n private state: number\n\n constructor(\n field: DamageField,\n pool: ParticlePool,\n locate: SurfaceLocator,\n options: FireEmitterOptions = {},\n ) {\n this.field = field\n this.pool = pool\n this.locate = locate\n this.o = { ...fireEmitterDefaults, ...options }\n this.state = this.o.seed >>> 0 || 1\n }\n\n /** Call once a frame, after the field has stepped. */\n update(dt: number): void {\n const { field, o } = this\n const front = field.frontCount\n const consumed = field.consumedCount\n if (front === 0 && consumed === 0) {\n // A fire that has gone out owes nothing: a debt carried across would\n // put out a burst of sparks the moment the next one caught.\n this.emberDebt = 0\n this.smokeDebt = 0\n return\n }\n\n for (let k = 0; k < consumed; k++) {\n if (this.next() < o.ash) this.emit('ash', field.consumedCells[k]!)\n }\n\n if (front > 0 && dt > 0) {\n // The air the page is blowing, from the same pool the smoke rides.\n const [wx, wy, wz] = this.pool.wind\n const air = Math.hypot(wx, wy, wz)\n // How well it is burning: the mean heat along the front. A clean, hot\n // front makes little smoke; one that is catching, dying or being blown\n // on makes a lot (fire spec §4.9, §8.2 — smoke peaks when burning\n // struggles, never at the peak).\n let heat = 0\n for (let k = 0; k < front; k++) heat += field.data[field.frontCells[k]! * 4 + HEAT]!\n heat /= front\n const struggle = 0.35 + 1.6 * Math.max(0, 0.7 - heat) + air * 0.8\n // Blown on, a fire gets air: the embers flare and more fly (§10.6).\n const oxygen = 1 + air * 1.5\n // Capped rather than trusted — see `PER_UPDATE`. A NaN delta falls\n // through both loops on its own, which is the right answer for it.\n this.emberDebt = Math.min(this.emberDebt + front * o.embers * oxygen * dt, PER_UPDATE)\n this.smokeDebt = Math.min(this.smokeDebt + front * o.smoke * struggle * dt, PER_UPDATE)\n while (this.emberDebt >= 1) {\n this.emberDebt -= 1\n this.emit('ember', field.frontCells[Math.floor(this.next() * front)]!)\n }\n while (this.smokeDebt >= 1) {\n this.smokeDebt -= 1\n this.emit('smoke', field.frontCells[Math.floor(this.next() * front)]!)\n }\n }\n }\n\n private emit(name: 'ember' | 'smoke' | 'ash', cell: number): void {\n const cap = this.o.caps[name]\n if (cap !== undefined && this.pool.countOf(name) >= cap) return\n const size = this.field.size\n const last = size - 1\n // The field's convention: texel x sits at u = x / (size - 1), row 0 at v = 0.\n const at = this.locate((cell % size) / last, ((cell / size) | 0) / last)\n if (!at) return\n if (name === 'ember') {\n // Sparks leave the flames as well as the edge: some start partway up a\n // tongue, a little to one side, and drift off from there.\n const lift = this.next() * this.next() * 0.09\n this.pool.spawn(name, at.x + (this.next() - 0.5) * 0.012, at.y + lift, at.z)\n } else {\n this.pool.spawn(name, at.x, at.y, at.z)\n }\n }\n\n /** xorshift32 — its own stream, so the pool's randomness cannot shift which cells are picked. */\n private next(): number {\n let s = this.state\n s ^= s << 13\n s ^= s >>> 17\n s ^= s << 5\n this.state = s >>> 0\n return this.state / 4294967296\n }\n}\n","import * as THREE from 'three'\nimport { useFrame, useThree } from '@react-three/fiber'\nimport { useEffect, useMemo } from 'react'\nimport type { ParticlePool, ParticleTarget } from './particles'\n\n/**\n * The pool, drawn — three kinds, three ways, because the spec's three\n * particles are three different things (paperlab-fx-fire-spec.md §8).\n *\n * - **Embers** are light, moving fast: quads STRETCHED along their own\n * screen-space velocity into streaks, a bright head and a fading tail,\n * additive and HDR so they bloom. A spark is never a static dot.\n * - **Smoke** is soft sprites broken up by noise — thin, grey-brown, widening\n * as it rises. Camera-facing points: one vertex each is still the right\n * price for something this soft.\n * - **Ash** is paper: thin curled planes that tumble in three dimensions,\n * double-sided, char-dark with pale ash edges, lit by where they face\n * rather than glowing — a few keep a hot edge for their first second.\n *\n * This only draws. The page owns the order — field, then emitters, then the\n * pool's own step — because only the page knows which field belongs to which\n * sheet.\n */\n\nexport interface FxParticlesProps {\n pool: ParticlePool\n}\n\n/** Seconds of motion an ember's streak covers — long enough to read as speed, short enough to stay a spark. */\nconst STREAK = 0.045\n\nconst QUAD_STREAK = new Float32Array([-1, 0, 0, 1, 0, 0, 1, 1, 0, -1, 1, 0])\nconst QUAD_FLAKE = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0])\nconst QUAD_INDEX = [0, 1, 2, 0, 2, 3]\n\nfunction makeTarget(capacity: number): ParticleTarget {\n return {\n position: new Float32Array(capacity * 3),\n color: new Float32Array(capacity * 4),\n extra: new Float32Array(capacity * 4),\n velocity: new Float32Array(capacity * 3),\n }\n}\n\n/** Instanced quads over a target's own arrays — no copy. */\nfunction useInstanced(capacity: number, quad: Float32Array) {\n const layer = useMemo(() => {\n const target = makeTarget(capacity)\n const geometry = new THREE.InstancedBufferGeometry()\n geometry.setAttribute('position', new THREE.BufferAttribute(quad, 3))\n geometry.setIndex(QUAD_INDEX)\n geometry.setAttribute('aPos', new THREE.InstancedBufferAttribute(target.position, 3))\n geometry.setAttribute('aVel', new THREE.InstancedBufferAttribute(target.velocity!, 3))\n geometry.setAttribute('aColor', new THREE.InstancedBufferAttribute(target.color, 4))\n geometry.setAttribute('aExtra', new THREE.InstancedBufferAttribute(target.extra, 4))\n geometry.instanceCount = 0\n geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(), Infinity)\n return { target, geometry }\n }, [capacity, quad])\n useEffect(() => () => layer.geometry.dispose(), [layer])\n return layer\n}\n\n/** Points over a target's arrays, for the smoke. */\nfunction usePoints(capacity: number) {\n const layer = useMemo(() => {\n const target = makeTarget(capacity)\n const geometry = new THREE.BufferGeometry()\n geometry.setAttribute('position', new THREE.BufferAttribute(target.position, 3))\n geometry.setAttribute('pColor', new THREE.BufferAttribute(target.color, 4))\n geometry.setAttribute('pExtra', new THREE.BufferAttribute(target.extra, 4))\n geometry.setDrawRange(0, 0)\n geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(), Infinity)\n return { target, geometry }\n }, [capacity])\n useEffect(() => () => layer.geometry.dispose(), [layer])\n return layer\n}\n\nconst OUTPUT = /* glsl */ `\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n`\n\nconst EMBER_VERTEX = /* glsl */ `\nattribute vec3 aPos;\nattribute vec3 aVel;\nattribute vec4 aColor;\nattribute vec4 aExtra; // size, angle, shape, seed\nuniform vec2 uResolution;\nuniform float uStreak;\nvarying vec4 vColor;\nvarying vec2 vQuad;\nvoid main() {\n vec4 head = projectionMatrix * viewMatrix * vec4(aPos, 1.0);\n vec4 tail = projectionMatrix * viewMatrix * vec4(aPos - aVel * uStreak, 1.0);\n vec2 half_ = 0.5 * uResolution;\n vec2 d = head.xy / head.w * half_ - tail.xy / tail.w * half_;\n float len = length(d);\n vec2 dir = len > 1e-3 ? d / len : vec2(0.0, 1.0);\n vec2 across = vec2(-dir.y, dir.x);\n // Diameter in pixels from a size in world units: the projection's own scale.\n float px = max(1.5, aExtra.x * projectionMatrix[1][1] * half_.y / head.w);\n float along = len + px;\n vec2 offset = across * position.x * px * 0.5 + dir * (px * 0.5 - position.y * along);\n gl_Position = head + vec4(offset / half_ * head.w, 0.0, 0.0);\n vColor = aColor;\n vQuad = position.xy;\n}\n`\n\nconst EMBER_FRAGMENT = /* glsl */ `\nvarying vec4 vColor;\nvarying vec2 vQuad;\nvoid main() {\n // A thin hot core with a soft edge, fading to NOTHING behind the head.\n //\n // It used to be flat-topped across (smoothstep from 0.35, so the middle\n // seventy per cent of the width was all at full brightness) and to keep a\n // quarter of that brightness all the way to the end of the quad. Both\n // together drew a wide bar with a squared-off end, which is what the review\n // saw up close. A spark is a point of light smeared by its own motion: it\n // is brightest on its centre line and it runs out.\n float across = abs(vQuad.x);\n float body = exp(-across * across * 7.0);\n float tail = clamp(1.0 - vQuad.y, 0.0, 1.0);\n float m = body * tail * tail;\n if (m <= 0.003) discard;\n gl_FragColor = vec4(vColor.rgb * vColor.a * m, 1.0);\n${OUTPUT}\n}\n`\n\nconst SMOKE_VERTEX = /* glsl */ `\nattribute vec4 pColor;\nattribute vec4 pExtra; // size, angle, shape, seed\nuniform float uScale;\nvarying vec4 vColor;\nvarying vec2 vSpin;\nvarying float vSeed;\nvoid main() {\n vColor = pColor;\n vSpin = vec2(cos(pExtra.y), sin(pExtra.y));\n vSeed = pExtra.w;\n vec4 view = modelViewMatrix * vec4(position, 1.0);\n bool perspective = projectionMatrix[2][3] == -1.0;\n float depth = perspective ? max(0.0001, -view.z) : 1.0;\n gl_PointSize = max(1.0, pExtra.x * uScale / depth);\n gl_Position = projectionMatrix * view;\n}\n`\n\nconst SMOKE_FRAGMENT = /* glsl */ `\nvarying vec4 vColor;\nvarying vec2 vSpin;\nvarying float vSeed;\nfloat smHash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\nfloat smNoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(smHash(i), smHash(i + vec2(1.0, 0.0)), u.x), mix(smHash(i + vec2(0.0, 1.0)), smHash(i + vec2(1.0, 1.0)), u.x), u.y);\n}\nvoid main() {\n vec2 p = gl_PointCoord * 2.0 - 1.0;\n p = vec2(p.x * vSpin.x - p.y * vSpin.y, p.x * vSpin.y + p.y * vSpin.x);\n float r = length(p);\n // Not a disc: a soft body torn by two octaves of noise, different for\n // every puff, so a cloud of them is a texture rather than a pile of dots.\n float n = smNoise(p * 2.2 + vSeed * 19.0) * 0.65 + smNoise(p * 5.1 + vSeed * 7.0) * 0.35;\n float body = (1.0 - smoothstep(0.2, 1.0, r)) * smoothstep(0.25, 0.75, n + 0.25 * (1.0 - r));\n if (body <= 0.0) discard;\n gl_FragColor = vec4(vColor.rgb, vColor.a * body);\n${OUTPUT}\n}\n`\n\nconst FLAKE_VERTEX = /* glsl */ `\nattribute vec3 aPos;\nattribute vec4 aColor;\nattribute vec4 aExtra; // size, angle, shape, seed\nvarying vec2 vQuad;\nvarying vec4 vColor;\nvarying float vLight;\nvarying float vSeed;\nmat3 rotation(vec3 axis, float a) {\n float c = cos(a);\n float s = sin(a);\n float t = 1.0 - c;\n return mat3(\n t * axis.x * axis.x + c, t * axis.x * axis.y + s * axis.z, t * axis.x * axis.z - s * axis.y,\n t * axis.x * axis.y - s * axis.z, t * axis.y * axis.y + c, t * axis.y * axis.z + s * axis.x,\n t * axis.x * axis.z + s * axis.y, t * axis.y * axis.z - s * axis.x, t * axis.z * axis.z + c\n );\n}\nvoid main() {\n float seed = aExtra.w;\n // Its own tumbling axis, from its seed: flakes do not spin in step.\n vec3 axis = normalize(vec3(sin(seed * 40.0), cos(seed * 23.0), 0.4 + sin(seed * 71.0) * 0.5));\n mat3 r = rotation(axis, aExtra.y);\n // A little curl: burnt paper does not lie flat.\n vec3 local = vec3(position.x, position.y, position.x * position.x * 0.45 - 0.2) * aExtra.x * 0.5;\n vec3 world = aPos + r * local;\n vec3 n = r * vec3(0.0, 0.0, 1.0);\n // Lit by which way it faces — double-sided, so either face catches light.\n vLight = 0.25 + 0.75 * abs(dot(n, normalize(vec3(-0.35, 0.8, 0.5))));\n vQuad = position.xy;\n vColor = aColor;\n vSeed = seed;\n gl_Position = projectionMatrix * viewMatrix * vec4(world, 1.0);\n}\n`\n\nconst FLAKE_FRAGMENT = /* glsl */ `\nvarying vec2 vQuad;\nvarying vec4 vColor;\nvarying float vLight;\nvarying float vSeed;\nfloat flHash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\nfloat flNoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(flHash(i), flHash(i + vec2(1.0, 0.0)), u.x), mix(flHash(i + vec2(0.0, 1.0)), flHash(i + vec2(1.0, 1.0)), u.x), u.y);\n}\nvoid main() {\n float r = length(vQuad);\n float a = atan(vQuad.y, vQuad.x);\n // A torn scrap: an outline that wanders, different for every flake.\n float edge = 0.62 + 0.3 * flNoise(vec2(a * 1.6 + vSeed * 31.0, vSeed * 7.0));\n float mask = 1.0 - smoothstep(edge - 0.06, edge, r);\n if (mask <= 0.0) discard;\n // Char in the middle, pale brittle ash at the edge (§8.3) — in patches\n // along it, not all the way round: a flake is a few pixels across, and a\n // continuous pale rim round a dark middle drew it as a hollow ring.\n float rim = smoothstep(edge - 0.32, edge - 0.04, r);\n float patches = smoothstep(0.45, 0.75, flNoise(vec2(a * 2.2 + vSeed * 17.0, vSeed * 13.0)));\n float grain = flNoise(vQuad * 3.0 + vSeed * 23.0);\n vec3 charC = vec3(0.05, 0.043, 0.038) * (0.7 + 0.6 * grain);\n // #A49E9D, the spec's own sample of ash (§8.3), in linear.\n vec3 ashC = vec3(0.372, 0.344, 0.340);\n // Pale ash with char under it, not char with a pale rim.\n //\n // It was the other way round, and the flakes came out near black: at 0.07\n // linear they fell through the hole onto a black stage and read as dust in\n // the void. Burnt paper ash is a LIGHT grey — what makes a flake flash dark\n // and light as it tumbles is vLight, the face it is showing, not its own\n // colour being nearly black to begin with.\n vec3 c = mix(ashC, charC, (1.0 - rim * patches) * 0.45) * vLight;\n // A few carry a hot edge that fades over their first second or so, on ONE\n // arc of it — the side that was burning when it tore off. All the way round\n // drew it as an orange ring.\n float young = clamp((vColor.a - 0.55) / 0.4, 0.0, 1.0);\n float arc = smoothstep(0.25, 0.8, cos(a - vSeed * 40.0));\n float hotEdge = step(vSeed, 0.3) * rim * arc * young;\n c += vec3(2.4, 0.6, 0.08) * hotEdge;\n gl_FragColor = vec4(c, smoothstep(0.0, 0.2, vColor.a) * mask);\n${OUTPUT}\n}\n`\n\nexport function FxParticles({ pool }: FxParticlesProps) {\n const embers = useInstanced(pool.capacity, QUAD_STREAK)\n const flakes = useInstanced(pool.capacity, QUAD_FLAKE)\n const smoke = usePoints(pool.capacity)\n\n const materials = useMemo(\n () => ({\n ember: new THREE.ShaderMaterial({\n vertexShader: EMBER_VERTEX,\n fragmentShader: EMBER_FRAGMENT,\n uniforms: { uResolution: { value: new THREE.Vector2(1, 1) }, uStreak: { value: STREAK } },\n transparent: true,\n depthWrite: false,\n blending: THREE.AdditiveBlending,\n }),\n smoke: new THREE.ShaderMaterial({\n vertexShader: SMOKE_VERTEX,\n fragmentShader: SMOKE_FRAGMENT,\n uniforms: { uScale: { value: 100 } },\n transparent: true,\n depthWrite: false,\n }),\n flake: new THREE.ShaderMaterial({\n vertexShader: FLAKE_VERTEX,\n fragmentShader: FLAKE_FRAGMENT,\n transparent: true,\n depthWrite: false,\n side: THREE.DoubleSide,\n }),\n }),\n [],\n )\n useEffect(\n () => () => {\n materials.ember.dispose()\n materials.smoke.dispose()\n materials.flake.dispose()\n },\n [materials],\n )\n\n const size = useThree((s) => s.size)\n\n useFrame(({ gl, camera }) => {\n const counts = pool.write(embers.target, smoke.target, flakes.target)\n embers.geometry.instanceCount = counts.additive\n flakes.geometry.instanceCount = counts.flakes\n smoke.geometry.setDrawRange(0, counts.normal)\n for (const name of ['aPos', 'aVel', 'aColor', 'aExtra'] as const) {\n ;(embers.geometry.attributes[name] as THREE.BufferAttribute).needsUpdate = counts.additive > 0\n ;(flakes.geometry.attributes[name] as THREE.BufferAttribute).needsUpdate = counts.flakes > 0\n }\n if (counts.normal > 0) {\n for (const name of ['position', 'pColor', 'pExtra'] as const) {\n ;(smoke.geometry.attributes[name] as THREE.BufferAttribute).needsUpdate = true\n }\n }\n const ratio = gl.getPixelRatio()\n ;(materials.ember.uniforms.uResolution!.value as THREE.Vector2).set(\n size.width * ratio,\n size.height * ratio,\n )\n const projection = (camera as THREE.PerspectiveCamera).projectionMatrix.elements[5] ?? 1\n materials.smoke.uniforms.uScale!.value = size.height * ratio * 0.5 * projection\n })\n\n return (\n <>\n <mesh geometry={embers.geometry} material={materials.ember} frustumCulled={false} />\n <points geometry={smoke.geometry} material={materials.smoke} frustumCulled={false} />\n <mesh geometry={flakes.geometry} material={materials.flake} frustumCulled={false} />\n </>\n )\n}\n","import { Suspense, lazy } from 'react'\nimport type { ComponentType } from 'react'\nimport type { DamageField } from './field'\nimport type { SurfaceLocator } from './fire'\nimport type { FxQualityTier } from './quality'\n\n/** The rig's film. Mirrors `FilmName` — which fx cannot import; see `boundary.test.ts`. */\nexport type FxFilm = 'agx' | 'neutral' | 'filmic'\n\nexport interface FxPostProps {\n /**\n * The film the scene's lighting uses. The composer takes the tone curve off\n * the renderer, so the pass has to be told which one to put back. Every\n * built-in preset uses `neutral`.\n */\n film?: FxFilm\n /** How much the pass may cost — bloom is drawn at half resolution on `low`. */\n quality?: FxQualityTier\n /** Bloom strength; 0 keeps the tone curve and drops the bloom. */\n bloom?: number\n /** Bloom threshold in scene luminance. Must stay above paper white. */\n threshold?: number\n /**\n * The burning field, and where its sheet is — for the heat haze above its\n * flames and the frame's warm grade as it grows (§7). Leave both out and\n * the pass is bloom and the tone curve alone.\n */\n field?: DamageField\n locate?: SurfaceLocator\n /** Heat haze in pixels at 1080p, overriding the tier's; 0 turns it off. */\n haze?: number\n}\n\n/**\n * Bloom and the tone curve for a burn, loaded only if they are going to run.\n *\n * Put it inside the same `<Canvas>` as the burning sheet — as a child of\n * `<Paper>`. It is optional in exactly the way stage's print pass is:\n * `@react-three/postprocessing` and `postprocessing` are optional peers, this\n * reaches them through a dynamic import, and without them it renders nothing\n * and says so once. The fire still burns; it just does not glow.\n */\nconst Pass = lazy(() =>\n import('./FxPostPass')\n .then((m) => ({ default: m.FxPostPass as ComponentType<FxPostProps> }))\n .catch((error: unknown) => {\n warnOnce(error)\n return { default: (() => null) as ComponentType<FxPostProps> }\n }),\n)\n\nlet warned = false\n/** Says why, with the real error, rather than blaming two packages that may be installed. */\nfunction warnOnce(cause?: unknown) {\n if (warned) return\n warned = true\n console.warn(\n '[paperlab/fx] Rendering without FxPost — fire will not bloom.\\n' +\n 'It needs two optional peers:\\n' +\n ' npm i @react-three/postprocessing postprocessing\\n' +\n 'Leave <FxPost> out to go without it deliberately and silence this.',\n ...(cause === undefined ? [] : [cause]),\n )\n}\n\n/**\n * `fallback={null}`: the scene behind it is already drawn. One frame without\n * the pass, then one with it.\n */\nexport function FxPost(props: FxPostProps) {\n return (\n <Suspense fallback={null}>\n <Pass {...props} />\n </Suspense>\n )\n}\n","import type { DamageSource } from '../surface/damageContract'\nimport { FIELD_SIZE, HEAT, type DamageField } from './field'\n\nexport interface AfterglowOptions {\n /**\n * How long a bead keeps glowing after the heat under it has gone, in\n * seconds — a range, each cell drawn its own life from it by a seeded hash.\n */\n hold?: readonly [number, number]\n /** Seed for which bead outlives which. */\n seed?: number\n}\n\n/** The most a spent cell may keep glowing: a smoulder, not a fire. */\nconst SMOULDER = 0.3\n/** Below this a bead is out. */\nconst OUT = 0.004\n\n/**\n * The embers a burn leaves behind, drawn over the field it came from.\n *\n * `paperlab-fx-fire-spec.md` §9, \"Smoulder\": after the flames, glowing spots\n * crawl along the edge, flare on a breath, and go out one by one over\n * seconds (§4.10). The field cannot do this, and it is not a tuning problem:\n * a burn blown out loses its heat in a fraction of a second — below ignition\n * everywhere or it recovers — so its own heat channel has nothing left to\n * smoulder with.\n *\n * So this is presentation, and holds no simulation state. It wraps a field as\n * a `DamageSource`: every channel is the field's, except HEAT, which is the\n * field's heat or what each cell remembers of it, whichever is more. When the\n * heat under a cell goes, the cell keeps a smoulder's worth of it and lets it\n * go at its own pace — each cell's pace drawn once from a seeded hash, so\n * beads go out one at a time, and the same burn goes out the same way. A\n * breath flares what is left.\n *\n * Only the sheet reads it. Flames and the fire light read the FIELD, so the\n * flames die with the gas (§4.1) while the edge keeps glowing; the physics\n * reads char, saturation and presence, which pass through untouched.\n */\nexport class Afterglow implements DamageSource {\n readonly size = FIELD_SIZE\n readonly pixels = new Uint8Array(FIELD_SIZE * FIELD_SIZE * 4)\n private readonly field: DamageField\n private readonly glow = new Float32Array(FIELD_SIZE * FIELD_SIZE)\n private readonly rate = new Float32Array(FIELD_SIZE * FIELD_SIZE)\n private revision = 0\n private clock = 0\n private flare = 0\n private lastField = -1\n private lit = false\n\n constructor(field: DamageField, options: AfterglowOptions = {}) {\n this.field = field\n const [shortest, longest] = options.hold ?? [0.6, 2.2]\n const seed = (options.seed ?? 5) >>> 0\n for (let i = 0; i < this.rate.length; i++) {\n // A bead's life, from its own hash: e^(-rate · t) over [0.3 → out] is\n // ln(75) / rate, so these lives land the last bead between ~2 and ~8 s.\n let h = Math.imul(i ^ seed, 2654435761) >>> 0\n h ^= h >>> 15\n h = Math.imul(h, 2246822519) >>> 0\n const life = shortest + (longest - shortest) * ((h >>> 0) / 4294967296)\n this.rate[i] = 1 / life\n }\n this.pixels.set(field.pixels)\n }\n\n get version(): number {\n return this.revision\n }\n\n get detail(): number | undefined {\n return this.field.detail\n }\n\n /** Its own clock: the beads keep flickering after the field has gone to sleep. */\n get time(): number {\n return this.clock\n }\n\n /** Whether anything is still glowing that the field no longer is. */\n get smouldering(): boolean {\n return this.lit\n }\n\n /**\n * Advance by `dt`, after the field has stepped. `blow` is the breath on the\n * sheet, 0..1: it flares what is left (§10.6, \"on smoulder: beads flare,\n * then fade\").\n */\n step(dt: number, blow = 0): void {\n if (!(dt > 0)) return\n this.clock += dt\n this.flare = blow > 0.05 ? Math.min(1, this.flare + dt * 4) : Math.max(0, this.flare - dt * 1.5)\n const data = this.field.data\n const glow = this.glow\n let lit = false\n for (let i = 0; i < glow.length; i++) {\n const heat = data[i * 4 + HEAT]!\n const was = glow[i]!\n let next: number\n if (heat >= was) {\n // Follow the heat up at once.\n next = heat\n } else {\n // Let it go slowly: at most a smoulder's worth, then its own pace.\n next = Math.max(heat, Math.min(was, SMOULDER) * Math.exp(-dt * this.rate[i]!))\n }\n if (next < OUT) next = 0\n glow[i] = next\n if (next > heat + OUT) lit = true\n }\n this.lit = lit\n this.sync()\n }\n\n /** Copy the field in, and lay the remembered heat over its heat channel. */\n private sync(): void {\n const fieldVersion = this.field.version\n if (!this.lit && fieldVersion === this.lastField && this.flare === 0) return\n this.lastField = fieldVersion\n const src = this.field.pixels\n const dst = this.pixels\n dst.set(src)\n const boost = 1 + 0.9 * this.flare\n for (let i = 0; i < this.glow.length; i++) {\n const g = Math.round(Math.min(1, this.glow[i]! * boost) * 255)\n const k = i * 4 + HEAT\n if (g > dst[k]!) dst[k] = g\n }\n this.revision++\n }\n}\n","import type * as THREE from 'three'\nimport { useFrame } from '@react-three/fiber'\nimport { useEffect, useMemo, useRef } from 'react'\nimport type { DamageField } from './field'\nimport type { SurfaceLocator } from './fire'\nimport { flameAnchors, type FlameAnchor } from './flames'\nimport { flameGeometry, flameMaterial } from './flameShader'\nimport { fxQualityFor, type FxQualityTier } from './quality'\n\nexport interface FxFlamesProps {\n /** The burning field. Flames stand on its hot rim — see `flameAnchors`. */\n field: DamageField\n /** Where a point of the sheet is in the world — `(u, v) => handle.surfacePoint(u, v, scratch)`. */\n locate: SurfaceLocator\n /** How many flames may stand at once: 8 / 16 / 32 on low / medium / high. */\n quality?: FxQualityTier\n /**\n * The air, world units a second — pass the particle pool's `wind`, which is\n * read every frame. Flames lean away from it and shorten (§10.6).\n */\n wind?: readonly [number, number, number]\n}\n\n/**\n * The flames of a burn: separate tongues of burning gas, standing just off\n * the char, rising straight up whatever the sheet is doing.\n *\n * `paperlab-fx-fire-spec.md` §6. Each tongue is a quad that turns to face the\n * camera about WORLD up — never the sheet's normal (§13.9) — so a flame on a\n * sheet held flat still rises to the ceiling. The shader is upward-scrolling,\n * domain-warped noise inside a teardrop, coloured on the flame's own\n * blackbody ramp: a dim indigo root, a yellow-white core, orange edges and\n * tips, and a torn top that sheds wisps. Every flame puffs on its own noise\n * at 10–15 Hz, so no two ever move in step and nothing is a sine.\n *\n * Additive and HDR: the core is several times paper white, so `FxPost`\n * blooms it; it writes no depth, so the flames never occlude each other or\n * flicker as they sort. It animates on the FIELD's clock, so a replayed burn\n * burns with the same flames.\n */\nexport function FxFlames({ field, locate, quality = 'medium', wind }: FxFlamesProps) {\n const max = fxQualityFor(quality).flames\n const anchors = useRef<FlameAnchor[]>([])\n\n const geometry = useMemo(() => flameGeometry(max), [max])\n useEffect(() => () => geometry.dispose(), [geometry])\n const material = useMemo(() => flameMaterial(), [])\n useEffect(() => () => material.dispose(), [material])\n\n useFrame(() => {\n const n = flameAnchors(field, locate, max, anchors.current)\n const base = geometry.getAttribute('aBase') as THREE.InstancedBufferAttribute\n const shape = geometry.getAttribute('aShape') as THREE.InstancedBufferAttribute\n // The air leans them, and a strong wind shortens them — the tip is\n // blown away faster than the gas can climb.\n const [wx, wy, wz] = wind ?? [0, 0, 0]\n const gust = Math.hypot(wx, wy, wz)\n const shorten = 1 / (1 + gust * 0.6)\n for (let i = 0; i < n; i++) {\n const a = anchors.current[i]!\n base.array[i * 3] = a.x\n base.array[i * 3 + 1] = a.y\n base.array[i * 3 + 2] = a.z\n shape.array[i * 4] = a.height * shorten\n shape.array[i * 4 + 1] = a.width\n shape.array[i * 4 + 2] = a.seed\n shape.array[i * 4 + 3] = a.heat\n }\n base.needsUpdate = true\n shape.needsUpdate = true\n geometry.instanceCount = n\n material.uniforms.uTime!.value = field.time\n ;(material.uniforms.uLean!.value as THREE.Vector3).set(wx * 0.5, 0, wz * 0.5)\n })\n\n return <mesh geometry={geometry} material={material} frustumCulled={false} renderOrder={2} />\n}\n","import * as THREE from 'three'\n\n/**\n * One flame shader for every flame in paperlab/fx — the tongues on a burning\n * rim (`FxFlames`) and the match in a hand (`FxMatchFlame`) are the same fire,\n * and a match that looked different from what it lit would give the trick\n * away.\n *\n * A quad per flame, turned to the camera about WORLD up, never the sheet's\n * normal. Upward-scrolling, domain-warped noise inside a teardrop, coloured\n * on a dense, hot base: orange-white at the source, yellow above, orange at\n * the edges and tips, translucent as it thins, torn at the top into split\n * tongues, gaps and loose wisps. Each flame puffs on its own\n * value noise at 10–15 Hz (`flamePuff` in flames.ts runs the same function\n * on the CPU for the fire light). Additive, HDR, writes no depth.\n *\n * Per instance: `aBase` (root, world) and `aShape` (height, width, seed,\n * heat). Per material: `uTime`, `uLean` (the air and the motion, world units),\n * `uBlue` (0..1 — a flame starved by moving fast burns blue and dim) and\n * `uGain` (a strike's flare).\n */\n\nconst NOISE = /* glsl */ `\nfloat flHash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\nfloat flNoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(flHash(i), flHash(i + vec2(1.0, 0.0)), u.x), mix(flHash(i + vec2(0.0, 1.0)), flHash(i + vec2(1.0, 1.0)), u.x), u.y);\n}\nfloat flFbm(vec2 p) {\n float v = 0.0;\n float a = 0.5;\n for (int i = 0; i < 4; i++) {\n v += a * flNoise(p);\n p = p * 2.03 + 17.0;\n a *= 0.5;\n }\n return v;\n}\nfloat flHash1(float n) { return fract(sin(n) * 43758.5453); }\nfloat flNoise1(float x) {\n float i = floor(x);\n float f = fract(x);\n f = f * f * (3.0 - 2.0 * f);\n return mix(flHash1(i), flHash1(i + 1.0), f);\n}\n`\n\nconst VERTEX = /* glsl */ `\nattribute vec3 aBase;\nattribute vec4 aShape; // height, width, seed, heat\nuniform float uTime;\nuniform vec3 uLean;\nvarying vec2 vUv;\nvarying float vSeed;\nvarying float vHeat;\n${NOISE}\nvoid main() {\n vUv = position.xy;\n vSeed = aShape.z;\n vHeat = aShape.w;\n // The same puff as \\`flamePuff\\` in flames.ts — the fire light reads it too.\n float puff = 0.72 + 0.28 * flNoise1(uTime * 12.5 + aShape.z * 37.0) + 0.12 * (flNoise1(uTime * 23.0 + aShape.z * 11.0) - 0.5);\n float h = aShape.x * puff;\n vec3 up = normalize(vec3(0.0, 1.0, 0.0) + uLean);\n vec3 toCamera = normalize(cameraPosition - aBase);\n vec3 right = normalize(cross(up, toCamera));\n // A curve, not a tilt: the root holds and the tip is carried.\n vec3 p = aBase + toCamera * 0.004 + right * position.x * aShape.y + up * position.y * h\n + uLean * position.y * position.y * h * 0.8;\n gl_Position = projectionMatrix * viewMatrix * vec4(p, 1.0);\n}\n`\n\nconst FRAGMENT = /* glsl */ `\nuniform float uTime;\nuniform float uBlue;\nuniform float uGain;\nvarying vec2 vUv;\nvarying float vSeed;\nvarying float vHeat;\n${NOISE}\nvoid main() {\n // The quad is 1.8× the flame's width, so the turbulence can carry the\n // flame sideways without the quad's own edge cutting it into a bar.\n float x = vUv.x * 1.8;\n float y = vUv.y;\n // Climbing at a speed that itself wanders — per flame, and over time — so\n // the motion never settles into a loop. An offset, not a rate × time: a\n // rate that changes would make the pattern jump.\n float climb = uTime * (2.7 + 0.9 * vSeed) + 1.8 * flNoise1(uTime * 0.37 + vSeed * 9.1);\n // Three layers of turbulence: a slow sway of the whole tongue, a flutter\n // near the tip, and a fine shiver — each warping the next.\n vec2 q = vec2(x * 1.2 + vSeed * 13.0, y * 1.9 - climb);\n float sway = flFbm(q);\n float flutter = flFbm(vec2(x * 2.7 + vSeed * 5.0 + sway * 1.3, y * 4.3 - climb * 1.55));\n float shiver = flNoise(vec2(x * 7.0 + vSeed * 3.0, y * 11.0 - climb * 2.4));\n // Every tongue leans its own way — nothing here is symmetrical.\n float lean = (fract(vSeed * 91.7) - 0.5) * 0.45;\n float xw = x + (sway - 0.5) * 1.35 * y + (flutter - 0.5) * 0.5 * y * y + (shiver - 0.5) * 0.12 * y + lean * y;\n // The teardrop: dense and wide at the source, a point at the top.\n float half_ = 1.0 * pow(max(y, 0.0), 0.3) * pow(max(1.0 - y, 0.0), 1.3);\n // Crisp and dense low down, soft and translucent higher up.\n float body = 1.0 - smoothstep(half_ * mix(0.82, 0.4, y), half_, abs(xw));\n // Tips that split: a hard threshold high up tears the flame into separate\n // tongues and wisps that come loose ...\n float tear = flFbm(vec2(x * 2.4 + vSeed * 7.0, y * 4.2 - climb * 1.7));\n body *= smoothstep(0.0, 0.14, tear + 0.82 - y * 1.1);\n // ... and small gaps open inside the upper body, where the gas is thin.\n float gaps = smoothstep(0.58, 0.72, flFbm(vec2(x * 3.3 - vSeed * 4.0, y * 6.5 - climb * 2.0)));\n body *= 1.0 - gaps * smoothstep(0.3, 0.8, y) * 0.95;\n body *= smoothstep(0.0, 0.04, y);\n // The source is hot and dense: orange-white at the root, yellow above it,\n // orange at the edges and tips, and translucent as it thins.\n vec3 white = vec3(1.0, 0.86, 0.62); // orange-white\n vec3 yellow = vec3(0.98, 0.62, 0.09); // #FDCE54\n vec3 orange = vec3(0.77, 0.2, 0.0); // #E37B04\n // The orange-white is only the very root; above it the flame is saturated\n // yellow, then orange. Mixed broadly it washed the whole tongue pale.\n float base = 1.0 - smoothstep(0.0, 0.26, y);\n float inner = 1.0 - smoothstep(0.0, half_ * 0.5, abs(xw));\n vec3 c = mix(orange, yellow, smoothstep(0.35, 0.85, body * (1.0 - y * 0.85)));\n c = mix(c, white, clamp(base * inner, 0.0, 1.0));\n // Dense at the source, and bright enough there to bloom; thinning above.\n c *= mix(1.0, 4.4, base * (0.45 + 0.55 * inner)) * (0.55 + 0.45 * vHeat) * uGain;\n // Only a match starved of air by moving fast burns blue.\n c = mix(c, vec3(0.027, 0.025, 0.099) * 2.5, uBlue * 0.75);\n float fade = 1.0 - smoothstep(0.55, 1.0, y);\n gl_FragColor = vec4(c * body * fade, 1.0);\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n}\n`\n\n/** Instanced quads for up to `max` flames. `instanceCount` starts at 0. */\nexport function flameGeometry(max: number): THREE.InstancedBufferGeometry {\n const g = new THREE.InstancedBufferGeometry()\n // A unit quad: x across the tongue, y from root (0) to tip (1).\n g.setAttribute(\n 'position',\n new THREE.BufferAttribute(new Float32Array([-1, 0, 0, 1, 0, 0, 1, 1, 0, -1, 1, 0]), 3),\n )\n g.setIndex([0, 1, 2, 0, 2, 3])\n g.setAttribute('aBase', new THREE.InstancedBufferAttribute(new Float32Array(Math.max(1, max) * 3), 3))\n g.setAttribute('aShape', new THREE.InstancedBufferAttribute(new Float32Array(Math.max(1, max) * 4), 4))\n g.instanceCount = 0\n // Flames go wherever the fire is; a bounding sphere would be recomputed a\n // frame to save a cull that never fires.\n g.boundingSphere = new THREE.Sphere(new THREE.Vector3(), Infinity)\n return g\n}\n\nexport function flameMaterial(): THREE.ShaderMaterial {\n return new THREE.ShaderMaterial({\n vertexShader: VERTEX,\n fragmentShader: FRAGMENT,\n uniforms: {\n uTime: { value: 0 },\n uLean: { value: new THREE.Vector3() },\n uBlue: { value: 0 },\n uGain: { value: 1 },\n },\n transparent: true,\n depthWrite: false,\n blending: THREE.AdditiveBlending,\n side: THREE.DoubleSide,\n })\n}\n","import * as THREE from 'three'\nimport { useFrame } from '@react-three/fiber'\nimport { useEffect, useMemo, useRef } from 'react'\nimport type { Afterglow } from './afterglow'\nimport { FIELD_SIZE, HEAT, PRESENCE, type DamageField } from './field'\nimport type { SurfaceLocator } from './fire'\n\nexport interface FxWispsProps {\n /** What the sheet draws: where the smouldering beads are. */\n glow: Afterglow\n /** The field under it — a wisp rises where the glow outlives the heat. */\n field: DamageField\n locate: SurfaceLocator\n /** The air, read every frame — pass the pool's `wind`. */\n wind?: readonly [number, number, number]\n}\n\n/**\n * How opaque a point of the thread is at `age` seconds: 0 unless it is alive.\n *\n * Its own function because the inline version drew white suns on the paper.\n * It was `alive * ramp * (1 - age / LIFE) ** 1.6`, and for a dead point\n * `1 - age / LIFE` is NEGATIVE — a negative number to a fractional power is\n * NaN, and `0 * NaN` is still NaN. Dead points are collapsed onto a living\n * one precisely so that nothing they carry is ever drawn, but NaN in a vertex\n * attribute only needs one fragment to reach the HDR frame, and bloom then\n * spreads it into a white disc that greys the whole stage. It showed only in\n * the smoulder (the one time wisps are drawn), only while playing, and it was\n * gone with the wisp layer switched off.\n */\nexport function wispAlpha(age: number): number {\n if (!(age >= 0 && age < LIFE)) return 0\n return Math.min(1, age / 0.25) * (1 - age / LIFE) ** 1.6 * 0.42\n}\n\n/** At most this many threads at once (spec §8.2: one or two). */\nconst WISPS = 2\n/** Points along one thread. */\nconst POINTS = 64\n/** How long a point of smoke lives before it has thinned to nothing, in seconds. */\nconst LIFE = 3.4\n/** How fast the thread climbs, world units a second — a few centimetres. */\nconst RISE = 0.075\n/** How far apart two threads' beads must be, in cells (~3 cm). */\nconst APART = 9\n\n/**\n * The smoulder wisp: the shot people screenshot (`Stage_5__cold.png`).\n *\n * Not smoke sprites. A thin, pale thread — a trail of connected points drawn\n * as a ribbon — rising from a glowing bead once the flames are gone, curving\n * in a slow S as it climbs, and lingering a few seconds after the last bead\n * has gone out. One or two at a time.\n *\n * Every point's position is a pure function of where and when it left its\n * bead, on the afterglow's clock, so a scripted burn draws the same wisp\n * every time and nothing here integrates anything that could drift.\n */\nexport function FxWisps({ glow, field, locate, wind }: FxWispsProps) {\n const state = useRef(\n Array.from({ length: WISPS }, (_, w) => ({\n cell: -1,\n phase: w * 3.1 + 0.7,\n // Ring buffer of births: root position and birth time.\n root: new Float32Array(POINTS * 3),\n born: new Float32Array(POINTS).fill(-1e9),\n next: 0,\n owed: 0,\n })),\n )\n const last = useRef(-1)\n\n const geometry = useMemo(() => {\n const g = new THREE.BufferGeometry()\n const vertices = WISPS * POINTS * 2\n g.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices * 3), 3))\n g.setAttribute('aAlpha', new THREE.BufferAttribute(new Float32Array(vertices), 1))\n const index: number[] = []\n for (let w = 0; w < WISPS; w++) {\n for (let i = 0; i < POINTS - 1; i++) {\n const a = (w * POINTS + i) * 2\n index.push(a, a + 1, a + 2, a + 1, a + 3, a + 2)\n }\n }\n g.setIndex(index)\n g.boundingSphere = new THREE.Sphere(new THREE.Vector3(), Infinity)\n return g\n }, [])\n useEffect(() => () => geometry.dispose(), [geometry])\n const material = useMemo(\n () =>\n new THREE.ShaderMaterial({\n vertexShader: /* glsl */ `\n attribute float aAlpha;\n varying float vAlpha;\n void main() {\n vAlpha = aAlpha;\n gl_Position = projectionMatrix * viewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: /* glsl */ `\n varying float vAlpha;\n void main() {\n // Pale grey smoke, thin enough that only its accumulation reads.\n gl_FragColor = vec4(vec3(0.62, 0.61, 0.6), vAlpha);\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n }\n `,\n transparent: true,\n depthWrite: false,\n side: THREE.DoubleSide,\n }),\n [],\n )\n useEffect(() => () => material.dispose(), [material])\n\n useFrame(({ camera }) => {\n const now = glow.time\n const dt = last.current < 0 ? 0 : Math.min(0.1, Math.max(0, now - last.current))\n last.current = now\n const beads = smoulderingBeads(glow, field, WISPS)\n const [wx, , wz] = wind ?? [0, 0, 0]\n const positions = geometry.getAttribute('position') as THREE.BufferAttribute\n const alphas = geometry.getAttribute('aAlpha') as THREE.BufferAttribute\n const p = new THREE.Vector3()\n const q = new THREE.Vector3()\n const toCamera = new THREE.Vector3()\n const side = new THREE.Vector3()\n const path = new Float32Array(POINTS * 3)\n const age = new Float32Array(POINTS)\n\n for (let w = 0; w < WISPS; w++) {\n const s = state.current[w]!\n // Keep a bead while it glows; take a new one when it goes out — and\n // START THE THREAD OVER when it does.\n //\n // The ring buffer holds where each point was BORN, and those roots\n // belong to the bead it was rising from. Left in place when the wisp\n // moved to another bead, the thread's newest points sat over the new\n // one while its older points were still alive over the old one, and the\n // ribbon joining them is a straight line across the sheet at full\n // alpha. During the smoulder the beads go out one by one, so the wisp\n // re-targets again and again and lays down one line per move: the\n // \"random lines\" that appear late in a burn, fanning across the burnt\n // area from bead to bead. They only showed while PLAYING, because a\n // seek rebuilds the page and never changes bead twice.\n if (!beads.includes(s.cell)) {\n const taken = beads.find((c) => !state.current.some((o) => o !== s && o.cell === c)) ?? -1\n if (taken !== s.cell) {\n s.born.fill(-1e9)\n s.next = 0\n s.owed = 0\n }\n s.cell = taken\n }\n if (s.cell >= 0 && dt > 0) {\n s.owed += dt\n const every = LIFE / POINTS\n while (s.owed >= every) {\n s.owed -= every\n const at = locate(\n (s.cell % FIELD_SIZE) / (FIELD_SIZE - 1),\n ((s.cell / FIELD_SIZE) | 0) / (FIELD_SIZE - 1),\n )\n if (!at) break\n const k = s.next\n s.root[k * 3] = at.x\n s.root[k * 3 + 1] = at.y\n s.root[k * 3 + 2] = at.z + 0.004\n s.born[k] = now\n s.next = (k + 1) % POINTS\n }\n }\n // Points in age order, newest (at the bead) first.\n for (let i = 0; i < POINTS; i++) {\n const k = (s.next - 1 - i + POINTS * 2) % POINTS\n const a = now - s.born[k]!\n age[i] = a\n const bx = s.root[k * 3]!\n const by = s.root[k * 3 + 1]!\n const bz = s.root[k * 3 + 2]!\n // A slow S: value noise in the point's age, drifting with its birth\n // time so the whole thread sways rather than holding one shape.\n const sway = (noise1(a * 0.8 + s.born[k]! * 0.3 + s.phase) - 0.5) * 0.07 * a\n const lean = (noise1(a * 0.5 + s.phase * 2.3) - 0.5) * 0.02 * a\n path[i * 3] = bx + sway + wx * a * a * 0.25\n path[i * 3 + 1] = by + RISE * a * (1 + 0.18 * a)\n path[i * 3 + 2] = bz + lean + wz * a * a * 0.25\n }\n // A point that is not alive — never born, or already gone — is pulled\n // onto the youngest living one, so the ribbon has no area there. Left\n // where its formula put it, an unborn point is a BILLION seconds old\n // and ~10^16 units away; a strip joining it to a live one covered the\n // frame, and the HDR frame's bloom turned that into a black picture.\n let anchor = -1\n for (let i = 0; i < POINTS; i++) {\n if (age[i]! >= 0 && age[i]! < LIFE) {\n anchor = i\n break\n }\n }\n for (let i = 0; i < POINTS; i++) {\n const a = age[i]!\n if (!(a >= 0 && a < LIFE)) {\n // Onto the nearest living point, so the ribbon has no area here.\n // With NOTHING alive there is no such point; collapse onto the root\n // the thread was last born at — finite, and on the sheet. (Not onto\n // point 0's own position: for a dead thread that is a billion\n // seconds of rise away, ~10^16 units.)\n if (anchor >= 0) {\n path[i * 3] = path[anchor * 3]!\n path[i * 3 + 1] = path[anchor * 3 + 1]!\n path[i * 3 + 2] = path[anchor * 3 + 2]!\n } else {\n const r = ((s.next - 1 + POINTS) % POINTS) * 3\n path[i * 3] = s.root[r]!\n path[i * 3 + 1] = s.root[r + 1]!\n path[i * 3 + 2] = s.root[r + 2]!\n }\n } else {\n anchor = i\n }\n }\n for (let i = 0; i < POINTS; i++) {\n const a = age[i]!\n p.set(path[i * 3]!, path[i * 3 + 1]!, path[i * 3 + 2]!)\n const j = Math.min(POINTS - 1, i + 1)\n const h = Math.max(0, i - 1)\n q.set(\n path[j * 3]! - path[h * 3]!,\n path[j * 3 + 1]! - path[h * 3 + 1]!,\n path[j * 3 + 2]! - path[h * 3 + 2]!,\n )\n toCamera.copy(camera.position).sub(p)\n const alive = a >= 0 && a < LIFE\n if (alive || i === 0) {\n side.crossVectors(q, toCamera)\n // A degenerate segment has no side; give it one rather than a NaN.\n if (side.lengthSq() < 1e-20) side.set(1, 0, 0)\n side.normalize()\n }\n // A dead point KEEPS the side of the living point it was collapsed\n // onto. Computing its own gave it the degenerate fallback (1, 0, 0)\n // while its neighbour used the real direction, so the two made a\n // small bow-tie with area — enough to rasterise whatever the dead\n // vertices carried. Same position and same side is no area at all.\n // A thread at the bead, widening a little as it climbs and thins.\n // Clamped, so a dead point's (huge) age cannot widen it past its anchor.\n const half = 0.0011 + 0.0022 * Math.min(1, Math.max(0, a) / LIFE)\n const v = (w * POINTS + i) * 2\n positions.setXYZ(v, p.x - side.x * half, p.y - side.y * half, p.z - side.z * half)\n positions.setXYZ(v + 1, p.x + side.x * half, p.y + side.y * half, p.z + side.z * half)\n const alpha = wispAlpha(a)\n alphas.setX(v, alpha)\n alphas.setX(v + 1, alpha)\n }\n }\n positions.needsUpdate = true\n alphas.needsUpdate = true\n })\n\n return <mesh geometry={geometry} material={material} frustumCulled={false} renderOrder={1} />\n}\n\n/**\n * The beads a wisp may rise from: rim cells where the sheet still glows and\n * the field is cold — smouldering, not burning. Brightest first, and spread\n * `APART` from each other. Deterministic: ties go to the lower cell.\n */\nfunction smoulderingBeads(glow: Afterglow, field: DamageField, max: number): number[] {\n const size = FIELD_SIZE\n const shown = glow.pixels\n const heat = field.pixels\n const found: { cell: number; g: number }[] = []\n for (let y = 1; y < size - 1; y++) {\n for (let x = 1; x < size - 1; x++) {\n const c = y * size + x\n const g = shown[c * 4 + HEAT]!\n if (g < 10 || heat[c * 4 + HEAT]! > 4 || shown[c * 4 + PRESENCE]! < 128) continue\n const rim =\n shown[(c - 1) * 4 + PRESENCE]! < 128 ||\n shown[(c + 1) * 4 + PRESENCE]! < 128 ||\n shown[(c - size) * 4 + PRESENCE]! < 128 ||\n shown[(c + size) * 4 + PRESENCE]! < 128\n if (rim) found.push({ cell: c, g })\n }\n }\n found.sort((a, b) => b.g - a.g || a.cell - b.cell)\n const picked: number[] = []\n for (const f of found) {\n if (picked.length >= max) break\n const fx = f.cell % size\n const fy = (f.cell / size) | 0\n if (picked.every((p) => Math.hypot((p % size) - fx, ((p / size) | 0) - fy) >= APART)) picked.push(f.cell)\n }\n return picked\n}\n\nfunction hash1(n: number): number {\n const s = Math.sin(n) * 43758.5453\n return s - Math.floor(s)\n}\n\nfunction noise1(x: number): number {\n const i = Math.floor(x)\n let f = x - i\n f = f * f * (3 - 2 * f)\n return hash1(i) + (hash1(i + 1) - hash1(i)) * f\n}\n","import * as THREE from 'three'\nimport { useFrame, useThree } from '@react-three/fiber'\nimport { type ReactNode, useEffect, useMemo, useRef } from 'react'\nimport {\n FIRE_CONTRAST,\n FIRE_HEAT_SCALE,\n FIRE_OPACITY,\n FIRE_SOOT_SCALE,\n FIRE_THIN,\n type FireZonesInput,\n PAPER_WHITE,\n fireZones,\n hexToLinear,\n} from './emission'\nimport type { DamageField } from './field'\nimport type { SurfaceLocator } from './fire'\nimport { FLAME_HEIGHT, flameAnchors, type FlameAnchor } from './flames'\nimport { FireFluid } from './fluid/FireFluid'\nimport { type FireFluidParams, fireFluidDefaults, solverUniforms } from './fluid/params'\nimport { MAX_SOURCES, RENDER_FRAGMENT, RENDER_VERTEX } from './fluid/passes'\nimport { fxQualityFor, type FxQualityTier } from './quality'\n\nexport interface FxFireFluidProps {\n field: DamageField\n locate: SurfaceLocator\n quality?: FxQualityTier\n /** The simulator's controls; anything left out takes the panel's default. */\n params?: Partial<FireFluidParams>\n /** False freezes the fire where it is — a paused lab. */\n running?: boolean\n /**\n * Change it to start the fire over: the air is emptied and the fire is\n * warmed up from the rim as it stands now, so a burn that was just seeked\n * to a moment shows that moment's flames rather than empty air.\n */\n resetKey?: unknown\n /** How bright the fire glows, against paper white. */\n glow?: number\n /**\n * The flame's four zones — root, core, body and tip — each part laid over\n * `FIRE_ZONES`. See `FireZones` for what each zone is and why it looks the\n * way it does.\n */\n zones?: FireZonesInput\n /** The solver temperature that counts as a flame's hottest gas. */\n heatScale?: number\n /** The soot density that counts as a full flame — see `FIRE_SOOT_SCALE`. */\n sootScale?: number\n /** Gamma on the flame's temperature — above 1 darkens the body against the core. */\n contrast?: number\n /** How opaque the densest flame gas is; 0 is purely additive fire. */\n opacity?: number\n /** How opaque gas must be to glow fully; see `FIRE_THIN`. */\n thin?: number\n /**\n * Seconds of fire run, unseen, whenever it starts over (see `resetKey`).\n * A plume started from still air rolls its leading edge into a mushroom cap\n * — the starting vortex — and a fire that has burned for seconds has long\n * since shed it. Too short and a seeked-to frame shows that cap.\n */\n warm?: number\n /**\n * Error-compensated (MacCormack) advection for what is drawn. Defaults to\n * on everywhere but the `low` tier, where the two extra passes a step are\n * the first thing a throttled phone should give back.\n */\n sharp?: boolean\n /**\n * What to draw where the simulator cannot run (no half-float render\n * targets) — typically `<FxFlames>`. Drawn INSTEAD, never as well.\n */\n fallback?: ReactNode\n}\n\n/** The domain, in world units: a sheet and a half across, room above it for the flames. */\nconst DOMAIN = { width: 1.5, height: 2 } as const\n/** Below the sheet's middle, where the domain starts. */\nconst BELOW = 0.8\n/**\n * Fixed solver step. 1/120: gas leaving the rim at ~0.3 m/s crosses about a\n * velocity cell a step at this rate, which is what semi-Lagrangian advection\n * is accurate for; at 1/60 it crossed two, and smeared.\n */\nconst STEP = 1 / 120\n/** The unseen warm-up after a reset runs at the old rate — it is shed before it is shown. */\nconst WARM_STEP = 1 / 60\n/** Steps a frame may take before the fire falls behind rather than stalling the page. */\nconst MAX_STEPS = 6\n/**\n * Half-width of the band gas comes off, across the rim: ~1.2 mm of A4. The\n * char right behind the ember line, which is where paper gives off its gas.\n */\nconst BAND = 1.2 / 210\n/**\n * Spots of the rim that remember a flame, so they go on smoking after it.\n * Each flame writes itself into one slot (by its seed) every step it burns;\n * a slot not written for a while is a spot whose flame has gone out, and it\n * releases smoke — no fuel — fading over `smokeAfter` seconds.\n */\nconst SMOULDER_SLOTS = 16\n/** Seconds a spot must have had no flame before it counts as out and starts to smoke. */\nconst SMOULDER_AFTER = 1\n/**\n * How long a reset fire is run before it is shown.\n *\n * Was 0.8 s, and that is what drew the HOOKS — tongues curling over at the\n * top like ribbons. A plume started from still air rolls its leading edge\n * into a mushroom cap, the starting vortex, and 0.8 s is not long enough for\n * it to have risen out of frame; a fire that has actually burned for a few\n * seconds shed it long ago. Swept on the peak frame: at 0.8 s two hooks, at\n * 2 s nearly none, at 4 s straight vertical tongues. Turbulence and vorticity\n * were tried first and changed nothing — the hooks were never the flow's, they\n * were the seek's. Only a reset pays this (a seek, or `resetKey` changing).\n */\nconst WARM = 3\n\nconst up = new THREE.Vector3(0, 1, 0)\n\n/**\n * Fire, simulated — the flames and the smoke of a burning sheet as a fluid:\n * gas released along the hot rim, burning where it has oxygen, rising on its\n * own heat, torn by turbulence, cooling out of sight, leaving smoke.\n *\n * The damage field still decides everything about the PAPER; this is only\n * what the air above it does. The rim is read on the CPU as emission points —\n * the same irregular clusters `flameAnchors` stands the sprite flames on, so\n * the fire still gathers in tall tongues above a hole and short licks below\n * it — and nothing is ever read back from the GPU.\n *\n * It lives in a vertical plane through the sheet, facing the camera, so the\n * flames rise straight up whatever the sheet is doing. Needs half-float\n * render targets; where there are none it renders nothing, and the caller\n * falls back to `FxFlames` (see `FireFluid.supported`).\n */\nexport function FxFireFluid({\n field,\n locate,\n quality = 'medium',\n params,\n running = true,\n resetKey,\n glow = 1,\n zones,\n heatScale = FIRE_HEAT_SCALE,\n sootScale = FIRE_SOOT_SCALE,\n contrast = FIRE_CONTRAST,\n opacity = FIRE_OPACITY,\n thin = FIRE_THIN,\n warm = WARM,\n sharp,\n fallback,\n}: FxFireFluidProps) {\n const gl = useThree((s) => s.gl)\n const camera = useThree((s) => s.camera)\n const grid = fxQualityFor(quality).fluid\n const fluid = useMemo(() => (FireFluid.supported(gl) ? new FireFluid(gl, grid, DOMAIN) : null), [gl, grid])\n useEffect(() => () => fluid?.dispose(), [fluid])\n\n const merged = useMemo(() => ({ ...fireFluidDefaults, ...params }), [params])\n const material = useMemo(\n () =>\n new THREE.ShaderMaterial({\n vertexShader: RENDER_VERTEX,\n fragmentShader: RENDER_FRAGMENT,\n uniforms: {\n uA: { value: null },\n uB: { value: null },\n uTime: { value: 0 },\n uGlow: { value: 1 },\n // Thin: smoke over a clear background, not a veil across it.\n uSmokeDensity: { value: 0.9 },\n // How bright the fire is, in the one unit `emission.ts` defines.\n uPaperWhite: { value: PAPER_WHITE },\n uHeatScale: { value: FIRE_HEAT_SCALE },\n uSootScale: { value: FIRE_SOOT_SCALE },\n uContrast: { value: FIRE_CONTRAST },\n uOpacity: { value: FIRE_OPACITY },\n uThin: { value: FIRE_THIN },\n // The four zones (FireZones). Colours linear, glows in multiples of\n // paper white, boundaries in fractions of the hottest gas.\n uTipColor: { value: new THREE.Vector3() },\n uBodyColor: { value: new THREE.Vector3() },\n uCoreColor: { value: new THREE.Vector3() },\n uRootColor: { value: new THREE.Vector3() },\n uTipGlow: { value: 0 },\n uBodyGlow: { value: 0 },\n uCoreGlow: { value: 0 },\n uTipFrom: { value: 0 },\n uTipTo: { value: 0 },\n uCoreFrom: { value: 0 },\n uSoftness: { value: 0 },\n uTearing: { value: 0 },\n uRootAmount: { value: 0 },\n uRootReach: { value: 0 },\n },\n transparent: true,\n depthWrite: false,\n // The fire is always in front of the paper it rises from. A sheet that\n // drapes or curls crosses the plane in places, and a depth test cut\n // the fire off there in a hard vertical seam.\n depthTest: false,\n // Premultiplied: fire adds light, smoke covers what is behind it.\n blending: THREE.CustomBlending,\n blendSrc: THREE.OneFactor,\n blendDst: THREE.OneMinusSrcAlphaFactor,\n side: THREE.DoubleSide,\n }),\n [],\n )\n useEffect(() => () => material.dispose(), [material])\n const plane = useMemo(() => new THREE.PlaneGeometry(DOMAIN.width, DOMAIN.height), [])\n useEffect(() => () => plane.dispose(), [plane])\n const mesh = useRef<THREE.Mesh>(null)\n\n const state = useRef({\n key: Symbol('unset') as unknown,\n time: 0,\n owed: 0,\n /** The fire's clock when the rim last released anything — flame or smoulder. */\n lastGas: Number.NEGATIVE_INFINITY,\n origin: new THREE.Vector3(),\n right: new THREE.Vector3(1, 0, 0),\n normal: new THREE.Vector3(0, 0, 1),\n })\n const anchors = useRef<FlameAnchor[]>([])\n const sources = useMemo(() => new Float32Array(MAX_SOURCES * 4), [])\n const across = useMemo(() => new Float32Array(MAX_SOURCES * 4), [])\n const smoulder = useMemo(\n () =>\n Array.from({ length: SMOULDER_SLOTS }, () => ({\n seen: Number.NEGATIVE_INFINITY,\n source: new Float32Array(4),\n across: new Float32Array(4),\n })),\n [],\n )\n const scratch = useMemo(() => new THREE.Vector3(), [])\n\n /** Where the domain stands: a vertical plane through the sheet, facing the camera. */\n const place = () => {\n const s = state.current\n const centre = locate(0.5, 0.5)\n const c = new THREE.Vector3(centre?.x ?? 0, centre?.y ?? 0, centre?.z ?? 0)\n s.normal.set(camera.position.x - c.x, 0, camera.position.z - c.z)\n if (s.normal.lengthSq() < 1e-8) s.normal.set(0, 0, 1)\n s.normal.normalize()\n s.right.crossVectors(up, s.normal).normalize()\n s.origin\n .copy(c)\n .addScaledVector(s.right, -DOMAIN.width / 2)\n .addScaledVector(up, -BELOW)\n const m = mesh.current\n if (m) {\n m.position\n .copy(s.origin)\n .addScaledVector(s.right, DOMAIN.width / 2)\n .addScaledVector(up, DOMAIN.height / 2)\n // A little toward the viewer, so the flames stand in front of the paper.\n m.position.addScaledVector(s.normal, 0.03)\n m.quaternion.setFromRotationMatrix(new THREE.Matrix4().makeBasis(s.right, up, s.normal))\n }\n }\n\n /**\n * The rim, as short lines of emission in the domain's UV: one per flame,\n * as long as its tongue is wide, a band ~1.2 mm across, laid just onto the\n * paper side of the edge. Lengths are in the pass's aspect-corrected space,\n * where a world length is `length / DOMAIN.height`.\n */\n const gather = (time: number): number => {\n const s = state.current\n const room = MAX_SOURCES - SMOULDER_SLOTS\n const n = Math.min(room, flameAnchors(field, locate, room, anchors.current, time))\n const band = BAND / DOMAIN.height\n for (let i = 0; i < n; i++) {\n const a = anchors.current[i]!\n scratch.set(a.x, a.y, a.z).sub(s.origin)\n const k = i * 4\n sources[k] = scratch.dot(s.right) / DOMAIN.width\n sources[k + 1] = scratch.dot(up) / DOMAIN.height\n // Which way the paper lies, in the plane the fire is drawn in.\n const nu = a.nx * s.right.x + a.ny * s.right.y + a.nz * s.right.z\n const nv = a.ny\n const nl = Math.hypot(nu, nv)\n let area: number\n if (nl > 0.25) {\n const half = Math.max(1.5 / 210, a.width * 0.5) / DOMAIN.height\n sources[k + 2] = half\n across[k] = nu / nl\n across[k + 1] = nv / nl\n across[k + 2] = band\n across[k + 3] = band\n area = half * band\n } else {\n // A rim seen end-on has no direction in this plane: a small disc.\n sources[k + 2] = band * 2\n across[k] = 0\n across[k + 1] = 1\n across[k + 2] = band * 2\n across[k + 3] = 0\n area = band * band * 4\n }\n // Tall clusters release more gas than short licks: per length of rim,\n // in proportion to the height. It was in proportion to height × width²\n // — the disc's area — and since width follows height that is height³:\n // a lower-rim lick at a third of the height got a thirtieth of the gas,\n // never grew enough soot to show, and the ring lost its short flames.\n const disc = Math.max(0.0025, (a.width * 0.6) / DOMAIN.height)\n const tallest = (FLAME_HEIGHT[1] * 0.47 * 0.6) / DOMAIN.height\n sources[k + 3] =\n (a.height / FLAME_HEIGHT[1]) * (0.5 + 0.5 * a.heat) * Math.min(30, (disc * tallest) / area)\n // This spot is burning: remember it, so it smokes once it stops.\n const slot = smoulder[Math.floor(a.seed * SMOULDER_SLOTS) % SMOULDER_SLOTS]!\n slot.seen = time\n slot.source.set(sources.subarray(k, k + 4))\n slot.across.set(across.subarray(k, k + 4))\n }\n // Spots whose flame has gone out, smoking and fading — flagged by a\n // negative strength, which the emission pass turns into smoke alone.\n const after = Math.max(0, merged.smokeAfter ?? 0)\n let count = n\n for (const slot of smoulder) {\n const since = time - slot.seen\n // Only once the flame has really gone. Flames hop along the rim every\n // ~0.7 s, so a slot left unwritten for a step or two is a flame that\n // moved, not one that went out — and smoking at once from every such\n // slot poured a grey cloud over the sheet while it was still burning.\n if (!(since > SMOULDER_AFTER) || after <= 0 || since > after * 3) continue\n const k = count * 4\n sources.set(slot.source, k)\n across.set(slot.across, k)\n const onset = Math.min(1, (since - SMOULDER_AFTER) / 0.6)\n sources[k + 3] = -slot.source[3]! * onset * Math.exp(-since / after)\n count++\n }\n return count\n }\n\n useFrame((_, delta) => {\n if (!fluid) return\n const s = state.current\n const u = solverUniforms(merged)\n fluid.sharp = sharp ?? quality !== 'low'\n if (s.key !== resetKey) {\n s.key = resetKey\n place()\n fluid.reset(u.ambient)\n for (const slot of smoulder) slot.seen = Number.NEGATIVE_INFINITY\n const end = field.time\n // Warm up from the rim as it stands, on the burn's own clock, so the\n // same moment of the same burn draws the same fire. Not when nothing is\n // burning: the warm-up replays THIS field at earlier times, so with no\n // front it would run 180 steps releasing nothing — which /hands paid on\n // every fresh sheet.\n if (field.frontCount > 0) {\n const steps = Math.round(warm / WARM_STEP)\n for (let k = 0; k < steps; k++) {\n const t = end - warm + k * WARM_STEP\n fluid.step(WARM_STEP, u, sources, across, gather(t), t)\n }\n s.lastGas = end\n } else {\n s.lastGas = Number.NEGATIVE_INFINITY\n }\n s.time = end\n s.owed = 0\n }\n // Idle: nothing burning, and nothing released for long enough that the\n // smoke left is under 1% (five of its lifetimes). Then the fire costs\n // nothing — no solver steps, no plane drawn. It used to step every frame\n // from mount, so a page that mounts the fire before anything burns (/hands\n // does, from the first frame) ran an empty simulation for as long as it\n // was open: on a GPU a waste, and on CI's CPU-drawn WebGL the reason the\n // Hands check went from under 5 minutes to over 20. A new front wakes it.\n const idle = field.frontCount === 0 && s.time - s.lastGas > Math.max(2, merged.smokeFade * 5)\n const m = mesh.current\n if (m) m.visible = !idle\n if (running && idle) {\n s.owed = 0\n s.time += Math.min(0.1, Math.max(0, delta))\n } else if (running) {\n s.owed += Math.min(0.1, Math.max(0, delta))\n let taken = 0\n while (s.owed >= STEP && taken < MAX_STEPS) {\n s.owed -= STEP\n s.time += STEP\n const count = gather(field.time)\n fluid.step(STEP, u, sources, across, count, s.time)\n if (count > 0) s.lastGas = s.time\n taken++\n }\n }\n material.uniforms.uA!.value = fluid.scalars.read.texture\n material.uniforms.uB!.value = fluid.air.read.texture\n material.uniforms.uTime!.value = s.time\n material.uniforms.uGlow!.value = glow\n material.uniforms.uHeatScale!.value = heatScale\n material.uniforms.uSootScale!.value = sootScale\n material.uniforms.uContrast!.value = contrast\n material.uniforms.uOpacity!.value = opacity\n material.uniforms.uThin!.value = thin\n // Read every frame, so a zone tuned in the lab shows at once, paused or not.\n const z = fireZones(zones)\n const mu = material.uniforms\n ;(mu.uTipColor!.value as THREE.Vector3).fromArray(hexToLinear(z.tip.color))\n ;(mu.uBodyColor!.value as THREE.Vector3).fromArray(hexToLinear(z.body.color))\n ;(mu.uCoreColor!.value as THREE.Vector3).fromArray(hexToLinear(z.core.color))\n ;(mu.uRootColor!.value as THREE.Vector3).fromArray(hexToLinear(z.root.color))\n mu.uTipGlow!.value = z.tip.glow\n mu.uBodyGlow!.value = z.body.glow\n mu.uCoreGlow!.value = z.core.glow\n mu.uTipFrom!.value = z.tip.from\n mu.uTipTo!.value = z.tip.to\n mu.uCoreFrom!.value = z.core.from\n mu.uSoftness!.value = z.tip.softness\n mu.uTearing!.value = z.tip.tearing\n mu.uRootAmount!.value = z.root.amount\n mu.uRootReach!.value = z.root.reach\n })\n\n if (!fluid) return <>{fallback ?? null}</>\n return <mesh ref={mesh} geometry={plane} material={material} frustumCulled={false} renderOrder={2} />\n}\n","import * as THREE from 'three'\nimport type { SolverUniforms } from './params'\nimport {\n ADVECT,\n CURL,\n DIVERGENCE,\n EMIT,\n FILL,\n FORCES,\n GRADIENT,\n MACCORMACK,\n MAX_SOURCES,\n PASS_VERTEX,\n PRESSURE,\n REACT,\n VORTICITY,\n} from './passes'\n\n/** Grid sizes for one tier. Both grids share the domain's aspect, so cells are square. */\nexport interface FluidGrid {\n /** Velocity and pressure — coarse, because they are smooth. */\n velocity: readonly [number, number]\n /** Fuel, heat, smoke, flame and air — fine, because that is what is drawn. */\n dye: readonly [number, number]\n /** Jacobi iterations for the pressure solve. */\n iterations: number\n}\n\n/** A pair of targets, read one and write the other, swapped after each pass. */\nclass Pair {\n read: THREE.WebGLRenderTarget\n write: THREE.WebGLRenderTarget\n constructor(w: number, h: number) {\n this.read = target(w, h)\n this.write = target(w, h)\n }\n swap(): void {\n const t = this.read\n this.read = this.write\n this.write = t\n }\n dispose(): void {\n this.read.dispose()\n this.write.dispose()\n }\n}\n\nfunction target(w: number, h: number): THREE.WebGLRenderTarget {\n return new THREE.WebGLRenderTarget(w, h, {\n type: THREE.HalfFloatType,\n format: THREE.RGBAFormat,\n minFilter: THREE.LinearFilter,\n magFilter: THREE.LinearFilter,\n wrapS: THREE.ClampToEdgeWrapping,\n wrapT: THREE.ClampToEdgeWrapping,\n depthBuffer: false,\n stencilBuffer: false,\n generateMipmaps: false,\n })\n}\n\nfunction pass(fragmentShader: string, uniforms: Record<string, THREE.IUniform>): THREE.ShaderMaterial {\n return new THREE.ShaderMaterial({\n vertexShader: PASS_VERTEX,\n fragmentShader,\n uniforms,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NoBlending,\n })\n}\n\n/**\n * A fire, simulated on the GPU: a 2D grid fluid with combustion.\n *\n * Owns its render targets and the passes over them, and nothing about where\n * the fire is drawn — `FxFireFluid` places it. Every step reads the burning\n * rim as a list of emission points (position, radius, strength) the caller\n * works out on the CPU from the damage field, so nothing is ever read back\n * from the GPU.\n */\nexport class FireFluid {\n /** (fuel, heat, smoke, soot) on the fine grid — what is drawn. */\n readonly scalars: Pair\n /** (premixed oxygen, ambient oxygen, burn rate, flame heat) on the fine grid. */\n readonly air: Pair\n readonly velocity: Pair\n readonly pressure: Pair\n /** The rim's emission this step, on the fine grid — see EMIT. */\n private readonly emit: THREE.WebGLRenderTarget\n private readonly divergence: THREE.WebGLRenderTarget\n private readonly curl: THREE.WebGLRenderTarget\n /** The two intermediate advections the MacCormack step compares. */\n private readonly forward: THREE.WebGLRenderTarget\n private readonly backward: THREE.WebGLRenderTarget\n /**\n * Error-compensated advection for the drawn fields. On by default; off is\n * a single semi-Lagrangian step, which is two passes a step cheaper and\n * visibly softer — a knob for the lowest tier, and for an A/B.\n */\n sharp = true\n private readonly scene = new THREE.Scene()\n private readonly camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)\n private readonly mesh: THREE.Mesh\n private readonly m: Record<string, THREE.ShaderMaterial>\n private readonly sources: THREE.Vector4[] = Array.from({ length: MAX_SOURCES }, () => new THREE.Vector4())\n private readonly across: THREE.Vector4[] = Array.from({ length: MAX_SOURCES }, () => new THREE.Vector4())\n\n /** Whether this renderer can draw into half-float targets at all. */\n static supported(renderer: THREE.WebGLRenderer): boolean {\n return (\n renderer.capabilities.isWebGL2 &&\n (renderer.extensions.has('EXT_color_buffer_float') ||\n renderer.extensions.has('EXT_color_buffer_half_float'))\n )\n }\n\n constructor(\n private readonly renderer: THREE.WebGLRenderer,\n readonly grid: FluidGrid,\n /** The domain's size in world units — width, height. */\n readonly domain: { width: number; height: number },\n ) {\n const [vw, vh] = grid.velocity\n const [dw, dh] = grid.dye\n this.scalars = new Pair(dw, dh)\n this.air = new Pair(dw, dh)\n this.velocity = new Pair(vw, vh)\n this.pressure = new Pair(vw, vh)\n this.emit = target(dw, dh)\n this.divergence = target(vw, vh)\n this.curl = target(vw, vh)\n this.forward = target(dw, dh)\n this.backward = target(dw, dh)\n\n const aspect = domain.width / domain.height\n const vTexel = new THREE.Vector2(1 / vw, 1 / vh)\n const dTexel = new THREE.Vector2(1 / dw, 1 / dh)\n const cell = domain.width / vw\n this.m = {\n emit: pass(EMIT, {\n uSources: { value: this.sources },\n uAcross: { value: this.across },\n uCount: { value: 0 },\n uAspect: { value: aspect },\n }),\n advect: pass(ADVECT, {\n uVelocity: { value: null },\n uSource: { value: null },\n uDomain: { value: new THREE.Vector2(domain.width, domain.height) },\n uDt: { value: 0 },\n uKeep: { value: new THREE.Vector4(1, 1, 1, 1) },\n }),\n react: pass(REACT, {\n uA: { value: null },\n uB: { value: null },\n uEmit: { value: this.emit.texture },\n uVelocity: { value: null },\n uDomain: { value: new THREE.Vector2(domain.width, domain.height) },\n uTexel: { value: dTexel },\n uOut: { value: 0 },\n uDt: { value: 0 },\n uFuel: { value: 0 },\n uHeat: { value: 0 },\n uSmoke: { value: 0 },\n uPremixed: { value: 0 },\n uAmbient: { value: 0 },\n uBurnRate: { value: 0 },\n uHeatRelease: { value: 0 },\n uCooling: { value: 0 },\n uSmokeProduction: { value: 0 },\n uSmokeFade: { value: 1 },\n uPersistence: { value: 0.1 },\n uMixing: { value: 0 },\n uEntrain: { value: 0 },\n uFuelBlock: { value: 1 },\n uSootYield: { value: 0 },\n uSootHeat: { value: 1 },\n uStoich: { value: 1 },\n uSmoulderSmoke: { value: 0 },\n uSmoulderHeat: { value: 0 },\n }),\n forces: pass(FORCES, {\n uVelocity: { value: null },\n uA: { value: null },\n uEmit: { value: this.emit.texture },\n uTexel: { value: vTexel },\n uDomain: { value: new THREE.Vector2(domain.width, domain.height) },\n uDt: { value: 0 },\n uTime: { value: 0 },\n uBuoyancy: { value: 0 },\n uWind: { value: 0 },\n uTurbulence: { value: 0 },\n uTurbScale: { value: 1 },\n uTurbEvolve: { value: 0 },\n uRadial: { value: 0 },\n uInitVel: { value: new THREE.Vector2() },\n }),\n curl: pass(CURL, { uVelocity: { value: null }, uTexel: { value: vTexel }, uCell: { value: cell } }),\n vorticity: pass(VORTICITY, {\n uVelocity: { value: null },\n uCurl: { value: null },\n uTexel: { value: vTexel },\n uCell: { value: cell },\n uDt: { value: 0 },\n uVorticity: { value: 0 },\n }),\n divergence: pass(DIVERGENCE, {\n uVelocity: { value: null },\n uB: { value: null },\n uTexel: { value: vTexel },\n uCell: { value: cell },\n uExpansion: { value: 0 },\n }),\n pressure: pass(PRESSURE, {\n uPressure: { value: null },\n uDivergence: { value: null },\n uTexel: { value: vTexel },\n uCell: { value: cell },\n }),\n gradient: pass(GRADIENT, {\n uVelocity: { value: null },\n uPressure: { value: null },\n uTexel: { value: vTexel },\n uCell: { value: cell },\n }),\n fill: pass(FILL, { uValue: { value: new THREE.Vector4() } }),\n maccormack: pass(MACCORMACK, {\n uVelocity: { value: null },\n uSource: { value: null },\n uForward: { value: null },\n uBackward: { value: null },\n uDomain: { value: new THREE.Vector2(domain.width, domain.height) },\n uTexel: { value: new THREE.Vector2(1 / dw, 1 / dh) },\n uDt: { value: 0 },\n }),\n }\n const triangle = new THREE.BufferGeometry()\n triangle.setAttribute(\n 'position',\n new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3),\n )\n this.mesh = new THREE.Mesh(triangle, this.m.fill)\n this.mesh.frustumCulled = false\n this.scene.add(this.mesh)\n }\n\n /** Empty air, at the given ambient oxygen. */\n reset(ambient: number): void {\n this.withRenderer(() => {\n const fill = this.m.fill!\n const value = fill.uniforms.uValue!.value as THREE.Vector4\n value.set(0, 0, 0, 0)\n for (const t of [this.scalars, this.velocity, this.pressure]) {\n this.run(fill, t.read)\n this.run(fill, t.write)\n }\n this.run(fill, this.divergence)\n this.run(fill, this.curl)\n this.run(fill, this.emit)\n value.set(0, ambient, 0, 0)\n this.run(fill, this.air.read)\n this.run(fill, this.air.write)\n })\n }\n\n /**\n * One step of `dt` seconds. `sources` is (u, v, half-length, strength) per\n * rim segment and `across` is (toward-paper x, y, half-width, offset onto\n * the paper) — see EMIT — `count` of each; `time` drives the turbulence.\n */\n step(\n dt: number,\n u: SolverUniforms,\n sources: Float32Array,\n across: Float32Array,\n count: number,\n time: number,\n ): void {\n const n = Math.min(MAX_SOURCES, count)\n for (let i = 0; i < n; i++) {\n this.sources[i]!.fromArray(sources, i * 4)\n this.across[i]!.fromArray(across, i * 4)\n }\n const m = this.m\n m.emit!.uniforms.uCount!.value = n\n\n this.withRenderer(() => {\n // 0. Where the rim releases gas this step, once, for every pass after.\n this.run(m.emit!, this.emit)\n\n // 1. Carry everything along the flow.\n const advect = m.advect!\n advect.uniforms.uDt!.value = dt\n advect.uniforms.uVelocity!.value = this.velocity.read.texture\n ;(advect.uniforms.uKeep!.value as THREE.Vector4).set(0.998, 0.998, 1, 1)\n advect.uniforms.uSource!.value = this.velocity.read.texture\n this.run(advect, this.velocity.write)\n this.velocity.swap()\n advect.uniforms.uVelocity!.value = this.velocity.read.texture\n ;(advect.uniforms.uKeep!.value as THREE.Vector4).set(1, 1, 1, 1)\n if (this.sharp) {\n // What is drawn, advected with its own error given back — see\n // MACCORMACK. Forward, back again, then the corrected, clamped result.\n advect.uniforms.uSource!.value = this.scalars.read.texture\n this.run(advect, this.forward)\n advect.uniforms.uDt!.value = -dt\n advect.uniforms.uSource!.value = this.forward.texture\n this.run(advect, this.backward)\n advect.uniforms.uDt!.value = dt\n const mc = m.maccormack!\n mc.uniforms.uDt!.value = dt\n mc.uniforms.uVelocity!.value = this.velocity.read.texture\n mc.uniforms.uSource!.value = this.scalars.read.texture\n mc.uniforms.uForward!.value = this.forward.texture\n mc.uniforms.uBackward!.value = this.backward.texture\n this.run(mc, this.scalars.write)\n } else {\n advect.uniforms.uSource!.value = this.scalars.read.texture\n this.run(advect, this.scalars.write)\n }\n this.scalars.swap()\n advect.uniforms.uSource!.value = this.air.read.texture\n this.run(advect, this.air.write)\n this.air.swap()\n\n // 2. Release gas at the rim, and burn what has oxygen.\n const react = m.react!\n const r = react.uniforms\n r.uDt!.value = dt\n r.uFuel!.value = u.fuel\n r.uHeat!.value = u.heat\n r.uSmoke!.value = u.smoke\n r.uPremixed!.value = u.premixed\n r.uAmbient!.value = u.ambient\n r.uBurnRate!.value = u.burnRate\n r.uHeatRelease!.value = u.heatRelease\n r.uCooling!.value = u.cooling\n r.uSmokeProduction!.value = u.smokeProduction\n r.uSmokeFade!.value = u.smokeFade\n r.uPersistence!.value = u.persistence\n r.uMixing!.value = u.mixing\n r.uEntrain!.value = u.entrain\n r.uFuelBlock!.value = u.fuelBlock\n r.uSootYield!.value = u.sootYield\n r.uSootHeat!.value = u.sootHeat\n r.uStoich!.value = u.stoich\n r.uSmoulderSmoke!.value = u.smoulderSmoke\n r.uSmoulderHeat!.value = u.smoulderHeat\n r.uVelocity!.value = this.velocity.read.texture\n r.uA!.value = this.scalars.read.texture\n r.uB!.value = this.air.read.texture\n r.uOut!.value = 0\n this.run(react, this.scalars.write)\n r.uOut!.value = 1\n this.run(react, this.air.write)\n this.scalars.swap()\n this.air.swap()\n\n // 3. Forces: buoyancy, wind, turbulence, the gas's own launch.\n const forces = m.forces!\n const f = forces.uniforms\n f.uVelocity!.value = this.velocity.read.texture\n f.uA!.value = this.scalars.read.texture\n f.uDt!.value = dt\n f.uTime!.value = time\n f.uBuoyancy!.value = u.buoyancy\n f.uWind!.value = u.wind\n f.uTurbulence!.value = u.turbulence\n f.uTurbScale!.value = u.turbulenceScale\n f.uTurbEvolve!.value = u.turbulenceEvolve\n f.uRadial!.value = u.radial\n ;(f.uInitVel!.value as THREE.Vector2).set(u.initialVelocity[0], u.initialVelocity[1])\n this.run(forces, this.velocity.write)\n this.velocity.swap()\n\n // 4. Vorticity confinement.\n m.curl!.uniforms.uVelocity!.value = this.velocity.read.texture\n this.run(m.curl!, this.curl)\n const vort = m.vorticity!\n vort.uniforms.uVelocity!.value = this.velocity.read.texture\n vort.uniforms.uCurl!.value = this.curl.texture\n vort.uniforms.uDt!.value = dt\n vort.uniforms.uVorticity!.value = u.vorticity\n this.run(vort, this.velocity.write)\n this.velocity.swap()\n\n // 5. Keep the air from compressing — except where burning expands it.\n const div = m.divergence!\n div.uniforms.uVelocity!.value = this.velocity.read.texture\n div.uniforms.uB!.value = this.air.read.texture\n div.uniforms.uExpansion!.value = u.expansion\n this.run(div, this.divergence)\n const pressure = m.pressure!\n pressure.uniforms.uDivergence!.value = this.divergence.texture\n for (let k = 0; k < this.grid.iterations; k++) {\n pressure.uniforms.uPressure!.value = this.pressure.read.texture\n this.run(pressure, this.pressure.write)\n this.pressure.swap()\n }\n const gradient = m.gradient!\n gradient.uniforms.uVelocity!.value = this.velocity.read.texture\n gradient.uniforms.uPressure!.value = this.pressure.read.texture\n this.run(gradient, this.velocity.write)\n this.velocity.swap()\n })\n }\n\n dispose(): void {\n for (const p of [this.scalars, this.air, this.velocity, this.pressure]) p.dispose()\n this.emit.dispose()\n this.divergence.dispose()\n this.curl.dispose()\n this.forward.dispose()\n this.backward.dispose()\n for (const m of Object.values(this.m)) m.dispose()\n this.mesh.geometry.dispose()\n }\n\n private run(material: THREE.ShaderMaterial, out: THREE.WebGLRenderTarget): void {\n this.mesh.material = material\n this.renderer.setRenderTarget(out)\n this.renderer.render(this.scene, this.camera)\n }\n\n /** Passes change the renderer's target and clearing; put both back. */\n private withRenderer(body: () => void): void {\n const previous = this.renderer.getRenderTarget()\n const autoClear = this.renderer.autoClear\n this.renderer.autoClear = false\n try {\n body()\n } finally {\n this.renderer.setRenderTarget(previous)\n this.renderer.autoClear = autoClear\n }\n }\n}\n","/**\n * The fire solver's passes, as GLSL. A grid fluid of the kind real-time fire\n * has been built on since Stam's stable fluids: semi-Lagrangian advection, a\n * Jacobi pressure solve that keeps the air from compressing, buoyancy from\n * temperature, curl-noise turbulence and vorticity confinement for the small\n * eddies that make fire restless — and a combustion step that burns fuel\n * only where AIR HAS REACHED IT, turning it into heat, soot and smoke.\n *\n * That last clause is the whole difference between a flame and a plume of hot\n * smoke, and the first version of this file got it wrong: it refilled every\n * cell toward ambient oxygen, inside the flame as much as outside it, so fuel\n * burnt within a twelfth of a second of leaving the paper wherever it was. A\n * flame is the surface where fuel meets air. The fuel inside it cannot burn\n * until air has worked its way in from the sides, so the fuel core is eaten\n * from the outside and closes to a point — the TIP — at the height where the\n * last of it meets air. Nothing else gives a tongue that shape.\n *\n * Every pass is a full-screen triangle over one field. Velocity and pressure\n * live on a coarser grid; fuel, heat, smoke, soot and air on a finer one,\n * sampling the velocity bilinearly.\n */\n\n/** Maximum emission points the passes loop over. */\nexport const MAX_SOURCES = 64\n\nexport const PASS_VERTEX = /* glsl */ `\nvarying vec2 vUv;\nvoid main() {\n vUv = position.xy * 0.5 + 0.5;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`\n\n/**\n * Hashes with no \\`sin\\` in them (Dave Hoskins' \"hash without sine\").\n *\n * The classic \\`fract(sin(dot(p, k)) * 43758.5)\\` asks \\`sin\\` for an answer\n * to many digits at arguments in the tens of thousands, and every GPU gives a\n * different one: the same fire measured on Metal and on SwiftShader differed\n * in 13% of its pixels, and — because only the hottest percent of a flame\n * clears the bloom threshold — bloomed 1.47% of the frame on one and 0.02% on\n * the other. A fire has to look the same on every device.\n */\nconst NOISE = /* glsl */ `\nfloat fxHash(vec2 p) {\n vec3 p3 = fract(vec3(p.xyx) * 0.1031);\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\nfloat fxNoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(fxHash(i), fxHash(i + vec2(1.0, 0.0)), u.x), mix(fxHash(i + vec2(0.0, 1.0)), fxHash(i + vec2(1.0, 1.0)), u.x), u.y);\n}\nfloat fxFbm(vec2 p) {\n float v = 0.0;\n float a = 0.5;\n for (int i = 0; i < 3; i++) {\n v += a * fxNoise(p);\n p = p * 2.07 + 13.0;\n a *= 0.5;\n }\n return v;\n}\nfloat fxHash3(vec3 p) {\n vec3 p3 = fract(p * 0.1031);\n p3 += dot(p3, p3.zyx + 31.32);\n return fract((p3.x + p3.y) * p3.z);\n}\nfloat fxNoise3(vec3 p) {\n vec3 i = floor(p);\n vec3 f = fract(p);\n vec3 u = f * f * (3.0 - 2.0 * f);\n float a = mix(mix(fxHash3(i), fxHash3(i + vec3(1.0, 0.0, 0.0)), u.x), mix(fxHash3(i + vec3(0.0, 1.0, 0.0)), fxHash3(i + vec3(1.0, 1.0, 0.0)), u.x), u.y);\n float b = mix(mix(fxHash3(i + vec3(0.0, 0.0, 1.0)), fxHash3(i + vec3(1.0, 0.0, 1.0)), u.x), mix(fxHash3(i + vec3(0.0, 1.0, 1.0)), fxHash3(i + vec3(1.0, 1.0, 1.0)), u.x), u.y);\n return mix(a, b, u.z);\n}\nfloat fxFbm3(vec3 p) {\n float v = 0.0;\n float a = 0.5;\n for (int i = 0; i < 3; i++) {\n v += a * fxNoise3(p);\n p = p * 2.07 + 13.0;\n a *= 0.5;\n }\n return v;\n}\n`\n\n/**\n * Sources are short LINES along the rim, not discs.\n *\n * `uSources`: (u, v, half-length along the rim, strength). A NEGATIVE\n * strength is a spot that has stopped burning and only smokes —\n * see `FxFireFluid`'s smoulder.\n * `uAcross`: (toward the paper — a unit vector in aspect-corrected domain\n * space —, half-width across the rim, how far onto the paper).\n *\n * A disc was the old shape: up to 15 mm in radius, releasing gas into the\n * hole and over the paper alike, and it was what drew every flame as a\n * droplet. Gas comes off the char just behind the ember line, along it, in a\n * band a couple of millimetres wide; that band is what this draws.\n */\nconst SOURCES = /* glsl */ `\nuniform vec4 uSources[${MAX_SOURCES}];\nuniform vec4 uAcross[${MAX_SOURCES}];\nuniform int uCount;\nuniform float uAspect;\nfloat fxEmission(vec2 uv, out vec2 outward, out float smoulder) {\n float e = 0.0;\n outward = vec2(0.0);\n smoulder = 0.0;\n for (int i = 0; i < ${MAX_SOURCES}; i++) {\n if (i >= uCount) break;\n vec4 s = uSources[i];\n vec4 k = uAcross[i];\n vec2 d = (uv - s.xy) * vec2(uAspect, 1.0);\n vec2 n = k.xy;\n float across = dot(d, n) - k.w;\n float along = dot(d, vec2(-n.y, n.x));\n float g = abs(s.w) * exp(-along * along / (s.z * s.z) - across * across / (k.z * k.z));\n if (s.w < 0.0) {\n smoulder += g;\n continue;\n }\n e += g;\n // Off the edge, away from the paper it came from.\n outward -= g * n;\n }\n return e;\n}\n`\n\n/**\n * The rim's emission, once per step: (strength, outward.x, outward.y,\n * smouldering strength).\n *\n * The combustion pass used to loop over every source for every texel, TWICE\n * (it runs once per output target), and the forces pass a third time. Written\n * once here and read back as a texture, it costs one loop a step.\n */\nexport const EMIT = /* glsl */ `\nvarying vec2 vUv;\n${SOURCES}\nvoid main() {\n vec2 outward;\n float smoulder;\n float e = fxEmission(vUv, outward, smoulder);\n gl_FragColor = vec4(e, outward, smoulder);\n}\n`\n\n/** Carry a field along the velocity, backward in time. */\nexport const ADVECT = /* glsl */ `\nuniform sampler2D uVelocity;\nuniform sampler2D uSource;\nuniform vec2 uDomain;\nuniform float uDt;\nuniform vec4 uKeep;\nvarying vec2 vUv;\nvoid main() {\n vec2 v = texture2D(uVelocity, vUv).xy;\n vec2 back = vUv - v * uDt / uDomain;\n gl_FragColor = texture2D(uSource, back) * uKeep;\n}\n`\n\n/**\n * The MacCormack correction, for what is drawn.\n *\n * One semi-Lagrangian back-trace loses about half a cell of detail a step,\n * because every read is a bilinear average of four cells; at sixty steps a\n * second, anything the solver resolves is butter within a few frames.\n *\n * MacCormack measures its own error and gives it back: advect forward\n * (uForward), advect THAT back again (uBackward), and the difference between\n * where it ended up and where it started is twice the error one step made.\n * Half of it is added back. Then the result is clamped to the four cells the\n * forward step actually read from, because an uncorrected overshoot is worse\n * than a blur — it grows hot spots brighter than anything the rim ever\n * released, and they ring.\n */\nexport const MACCORMACK = /* glsl */ `\nuniform sampler2D uVelocity;\nuniform sampler2D uSource;\nuniform sampler2D uForward;\nuniform sampler2D uBackward;\nuniform vec2 uDomain;\nuniform vec2 uTexel;\nuniform float uDt;\nvarying vec2 vUv;\nvoid main() {\n vec4 forward = texture2D(uForward, vUv);\n vec4 phi = forward + 0.5 * (texture2D(uSource, vUv) - texture2D(uBackward, vUv));\n vec2 v = texture2D(uVelocity, vUv).xy;\n vec2 back = vUv - v * uDt / uDomain;\n vec2 corner = (floor(back / uTexel - 0.5) + 0.5) * uTexel;\n vec4 a = texture2D(uSource, corner);\n vec4 b = texture2D(uSource, corner + vec2(uTexel.x, 0.0));\n vec4 c = texture2D(uSource, corner + vec2(0.0, uTexel.y));\n vec4 d = texture2D(uSource, corner + uTexel);\n gl_FragColor = clamp(phi, min(min(a, b), min(c, d)), max(max(a, b), max(c, d)));\n}\n`\n\n/**\n * Emission and combustion, for the fine grid. Run twice with `uOut` 0 and 1:\n * once to write (fuel, heat, smoke, soot), once for (premixed, oxygen, burn\n * rate, flame heat). The same arithmetic both times, so the two agree.\n *\n * FLAME HEAT is the part of the heat that burning made, carried and cooled\n * like the rest; it is what a flame's colour is drawn from. The rest of the\n * heat — what the gas left the paper with — makes soot form and lifts the\n * gas, but it is hottest at the root, where a real flame is at its coolest\n * and dimmest, and colouring by it put a white-hot band on every rim.\n *\n * Oxygen reaches fuel two ways, and neither is \"everywhere at once\":\n *\n * mixing from the air beside it, in the plane — a diffusion step\n * over the four neighbours.\n * entrainment from in front and behind: a slice through a fire has no\n * third dimension for air to come from, so it is put back\n * here — but dense fuel BLOCKS it, because the air drawn into\n * a real tongue meets the fuel's outside first. That is what\n * keeps a fuel core fuel-rich and burning only at its skin.\n *\n * Soot is what a flame's light comes from, and the only thing drawn as flame\n * (the render pass reads it, not the heat). It forms from fuel that is hot,\n * burns away where there is oxygen, and goes out when the gas cools — so a\n * tongue glows from the paper to the height where its fuel runs out, and no\n * further. `persistence` is how long soot lasts in open air.\n */\nexport const REACT = /* glsl */ `\nuniform sampler2D uA;\nuniform sampler2D uB;\nuniform sampler2D uEmit;\nuniform sampler2D uVelocity;\nuniform vec2 uDomain;\nuniform vec2 uTexel;\nuniform int uOut;\nuniform float uDt;\nuniform float uFuel;\nuniform float uHeat;\nuniform float uSmoke;\nuniform float uPremixed;\nuniform float uAmbient;\nuniform float uBurnRate;\nuniform float uHeatRelease;\nuniform float uCooling;\nuniform float uSmokeProduction;\nuniform float uSmokeFade;\nuniform float uPersistence;\nuniform float uMixing;\nuniform float uEntrain;\nuniform float uFuelBlock;\nuniform float uSootYield;\nuniform float uSootHeat;\nuniform float uStoich;\nuniform float uSmoulderSmoke;\nuniform float uSmoulderHeat;\nvarying vec2 vUv;\nvoid main() {\n vec4 a = texture2D(uA, vUv);\n vec4 b = texture2D(uB, vUv);\n // What a parcel picked up crossing the rim during the step — sampled back\n // along its path, not at the one point it ended at. The band gas comes off\n // is ~2 mm across and the gas crosses it at ~5 mm a step at 60 Hz, so a\n // point sample drew one line of fuel per step: horizontal stripes up every\n // tongue.\n vec2 path = texture2D(uVelocity, vUv).xy * uDt / uDomain;\n vec4 em = 0.25 * (texture2D(uEmit, vUv) + texture2D(uEmit, vUv - path * 0.25) +\n texture2D(uEmit, vUv - path * 0.5) + texture2D(uEmit, vUv - path * 0.75));\n float e = em.r;\n // What the rim releases this step — and, where a flame has gone out, the\n // warm smoke the spot goes on giving off for a while.\n float fuel = a.r + e * uFuel * uDt;\n float heat = a.g + (e * uHeat + em.a * uSmoulderHeat) * uDt;\n float smoke = a.b + (e * uSmoke + em.a * uSmoulderSmoke) * uDt;\n float premixed = b.r + e * uFuel * uPremixed * uDt;\n // Air, mixed in from beside and entrained from in front and behind.\n float around = 0.25 * (\n texture2D(uB, vUv + vec2(uTexel.x, 0.0)).g + texture2D(uB, vUv - vec2(uTexel.x, 0.0)).g +\n texture2D(uB, vUv + vec2(0.0, uTexel.y)).g + texture2D(uB, vUv - vec2(0.0, uTexel.y)).g);\n float oxygen = mix(b.g, around, uMixing);\n float reach = exp(-fuel / max(uFuelBlock, 1e-4));\n oxygen += (uAmbient - oxygen) * (1.0 - exp(-uEntrain * reach * uDt));\n // Fuel burns only as far as there is oxygen for it — premixed first — and a\n // unit of fuel takes uStoich of air. At one to one (as it once was) fuel was\n // never denser than the air around it, so nothing ever ran out of air and\n // every scrap of fuel burnt where it stood: no core, no tip.\n float burn = min(fuel, premixed + oxygen / uStoich) * (1.0 - exp(-uBurnRate * uDt));\n fuel = max(0.0, fuel - burn) * exp(-uDt * 0.6);\n float fromPremixed = min(premixed, burn);\n premixed = max(0.0, premixed - fromPremixed) * exp(-uDt * 0.6);\n oxygen = max(0.0, oxygen - (burn - fromPremixed) * uStoich);\n heat = (heat + burn * uHeatRelease) * exp(-uCooling * uDt);\n float flameHeat = (b.a + burn * uHeatRelease) * exp(-uCooling * uDt);\n smoke = (smoke + burn * uSmokeProduction) * exp(-uDt / uSmokeFade);\n float rate = burn / max(uDt, 1e-4);\n // Soot: forms from hot fuel, burns where there is air, and is gone once the\n // gas is too cool to glow.\n float hot = heat / max(uSootHeat, 1e-3);\n float soot = a.a + uSootYield * (fuel + burn) * smoothstep(0.3, 0.8, hot) * uDt;\n float air = oxygen / max(uAmbient, 0.05);\n // Soot that cools stops glowing within a few frames — it is smoke now, and\n // the smoke channel already carries that. Held to glowing only below a\n // twentieth of uSootHeat, it outlived the flame by a hand's breadth and\n // drew ribbons and hooks of flame colour high over the sheet.\n float cold = 1.0 - smoothstep(0.2, 0.6, hot);\n soot *= exp(-uDt * (air / max(uPersistence, 1e-3) + cold * 25.0));\n // An open top: whatever reaches it leaves.\n float open = 1.0 - smoothstep(0.9, 1.0, vUv.y);\n if (uOut == 0) gl_FragColor = vec4(fuel, heat, smoke * open, soot) * vec4(open, open, 1.0, open);\n else gl_FragColor = vec4(premixed * open, oxygen, rate, flameHeat * open);\n}\n`\n\n/** Buoyancy, wind, turbulence and the emission's own push, on the coarse grid. */\nexport const FORCES = /* glsl */ `\nuniform sampler2D uVelocity;\nuniform sampler2D uA;\nuniform sampler2D uEmit;\nuniform vec2 uTexel;\nuniform vec2 uDomain;\nuniform float uDt;\nuniform float uTime;\nuniform float uBuoyancy;\nuniform float uWind;\nuniform float uTurbulence;\nuniform float uTurbScale;\nuniform float uTurbEvolve;\nuniform float uRadial;\nuniform vec2 uInitVel;\nvarying vec2 vUv;\n${NOISE}\nvoid main() {\n vec2 v = texture2D(uVelocity, vUv).xy;\n vec4 a = texture2D(uA, vUv);\n // Hot gas rises.\n v.y += uBuoyancy * a.g * uDt;\n v.x += uWind * uDt;\n // Curl noise: divergence-free eddies, in world units, carried up with the\n // gas AND changing as they go. The third coordinate is time: a 2D pattern\n // that only scrolled was a wave travelling up a still flame, never a flame\n // that flickers.\n float drift = uTime * 0.8 + 1.7 * fxNoise(vec2(uTime * 0.23, 3.1));\n vec3 p = vec3(vUv * uDomain * uTurbScale + vec2(0.37 * fxNoise(vec2(uTime * 0.17, 9.0)), -drift), uTime * uTurbEvolve);\n float h = 0.04;\n float n = fxFbm3(p);\n vec2 curl = vec2(fxFbm3(p + vec3(0.0, h, 0.0)) - n, -(fxFbm3(p + vec3(h, 0.0, 0.0)) - n)) / h;\n v += curl * uTurbulence * uDt * (0.2 + clamp(a.g, 0.0, 2.0));\n // The gas leaves the paper at its own speed, pushed off the edge if asked.\n vec4 em = texture2D(uEmit, vUv);\n float e = em.r;\n vec2 launch = uInitVel + uRadial * em.gb / max(e, 1e-4);\n v = mix(v, launch, clamp(e * uDt * 6.0, 0.0, 1.0));\n // Walls on three sides; the top is open.\n if (vUv.x < uTexel.x || vUv.x > 1.0 - uTexel.x || vUv.y < uTexel.y) v = vec2(0.0);\n gl_FragColor = vec4(v, 0.0, 1.0);\n}\n`\n\nexport const CURL = /* glsl */ `\nuniform sampler2D uVelocity;\nuniform vec2 uTexel;\nuniform float uCell;\nvarying vec2 vUv;\nvoid main() {\n float l = texture2D(uVelocity, vUv - vec2(uTexel.x, 0.0)).y;\n float r = texture2D(uVelocity, vUv + vec2(uTexel.x, 0.0)).y;\n float b = texture2D(uVelocity, vUv - vec2(0.0, uTexel.y)).x;\n float t = texture2D(uVelocity, vUv + vec2(0.0, uTexel.y)).x;\n gl_FragColor = vec4((r - l - t + b) * 0.5 / uCell, 0.0, 0.0, 1.0);\n}\n`\n\n/** Vorticity confinement: give back the small swirls the grid smooths away. */\nexport const VORTICITY = /* glsl */ `\nuniform sampler2D uVelocity;\nuniform sampler2D uCurl;\nuniform vec2 uTexel;\nuniform float uCell;\nuniform float uDt;\nuniform float uVorticity;\nvarying vec2 vUv;\nvoid main() {\n float l = abs(texture2D(uCurl, vUv - vec2(uTexel.x, 0.0)).x);\n float r = abs(texture2D(uCurl, vUv + vec2(uTexel.x, 0.0)).x);\n float b = abs(texture2D(uCurl, vUv - vec2(0.0, uTexel.y)).x);\n float t = abs(texture2D(uCurl, vUv + vec2(0.0, uTexel.y)).x);\n float w = texture2D(uCurl, vUv).x;\n vec2 n = vec2(r - l, t - b);\n n /= length(n) + 1e-5;\n vec2 v = texture2D(uVelocity, vUv).xy + uVorticity * uCell * vec2(n.y, -n.x) * w * uDt;\n gl_FragColor = vec4(v, 0.0, 1.0);\n}\n`\n\n/** How much the flow compresses — minus the gas burning expands by. */\nexport const DIVERGENCE = /* glsl */ `\nuniform sampler2D uVelocity;\nuniform sampler2D uB;\nuniform vec2 uTexel;\nuniform float uCell;\nuniform float uExpansion;\nvarying vec2 vUv;\nvoid main() {\n float l = texture2D(uVelocity, vUv - vec2(uTexel.x, 0.0)).x;\n float r = texture2D(uVelocity, vUv + vec2(uTexel.x, 0.0)).x;\n float b = texture2D(uVelocity, vUv - vec2(0.0, uTexel.y)).y;\n float t = texture2D(uVelocity, vUv + vec2(0.0, uTexel.y)).y;\n float div = (r - l + t - b) * 0.5 / uCell - uExpansion * texture2D(uB, vUv).b;\n gl_FragColor = vec4(div, 0.0, 0.0, 1.0);\n}\n`\n\nexport const PRESSURE = /* glsl */ `\nuniform sampler2D uPressure;\nuniform sampler2D uDivergence;\nuniform vec2 uTexel;\nuniform float uCell;\nvarying vec2 vUv;\nvoid main() {\n float l = texture2D(uPressure, vUv - vec2(uTexel.x, 0.0)).x;\n float r = texture2D(uPressure, vUv + vec2(uTexel.x, 0.0)).x;\n float b = texture2D(uPressure, vUv - vec2(0.0, uTexel.y)).x;\n float t = texture2D(uPressure, vUv + vec2(0.0, uTexel.y)).x;\n float div = texture2D(uDivergence, vUv).x;\n gl_FragColor = vec4((l + r + b + t - div * uCell * uCell) * 0.25, 0.0, 0.0, 1.0);\n}\n`\n\nexport const GRADIENT = /* glsl */ `\nuniform sampler2D uVelocity;\nuniform sampler2D uPressure;\nuniform vec2 uTexel;\nuniform float uCell;\nvarying vec2 vUv;\nvoid main() {\n float l = texture2D(uPressure, vUv - vec2(uTexel.x, 0.0)).x;\n float r = texture2D(uPressure, vUv + vec2(uTexel.x, 0.0)).x;\n float b = texture2D(uPressure, vUv - vec2(0.0, uTexel.y)).x;\n float t = texture2D(uPressure, vUv + vec2(0.0, uTexel.y)).x;\n vec2 v = texture2D(uVelocity, vUv).xy - vec2(r - l, t - b) * 0.5 / uCell;\n gl_FragColor = vec4(v, 0.0, 1.0);\n}\n`\n\nexport const FILL = /* glsl */ `\nuniform vec4 uValue;\nvoid main() {\n gl_FragColor = uValue;\n}\n`\n\n/** How the fine grid is drawn: soot glowing at its temperature, and smoke lit warm from below. */\nexport const RENDER_VERTEX = /* glsl */ `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`\n\n/**\n * Two fields, two jobs. The first version drew everything — outline, colour,\n * opacity, brightness — from temperature alone, and the contours of one\n * smooth scalar are nested smooth rings: an airbrushed blob with a white spot\n * in it, however the bands were tuned.\n *\n * soot WHERE the flame is and how dense: its outline, its opacity,\n * how much light it can give. It ends where the fuel runs out.\n * flame heat WHAT COLOUR that soot glows: the zones, tip to core. The\n * heat burning made, not the gas's own (see REACT).\n */\nexport const RENDER_FRAGMENT = /* glsl */ `\nuniform sampler2D uA;\nuniform sampler2D uB;\nuniform float uTime;\nuniform float uGlow;\nuniform float uSmokeDensity;\nuniform float uPaperWhite;\nuniform float uHeatScale;\nuniform float uSootScale;\nuniform float uContrast;\nuniform float uOpacity;\nuniform float uThin;\nuniform vec3 uTipColor;\nuniform vec3 uBodyColor;\nuniform vec3 uCoreColor;\nuniform vec3 uRootColor;\nuniform float uTipGlow;\nuniform float uBodyGlow;\nuniform float uCoreGlow;\nuniform float uTipFrom;\nuniform float uTipTo;\nuniform float uCoreFrom;\nuniform float uSoftness;\nuniform float uTearing;\nuniform float uRootAmount;\nuniform float uRootReach;\nvarying vec2 vUv;\n${NOISE}\nvoid main() {\n // Detail finer than the grid, none of it touching the solve: a small domain\n // warp so a tongue's outline is never the grid's.\n vec2 w = vUv * vec2(26.0, 18.0) + vec2(0.0, -uTime * 1.6);\n vec2 warp = vec2(fxFbm(w), fxFbm(w + 31.7)) - 0.5;\n vec4 a = texture2D(uA, vUv + warp * 0.004);\n float fuel = a.r;\n float heat = texture2D(uB, vUv + warp * 0.004).a;\n float smoke = a.b;\n // How dense the glowing soot is, against a full flame's (FIRE_SOOT_SCALE).\n float rho = a.a / max(uSootScale, 1e-4);\n // The tip's tearing: carved into the soot where it is thin, so tongues come\n // apart at their edges and keep their bodies.\n vec2 n1 = vUv * vec2(34.0, 24.0) + vec2(0.0, -uTime * 2.4);\n vec2 n2 = vUv * vec2(92.0, 64.0) + vec2(0.0, -uTime * 5.5);\n float grain = (fxFbm(n1) - 0.5) * 0.72 + (fxNoise(n2) - 0.5) * 0.28;\n rho = max(0.0, rho * (1.0 + grain * uTearing * (1.0 - smoothstep(0.1, 0.8, rho))));\n // Temperature against the hottest gas a flame has (FIRE_HEAT_SCALE).\n float t = pow(clamp(heat / max(uHeatScale, 1e-3), 0.0, 2.0), uContrast);\n\n // WHERE: the outline, from the soot.\n float shape = smoothstep(uTipFrom, uTipFrom + uSoftness, rho);\n // WHAT COLOUR: the zones, as bands of temperature.\n float toBody = smoothstep(uTipTo - 0.12, uTipTo + 0.12, t);\n float toCore = smoothstep(uCoreFrom, uCoreFrom + 0.35, t);\n vec3 color = mix(mix(uTipColor, uBodyColor, toBody), uCoreColor, toCore);\n float glow = mix(mix(uTipGlow, uBodyGlow, toBody), uCoreGlow, toCore);\n // Soot emits and absorbs together: thin soot glows as much as it covers, and\n // a dense flame covers what is behind it only as far as uOpacity says.\n float cover = 1.0 - exp(-rho * 3.0);\n float fireAlpha = shape * cover * clamp(uOpacity / 5.0, 0.0, 1.0);\n vec3 fire = color * glow * shape * uPaperWhite * uGlow * smoothstep(0.0, uThin, cover);\n // The root: added as LIGHT, where fresh fuel is still near the paper and the\n // gas has not heated through. Mixed in instead of added, blue makes grey.\n float rootMask = smoothstep(mix(0.4, 0.02, uRootReach), mix(0.6, 0.12, uRootReach), fuel) * (1.0 - smoothstep(0.25, 0.6, t));\n fire += uRootColor * rootMask * uRootAmount * uPaperWhite * uGlow * 0.6;\n\n // Smoke: warm grey-brown (a cool grey over cream reads lavender), lit by\n // the fire under it.\n float smokeAlpha = 1.0 - exp(-smoke * uSmokeDensity);\n vec3 smokeColor = vec3(0.16, 0.13, 0.10) + vec3(0.22, 0.09, 0.02) * smoothstep(0.15, 0.9, t);\n float alpha = smokeAlpha + fireAlpha - smokeAlpha * fireAlpha;\n // No square edge: the domain fades out before its borders.\n float edge = smoothstep(0.0, 0.06, vUv.x) * smoothstep(1.0, 0.94, vUv.x) * smoothstep(0.0, 0.03, vUv.y) * smoothstep(1.0, 0.9, vUv.y);\n gl_FragColor = vec4((fire + smokeColor * smokeAlpha) * edge, alpha * edge);\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n}\n`\n","/**\n * The fire simulator's controls — by the names, and at the defaults, of the\n * fluid-fire tool Noor pointed at (Emission, Combustion, Fuel & air, Motion\n * & turbulence), so the lab's panel reads like the one she knows.\n *\n * The numbers are the panel's, not the solver's. `solverUniforms` is the one\n * place they are turned into this solver's units — world units (a default\n * sheet is one unit, 210 mm, across) and seconds — so a value can be copied\n * between the two tools and mean roughly the same thing, and so tuning the\n * look never means renaming a control.\n *\n * What the chemistry terms mean here, since no tool documents its own:\n *\n * fuel gas released at the burning rim, per second.\n * premixed oxygen the share of that gas that arrives already mixed with\n * oxygen — it can burn the moment it leaves the paper.\n * ambient oxygen oxygen in the surrounding air, which fuel burns with as\n * it mixes in. At 0 only the premixed share ever burns,\n * so flames stay close to the rim.\n * heat temperature released with the gas — paper's gas leaves\n * it hot, and that heat is what lets its soot form and\n * glow before it has burnt (swept to 1, most of the flame\n * went out).\n * flame persistence how long glowing soot lasts in open air before it\n * burns away — the soot a flame's light comes from.\n * air mixing how fast air works into the fuel, and so how far up a\n * tongue's fuel core survives.\n */\nexport interface FireFluidParams {\n // Emission\n fuel: number\n premixedOxygen: number\n heat: number\n smoke: number\n radialImpulse: number\n initialVelocity: readonly [number, number, number]\n // Combustion\n burnRate: number\n gasExpansion: number\n buoyancy: number\n cooling: number\n smokeProduction: number\n // Fuel & air\n ambientOxygen: number\n flamePersistence: number\n /**\n * How fast air works its way into the fuel, 0..1. Ours, not the panel's.\n * Low and the fuel core survives a long way up — tall tongues with long\n * tips; high and the fuel burns out close to the paper — short licks.\n */\n airMixing: number\n // Motion & turbulence\n turbulence: number\n turbulenceScale: number\n vorticity: number\n wind: number\n /** How long smoke lingers, in seconds — long enough and it hangs as a haze. Ours, not the panel's. */\n smokeFade: number\n /**\n * How long a spot of the rim goes on smoking once its flame is out, in\n * seconds. Ours, not the panel's. Paper does not stop smoking when it stops\n * burning: the char is still hot, and a thin thread goes on rising from it.\n */\n smokeAfter: number\n}\n\n/**\n * The defaults — the panel's names, at the values Noor tuned in the lab on\n * 2026-09-12. No turbulence of its own: the motion comes from vorticity,\n * the radial impulse and the uneven rim that feeds it.\n */\nexport const fireFluidDefaults: FireFluidParams = {\n fuel: 33.5,\n premixedOxygen: 0.28,\n heat: 3.2,\n smoke: 0.7,\n // Was 5 — a full unit a second, 210 mm/s, of gas pushed sideways out of the\n // rim. Against a buoyancy of 1.7 units/s² that is what rolled the flames\n // into mushroom caps: the grey spirals over the text that the review called\n // the most synthetic thing in the frame. A flame leaves paper going UP.\n radialImpulse: 0.8,\n // In world units a second now (see `solverUniforms`): 1.4 is ~0.3 m/s, the\n // speed gas leaves burning paper with. It was 0.5 at a quarter scale —\n // 26 mm/s — and gas held that slow pooled into a bulb at the rim before\n // buoyancy stretched it into a neck: every flame was a droplet.\n initialVelocity: [0, 1.4, 0],\n burnRate: 6.1,\n // Was 0.65. Expansion is divergence where the gas burns, and it pushes the\n // gas SIDEWAYS as much as up: at 0.65 each tongue swelled into a puff and\n // the smoke billowed out over the paper. Your web reference is thin licking\n // tongues, and swept at 0.2 / 0.65 / 1.3 the low end is the one that looks\n // like it.\n gasExpansion: 0.3,\n // Was 3, when the flame body was authored below paper white and a tongue\n // needed carrying further to register. Now the gas leaves the rim at its\n // real speed and glows above paper white, and 3 threw tongues past the\n // text; 2 holds them lower. (1.5 was no shorter — height is set by how\n // soon the gas cools, below — only broader and slower.)\n buoyancy: 2,\n // Was 0.92. Gas that stays hot all the way up pools into ONE column: the\n // rim's forty-odd emission points merge a few centimetres above the paper\n // and the fire reads as a single plume with a couple of licks beside it.\n // Hero.png is many separate tongues around the whole rim — tall above,\n // short below — and cooling them faster is what keeps them apart, because\n // each one runs out of glow before it can merge with its neighbour.\n //\n // 1.8 since a flame's light comes from soot that only lives in hot gas:\n // cooling is now what sets a tongue's HEIGHT. Swept in live play: 1.15\n // reached the text (~150 mm), 1.6 ~100 mm, 2.1 short licks (~60 mm);\n // Hero.png's tallest is ~70 mm.\n cooling: 1.8,\n // Was 1.4, which made a thick grey column, then 0.5. Noor's direction is\n // light smoke, and with MacCormack advection keeping the smoke's fine\n // structure it needs less of it to read: at 0.5 it veiled the upper sheet.\n smokeProduction: 0.25,\n ambientOxygen: 0.47,\n // Was 0.005 — the slider's own floor, which is to say switched off. The\n // flame channel decayed with a 5 ms time constant, so it was an\n // instantaneous quantity with no history and therefore no shape.\n flamePersistence: 0.06,\n airMixing: 0.5,\n // Was 0, also switched off, with all the motion coming from vorticity and\n // the radial impulse — a few big eddies instead of many small ones, which\n // is the difference between a fire that tears and one that curls. Swept\n // against the peak frame: at 2 the flames are still a single plume, at 9\n // they break into separate tongues around the rim.\n turbulence: 9,\n // Finer eddies with it: 6.8 was ~24 mm, 12 is ~14 mm, and the smoke stops\n // reading as a few big hooks.\n turbulenceScale: 12,\n // Was 6.1. Vorticity confinement gives back the swirls the grid smooths\n // away; at 6 it was manufacturing swirls the flow never had.\n vorticity: 3,\n wind: 0.8,\n // Was 4.2 s. Smoke that lingers that long is stretched by the flow into\n // long grey veins, and sharper advection keeps every one of them — so the\n // whole upper sheet and the black stage behind it came out MARBLED. Real\n // paper smoke thins out within a second or two. Swept in live play against\n // the default: 1.5 s is thin threads and a clean sheet.\n smokeFade: 1.5,\n smokeAfter: 4,\n}\n\n/** Slider ranges for the lab, grouped the way the panel groups them. */\nexport const fireFluidControls: readonly {\n group: string\n key: Exclude<keyof FireFluidParams, 'initialVelocity'>\n label: string\n min: number\n max: number\n step: number\n}[] = [\n { group: 'Emission', key: 'fuel', label: 'Fuel', min: 0, max: 50, step: 0.1 },\n { group: 'Emission', key: 'premixedOxygen', label: 'Premixed oxygen', min: 0, max: 1, step: 0.01 },\n { group: 'Emission', key: 'heat', label: 'Heat', min: 0, max: 5, step: 0.1 },\n { group: 'Emission', key: 'smoke', label: 'Smoke', min: 0, max: 2, step: 0.05 },\n { group: 'Emission', key: 'radialImpulse', label: 'Radial impulse', min: 0, max: 5, step: 0.1 },\n { group: 'Combustion', key: 'burnRate', label: 'Burn rate', min: 0, max: 10, step: 0.1 },\n { group: 'Combustion', key: 'gasExpansion', label: 'Gas expansion', min: 0, max: 3, step: 0.05 },\n { group: 'Combustion', key: 'buoyancy', label: 'Buoyancy', min: 0, max: 6, step: 0.1 },\n { group: 'Combustion', key: 'cooling', label: 'Cooling', min: 0, max: 3, step: 0.01 },\n { group: 'Combustion', key: 'smokeProduction', label: 'Smoke production', min: 0, max: 4, step: 0.05 },\n { group: 'Fuel & air', key: 'ambientOxygen', label: 'Ambient oxygen', min: 0, max: 1, step: 0.01 },\n {\n group: 'Fuel & air',\n key: 'flamePersistence',\n label: 'Flame persistence',\n min: 0.005,\n max: 1,\n step: 0.005,\n },\n { group: 'Fuel & air', key: 'airMixing', label: 'Air mixing', min: 0, max: 1, step: 0.01 },\n // 0–10 while the default was 0. Now that turbulence is what breaks the\n // flames into tongues, the tuned value has to sit somewhere a slider can\n // move in BOTH directions — see the test.\n { group: 'Motion & turbulence', key: 'turbulence', label: 'Turbulence', min: 0, max: 20, step: 0.1 },\n {\n group: 'Motion & turbulence',\n key: 'turbulenceScale',\n label: 'Turbulence scale',\n min: 0.5,\n max: 24,\n step: 0.1,\n },\n { group: 'Motion & turbulence', key: 'vorticity', label: 'Vorticity', min: 0, max: 8, step: 0.1 },\n { group: 'Motion & turbulence', key: 'wind', label: 'Wind', min: -3, max: 3, step: 0.05 },\n { group: 'Look', key: 'smokeFade', label: 'Smoke lingers (s)', min: 0.2, max: 10, step: 0.1 },\n { group: 'Look', key: 'smokeAfter', label: 'Smoke after the flames (s)', min: 0, max: 12, step: 0.1 },\n]\n\n/** What the solver's passes read, in its own units. */\nexport interface SolverUniforms {\n fuel: number\n premixed: number\n heat: number\n smoke: number\n radial: number\n initialVelocity: [number, number]\n burnRate: number\n heatRelease: number\n expansion: number\n buoyancy: number\n cooling: number\n smokeProduction: number\n smokeFade: number\n ambient: number\n persistence: number\n /** Share of the neighbours' oxygen mixed in per step, in the plane. */\n mixing: number\n /** Per second: air drawn in from in front of and behind the slice. */\n entrain: number\n /** Fuel density at which entrainment is down to 1/e — dense fuel keeps air out. */\n fuelBlock: number\n /** Soot formed per unit of hot fuel a second. */\n sootYield: number\n /** The temperature soot needs to form and glow at. */\n sootHeat: number\n /** Units of air a unit of fuel burns with. */\n stoich: number\n /** Smoke a second, per unit of smouldering strength, where a flame has gone out. */\n smoulderSmoke: number\n /** Heat with it — enough that the smoke rises, not enough to glow. */\n smoulderHeat: number\n turbulence: number\n turbulenceScale: number\n /** Noise units a second the turbulence changes by, on top of rising with the gas. */\n turbulenceEvolve: number\n vorticity: number\n wind: number\n}\n\n/**\n * The panel's numbers, in the solver's units. Every scale here is a tuning\n * decision made by eye against the references, stated once so it is never\n * smeared across shaders.\n */\nexport function solverUniforms(p: FireFluidParams): SolverUniforms {\n const finite = (x: number, fallback: number) => (Number.isFinite(x) ? x : fallback)\n return {\n // Gas per second per unit of source strength.\n fuel: Math.max(0, finite(p.fuel, 0)) * 0.12,\n premixed: Math.min(1, Math.max(0, finite(p.premixedOxygen, 0))),\n // Temperature released with the gas, per second. At 1.8 the gas at the\n // source settled near a third of glowing — cooling took the heat faster\n // than the rim gave it — and the fire was a faint blush, not a flame.\n heat: Math.max(0, finite(p.heat, 0)) * 7,\n smoke: Math.max(0, finite(p.smoke, 0)) * 0.45,\n // World units a second: A4 flames climb a few tens of centimetres a second.\n radial: Math.max(0, finite(p.radialImpulse, 0)) * 0.2,\n // World units a second, one to one — a unit is 210 mm.\n initialVelocity: [finite(p.initialVelocity[0], 0), finite(p.initialVelocity[1], 0)],\n // Per second.\n burnRate: Math.max(0, finite(p.burnRate, 0)) * 2,\n // Temperature a unit of burnt fuel adds.\n heatRelease: 3,\n // Divergence per unit of burning a second — the gas expanding as it burns.\n expansion: Math.max(0, finite(p.gasExpansion, 0)) * 1.5,\n // World units a second squared per unit of temperature.\n buoyancy: Math.max(0, finite(p.buoyancy, 0)) * 0.9,\n // Per second: a flame at A4 scale has cooled out of sight 10–40 mm up.\n cooling: Math.max(0, finite(p.cooling, 0)) * 6,\n smokeProduction: Math.max(0, finite(p.smokeProduction, 0)) * 0.3,\n smokeFade: Math.max(0.05, finite(p.smokeFade, 2.5)),\n ambient: Math.min(1, Math.max(0, finite(p.ambientOxygen, 0))),\n persistence: Math.max(0.005, finite(p.flamePersistence, 0.085)),\n mixing: Math.min(1, Math.max(0, finite(p.airMixing, 0.5))) * 0.5,\n entrain: Math.min(1, Math.max(0, finite(p.airMixing, 0.5))) * 40,\n fuelBlock: 0.05,\n sootYield: 30,\n sootHeat: 2,\n // Paper's gas takes several times its own mass of air to burn; what\n // matters here is only that it is well above one, so fuel near the rim is\n // denser than the air that can reach it and burns from the outside in.\n stoich: 4,\n // Several times the rim's own smoke per unit strength: a smouldering spot\n // has no flame to lift its smoke fast, so it pools and rises slowly, and\n // at 1.5× it read 0.03 at the 99th percentile — about 3% opacity, gone —\n // at 8× 0.07, and at 30× thin wisps you had to look for.\n smoulderSmoke: Math.max(0, finite(p.smoke, 0)) * 0.45 * 80,\n smoulderHeat: 1.2,\n turbulence: Math.max(0, finite(p.turbulence, 0)) * 0.35,\n // Noise frequency per WORLD unit — the pass multiplies by the domain's\n // size, which it did not before (it read UV, and the domain is two units\n // tall, so every eddy was half the size this said). 12 → ~14 mm eddies.\n turbulenceScale: Math.max(0.1, finite(p.turbulenceScale, 4.6)) * 1.25,\n turbulenceEvolve: 3,\n vorticity: Math.max(0, finite(p.vorticity, 0)),\n wind: finite(p.wind, 0) * 0.3,\n }\n}\n","import * as THREE from 'three'\nimport { useFrame } from '@react-three/fiber'\nimport { useRef } from 'react'\nimport type { DamageField } from './field'\nimport type { SurfaceLocator } from './fire'\nimport { flameAnchors, flamePuff, type FlameAnchor } from './flames'\n\nexport interface FxFireLightProps {\n field: DamageField\n locate: SurfaceLocator\n /** How bright the fire is per unit of burning front. Tuned against Hero.png. */\n gain?: number\n}\n\n/**\n * Fire is a light source (§4.8, §7). A fire that changes nothing around it\n * looks pasted on.\n *\n * A warm point light — about 1900 K — at the middle of the burning rim,\n * raised by half a flame, as bright as the front is long and flickering on\n * the very noise the flames puff on, so light and flame agree. It reaches the\n * whole sheet: `decay` is held below the physical 2, because a sheet is a\n * small thing next to a fire and Hero.png warms it to the top edge.\n *\n * And a dimmer twin on the FAR side of the sheet, for translucency: paper is\n * thin, and a fire in front of it glows through to the back, warm and\n * diffused (§7). A second light is the whole trick — the sheet's own\n * material already lights its back face from whatever is behind it.\n */\nexport function FxFireLight({ field, locate, gain = FIRE_LIGHT_GAIN }: FxFireLightProps) {\n const front = useRef<THREE.PointLight>(null)\n const through = useRef<THREE.PointLight>(null)\n const anchors = useRef<FlameAnchor[]>([])\n\n useFrame(({ camera }) => {\n const a = front.current\n const b = through.current\n if (!a || !b) return\n const n = flameAnchors(field, locate, 32, anchors.current)\n if (n === 0) {\n a.intensity = 0\n b.intensity = 0\n return\n }\n let x = 0\n let y = 0\n let z = 0\n let h = 0\n let flicker = 0\n for (let i = 0; i < n; i++) {\n const f = anchors.current[i]!\n x += f.x\n y += f.y\n z += f.z\n h += f.height\n flicker += flamePuff(f.seed, field.time)\n }\n x /= n\n y /= n\n z /= n\n h /= n\n flicker /= n\n // Off the sheet toward whoever is looking, a little; its twin the same\n // distance behind.\n const toCamera = new THREE.Vector3(camera.position.x - x, 0, camera.position.z - z)\n toCamera.normalize().multiplyScalar(0.035)\n const lift = h * 0.5\n a.position.set(x + toCamera.x, y + lift, z + toCamera.z)\n b.position.set(x - toCamera.x, y + lift, z - toCamera.z)\n const level = field.lastStats.front * gain * flicker\n a.intensity = level\n b.intensity = level * 0.35\n })\n\n return (\n <>\n <pointLight ref={front} color={FIRE_COLOR} intensity={0} decay={0.9} distance={0} />\n <pointLight ref={through} color={FIRE_COLOR} intensity={0} decay={0.9} distance={0} />\n </>\n )\n}\n\n/** ~1900 K. */\n// Peach rather than red: a red-heavy light on white paper is exactly what\n// the tone curve turns salmon — the pink the spec forbids.\nconst FIRE_COLOR = new THREE.Color('#ffa24c')\n\n/** Light per unit of front length; the front runs to ~0.045 at a burn's peak. */\nexport const FIRE_LIGHT_GAIN = 42\n","import * as THREE from 'three'\nimport { useFrame } from '@react-three/fiber'\nimport { useEffect, useMemo, useRef } from 'react'\nimport { FxParticles } from './FxParticles'\nimport { flameGeometry, flameMaterial } from './flameShader'\nimport { ParticlePool } from './particles'\n\n/** What the page says about the match this frame. Written in place — never a prop change. */\nexport interface MatchFlameState {\n /** Where the flame's root is, in world space; null when there is no match. */\n position: { x: number; y: number; z: number } | null\n /** Arming is the dwell before a pinch becomes a match; lit is a match. */\n state: 'none' | 'arming' | 'lit'\n /** How hard the viewer is blowing, 0..1. */\n blow: number\n /** Whether the flame is held against the sheet. */\n touching: boolean\n /**\n * A clock to animate on, in seconds, instead of the frame clock — and\n * `litAt`, when on that clock the match was struck.\n *\n * For a scripted burn (`/fx-lab`) that has to photograph the same match\n * twice: given both, the flare is a function of time rather than of which\n * frame saw the strike, and no random sparks or puffs are thrown. A live\n * hand leaves both out.\n */\n time?: number\n litAt?: number\n}\n\nexport interface FxMatchFlameProps {\n /** Read every frame. A ref, so a hand moving the match re-renders nothing. */\n match: { readonly current: MatchFlameState }\n}\n\n/** A match flame at A4 scale: 15–25 mm tall (§10.3). */\nconst HEIGHT = 20 / 210\n/** How long a strike flares for, in seconds, and by how much. */\nconst FLARE = 0.42\nconst FLARE_GAIN = 1\n/** How long a pinch has to be held to arm — `MATCH_DWELL_MS` on /hands. */\nconst ARM = 0.32\n\n/**\n * A match, held: `paperlab-fx-fire-spec.md` §10, the parts that are the\n * flame's to draw.\n *\n * - **Arming.** Friction building: a few tiny sparks and a warm point that\n * grows at the pinch. A pinch released early (a flick) fizzles — nothing\n * lights, because the page never says `lit`.\n * - **Strike.** A one-frame flash of light, a burst of sparks, a puff, and\n * the flame flares to about twice its height for under half a second and\n * settles — a real match head flares as it burns off.\n * - **Held.** A 20 mm teardrop that flickers, leans away from where the hand\n * is going with a lag, and stretches thin and burns blue and dim when moved\n * fast. Against the paper it flattens and spills upward.\n * - **Blown.** It leans away and flickers hard; when the page says it has\n * gone out, it leaves a soft puff behind.\n * - **Light.** It lights the sheet warm before anything scorches — bringing\n * the flame near the paper is the first thing people notice (§10.4).\n *\n * It owns its own clock and its own handful of particles: nothing here is\n * part of a burn anyone replays.\n */\nexport function FxMatchFlame({ match }: FxMatchFlameProps) {\n const geometry = useMemo(() => flameGeometry(1), [])\n const material = useMemo(() => flameMaterial(), [])\n const pool = useMemo(() => new ParticlePool(160, 3), [])\n useEffect(\n () => () => {\n geometry.dispose()\n material.dispose()\n },\n [geometry, material],\n )\n const light = useRef<THREE.PointLight>(null)\n const memory = useRef({\n time: 0,\n last: 'none' as MatchFlameState['state'],\n armedAt: 0,\n flare: 0,\n flash: 0,\n spark: 0,\n was: new THREE.Vector3(),\n velocity: new THREE.Vector3(),\n lean: new THREE.Vector3(),\n at: new THREE.Vector3(),\n hasAt: false,\n })\n\n useFrame((_, delta) => {\n const now = match.current\n const m = memory.current\n // Scripted: the given clock, and nothing that depends on which frame\n // happened to see a transition.\n const scripted = now.time !== undefined\n const dt = scripted ? Math.max(0, now.time! - m.time) : Math.min(0.1, Math.max(0, delta))\n m.time = scripted ? now.time! : m.time + dt\n\n if (now.position) {\n m.at.set(now.position.x, now.position.y, now.position.z)\n if (!m.hasAt) m.was.copy(m.at)\n m.hasAt = true\n }\n\n // Transitions: what a strike and a blow-out LEAVE, spawned once — live\n // only; a scripted strike's flare is read off `litAt` below.\n if (!scripted && now.state === 'lit' && m.last !== 'lit') {\n m.flare = FLARE\n m.flash = 0.06\n for (let i = 0; i < 16; i++) pool.spawn('ember', m.at.x, m.at.y + HEIGHT * 0.2, m.at.z)\n for (let i = 0; i < 3; i++) pool.spawn('smoke', m.at.x, m.at.y + HEIGHT * 0.5, m.at.z)\n }\n if (!scripted && now.state !== 'lit' && m.last === 'lit' && m.hasAt) {\n for (let i = 0; i < 5; i++) pool.spawn('smoke', m.at.x, m.at.y + HEIGHT * 0.6, m.at.z)\n }\n if (now.state === 'arming' && m.last !== 'arming') m.armedAt = m.time\n m.last = now.state\n\n // Velocity, and a lean that follows it 100–200 ms late — the flame is\n // gas, and gas is left behind.\n if (dt > 0 && m.hasAt) {\n const v = m.at.clone().sub(m.was).divideScalar(dt)\n m.velocity.lerp(v, Math.min(1, dt / 0.08))\n m.was.copy(m.at)\n }\n const speed = m.velocity.length()\n const target = m.velocity.clone().multiplyScalar(-0.35)\n // Blowing comes from the viewer: it pushes the flame away, into the scene.\n target.z -= now.blow * 1.2\n m.lean.lerp(target, Math.min(1, dt / 0.15))\n\n m.flare = scripted ? Math.max(0, FLARE - (m.time - (now.litAt ?? -FLARE))) : Math.max(0, m.flare - dt)\n m.flash = scripted ? 0 : Math.max(0, m.flash - dt)\n\n const base = geometry.getAttribute('aBase') as THREE.InstancedBufferAttribute\n const shape = geometry.getAttribute('aShape') as THREE.InstancedBufferAttribute\n let height = 0\n let width = 0\n let gain = 1\n let lightLevel = 0\n if (now.state === 'arming' && m.hasAt) {\n // Friction building: a warm point, growing, and a spark now and then.\n const p = Math.min(1, (m.time - m.armedAt) / ARM)\n height = HEIGHT * 0.18 * p\n width = height * 0.5\n gain = 0.5 + p\n lightLevel = 0.012 * p\n m.spark -= dt\n if (!scripted && m.spark <= 0) {\n m.spark = 0.07\n pool.spawn('ember', m.at.x, m.at.y, m.at.z)\n }\n } else if (now.state === 'lit' && m.hasAt) {\n const flare = 1 + FLARE_GAIN * (m.flare / FLARE) ** 2\n // Stretched thin and taller when swung; flattened against the paper.\n const stretch = 1 + Math.min(1.2, speed * 0.9)\n height = HEIGHT * flare * stretch * (now.touching ? 0.6 : 1)\n width = (HEIGHT * 0.32 * (now.touching ? 1.6 : 1)) / Math.sqrt(stretch)\n // A hard blow makes it gutter.\n gain = 1 - now.blow * 0.45 * (0.5 + 0.5 * Math.sin(m.time * 61))\n // Enough to warm the sheet as it comes near (§10.4), not to scald it:\n // at 0.12 the paper a few centimetres away read ~12× white and bloomed\n // into a peach disc on the capture of the contact frame.\n lightLevel = 0.035 * flare + (m.flash > 0 ? 0.25 : 0)\n }\n material.uniforms.uBlue!.value = Math.min(1, Math.max(0, speed * 0.7 - 0.35))\n material.uniforms.uGain!.value = gain\n material.uniforms.uTime!.value = m.time\n ;(material.uniforms.uLean!.value as THREE.Vector3).copy(m.lean)\n base.array[0] = m.at.x\n base.array[1] = m.at.y\n base.array[2] = m.at.z\n shape.array[0] = height\n shape.array[1] = width\n shape.array[2] = 0.37\n shape.array[3] = 1\n base.needsUpdate = true\n shape.needsUpdate = true\n geometry.instanceCount = height > 0 ? 1 : 0\n\n if (light.current) {\n // Half a flame up and a little toward the viewer — a light ON the paper\n // is a hot spot, not a flame's glow.\n light.current.position.set(m.at.x, m.at.y + height * 0.5, m.at.z + 0.06)\n light.current.intensity = lightLevel\n }\n pool.step(dt)\n })\n\n return (\n <>\n <mesh geometry={geometry} material={material} frustumCulled={false} renderOrder={3} />\n <pointLight ref={light} color=\"#ffb066\" intensity={0} decay={1.6} distance={0} />\n <FxParticles pool={pool} />\n </>\n )\n}\n","import { fxQualityFor, type FxQualityName, type FxQualitySettings } from '../quality'\n\n/**\n * The audio graph: a context, a ceiling, and somewhere for a sound to go.\n *\n * There was no audio anywhere in this library or its three apps before this —\n * not an `AudioContext`, not a clip. Sound was not under-built, it was\n * absent, and that is most of why the wind reads as weightless: a sheet\n * moving in silence is a picture of a sheet moving.\n *\n * Two decisions are baked in here rather than left to each effect.\n *\n * **Synthesis, not samples.** A triggered crumple clip is the same crumple\n * every time, it does not know how fast you crushed it, and two overlapping\n * copies turn to mush. Paper sounds are broadband noise shaped by an\n * envelope, which is the cheapest thing a synthesiser does and the thing\n * sample playback is worst at. It also means no licensing question at all, in\n * a repo that has already had to reason carefully about the licence on the\n * hand-tracking weights.\n *\n * **A hard ceiling, from the tier.** Fire and crumple both want to be grain\n * clouds — dozens of short bursts a second — and a grain cloud with no\n * ceiling is an unbounded number of live nodes on a phone that is already\n * running a camera, a hand tracker and a cloth simulation. So voices come\n * from a fixed pool sized by `fx/quality.ts`, and asking for one past the\n * ceiling steals the least audible voice rather than allocating. Dropping the\n * NEW sound would be the obvious alternative and it is wrong: the newest\n * sound is the one the viewer just caused, and silence in response to an\n * action reads as a broken page.\n *\n * **What is testable, and what is not.** Everything above is policy — pool\n * size, stealing, the unlock state machine, gain arithmetic — and none of it\n * needs a browser. The DSP is a handful of node connections that only a\n * browser can make. So the context is injected rather than constructed, the\n * policy is tested against a stub, and what ships to the browser is the same\n * code path with a real `AudioContext` in it.\n */\n\n/**\n * The part of the Web Audio API this uses.\n *\n * Written out rather than referring to `AudioContext` so that the policy can\n * be tested without a DOM, and so that the surface this depends on is small\n * enough to read. Anything added here is a new thing to stub.\n */\nexport interface AudioLike {\n readonly currentTime: number\n readonly state: 'suspended' | 'running' | 'closed'\n readonly destination: AudioNodeLike\n createGain(): GainLike\n createBufferSource(): BufferSourceLike\n /**\n * Every paper sound is noise with the wrong frequencies taken out of it —\n * a fire is a low rumble under a bright crackle, a tear is the same noise\n * with the bottom removed. Without a filter there is one sound in the whole\n * library: white noise at different volumes.\n */\n createBiquadFilter(): BiquadFilterLike\n /**\n * Where the sound is. A sheet you are holding at arm's length, burning at\n * one corner, is not a sound in the middle of your head.\n */\n createPanner(): PannerLike\n createBuffer(channels: number, length: number, sampleRate: number): AudioBufferLike\n readonly sampleRate: number\n resume(): Promise<void>\n close(): Promise<void>\n}\n\nexport interface AudioNodeLike {\n connect(destination: AudioNodeLike): void\n disconnect(): void\n}\n\nexport interface AudioParamLike {\n value: number\n setValueAtTime(value: number, when: number): void\n linearRampToValueAtTime(value: number, when: number): void\n cancelScheduledValues(when: number): void\n}\n\nexport interface GainLike extends AudioNodeLike {\n readonly gain: AudioParamLike\n}\n\nexport interface BiquadFilterLike extends AudioNodeLike {\n type: 'lowpass' | 'highpass' | 'bandpass' | 'notch' | 'peaking' | 'lowshelf' | 'highshelf' | 'allpass'\n readonly frequency: AudioParamLike\n readonly Q: AudioParamLike\n}\n\nexport interface PannerLike extends AudioNodeLike {\n panningModel: 'equalpower' | 'HRTF'\n distanceModel: 'linear' | 'inverse' | 'exponential'\n refDistance: number\n maxDistance: number\n rolloffFactor: number\n readonly positionX: AudioParamLike\n readonly positionY: AudioParamLike\n readonly positionZ: AudioParamLike\n}\n\nexport interface AudioBufferLike {\n getChannelData(channel: number): Float32Array\n readonly length: number\n}\n\nexport interface BufferSourceLike extends AudioNodeLike {\n buffer: AudioBufferLike | null\n loop: boolean\n /**\n * `offset` and `duration` are what make one shared second of noise into\n * every crackle in a fire: a different slice each time, ending itself\n * without anything having to schedule a stop.\n */\n start(when?: number, offset?: number, duration?: number): void\n stop(when?: number): void\n onended: (() => void) | null\n}\n\nexport interface FxAudioOptions {\n quality?: FxQualityName | FxQualitySettings\n /**\n * The context to use. Required, and never made for you: constructing an\n * `AudioContext` is a side effect with a user-visible cost (a browser tab\n * shows an audio indicator), and a class that made one on construction\n * would take that from a page that never asked. Get a browser one from\n * `createAudioContext()`, inside the gesture that should start the sound.\n */\n context: AudioLike\n /** Master level, 0..1. */\n volume?: number\n}\n\n/** A sound in flight. Handed back so a caller can shape or stop it. */\nexport interface Voice {\n readonly id: number\n /** What this voice is for. Only used to explain who got stolen. */\n readonly kind: string\n /**\n * How much this voice deserves to survive, 0..1.\n *\n * The ONLY input to stealing, and it is not loudness. A near-silent tail of\n * a crackle is worth less than a quiet new ignition, because one is ending\n * and the other is something the viewer just did. Effects set this and the\n * pool obeys it.\n */\n priority: number\n readonly gain: GainLike\n readonly startedAt: number\n /**\n * Hand a source to this voice, so it lives and dies with it.\n *\n * Every source that plays through a voice must be given to it, because the\n * voice is the only thing that knows when to stop it. A voice that did not\n * own its sources could only fade its gain when stolen — and a looping bed\n * behind a silent gain keeps running, and keeps its nodes, for the life of\n * the page.\n *\n * A one-shot that ends on its own ends the voice with it; a voice already\n * released stops the source at once rather than letting it play unheard.\n */\n own(source: BufferSourceLike): void\n /**\n * Hand a NODE to this voice, so it is disconnected with it.\n *\n * The same rule as {@link own}, for the filters and panners a sound is\n * shaped by. A crackle makes its own filter — a few dozen a second in a\n * good burn — and a filter left connected to a freed gain is the same leak\n * the sources used to have, in a part of the graph nothing was watching.\n */\n use(node: AudioNodeLike): void\n /** Stop and return this voice to the pool. */\n stop(): void\n}\n\n/** Fades applied when a voice is taken away, so stealing never clicks. */\nconst STEAL_FADE = 0.012\n\nexport class FxAudio {\n readonly quality: FxQualitySettings\n private readonly ctx: AudioLike\n private readonly master: GainLike\n private readonly live: Voice[] = []\n /**\n * What each voice owns, until the last of it has actually stopped.\n *\n * Outlives the voice's place in `live` on purpose: a stolen voice leaves the\n * pool at once, so the new sound can have its slot, but its sources are\n * still fading for a few milliseconds and its gain is still connected. The\n * nodes are freed when the last source reports `ended`, not before — cut\n * sooner and the fade that stops stealing from clicking is cut with it.\n */\n private readonly slots = new Map<\n number,\n { gain: GainLike; sources: BufferSourceLike[]; nodes: AudioNodeLike[] }\n >()\n private nextId = 1\n private unlocked = false\n private noise: AudioBufferLike | null = null\n\n constructor(options: FxAudioOptions) {\n const { quality = 'auto', context, volume = 0.8 } = options\n this.quality = typeof quality === 'string' ? fxQualityFor(quality) : quality\n // The type already requires it; this is for callers without one.\n if (!context) throw new Error('FxAudio needs a context — see createAudioContext()')\n this.ctx = context\n this.master = this.ctx.createGain()\n this.master.gain.value = volume\n this.master.connect(this.ctx.destination)\n }\n\n get context(): AudioLike {\n return this.ctx\n }\n\n /** Live voices right now. Never more than the tier's ceiling. */\n get voices(): readonly Voice[] {\n return this.live\n }\n\n get isUnlocked(): boolean {\n return this.unlocked\n }\n\n get volume(): number {\n return this.master.gain.value\n }\n\n set volume(value: number) {\n this.master.gain.value = Math.min(1, Math.max(0, value))\n }\n\n /**\n * Let sound happen, from inside a user gesture.\n *\n * Every browser starts an `AudioContext` suspended until a real interaction\n * resumes it, and a page that calls this from anywhere else is a page whose\n * audio silently never starts. Here the cost is zero: `/hands` already has\n * a button that turns the camera on, and nothing can be heard before the\n * camera is on anyway.\n *\n * Safe to call more than once — it is a latch, not a toggle.\n */\n async unlock(): Promise<void> {\n if (this.unlocked) return\n if (this.ctx.state === 'suspended') await this.ctx.resume()\n this.unlocked = this.ctx.state === 'running'\n }\n\n /**\n * One second of white noise, made once and shared.\n *\n * Nearly every paper sound is filtered noise — a crumple is a cloud of\n * short bursts of it, a fire is a bed of it, a tear is a fast train of it —\n * so this is the single most reused object in the graph. Generating it per\n * voice would allocate a second of audio per crackle.\n */\n noiseBuffer(): AudioBufferLike {\n if (this.noise) return this.noise\n const length = Math.floor(this.ctx.sampleRate)\n const buffer = this.ctx.createBuffer(1, length, this.ctx.sampleRate)\n const data = buffer.getChannelData(0)\n // Deterministic rather than Math.random: a reproducible bed is one that\n // can be compared between two runs when something sounds wrong.\n let seed = 0x9e3779b9\n for (let i = 0; i < length; i++) {\n seed = (Math.imul(seed, 1664525) + 1013904223) | 0\n data[i] = (seed >>> 0) / 2147483648 - 1\n }\n this.noise = buffer\n return buffer\n }\n\n /**\n * Take a voice, stealing the least deserving one if the pool is full.\n *\n * Returns null only when the ceiling is zero or the caller's priority is\n * below everything already playing — the one case where refusing is right,\n * because the alternative is cutting off something more important to play\n * something less.\n */\n take(kind: string, priority = 0.5): Voice | null {\n if (this.quality.voices <= 0) return null\n\n if (this.live.length >= this.quality.voices) {\n // The weakest voice, tie-broken by age so the older of two equals goes.\n let weakest = 0\n for (let i = 1; i < this.live.length; i++) {\n const a = this.live[i]!\n const b = this.live[weakest]!\n if (a.priority < b.priority || (a.priority === b.priority && a.startedAt < b.startedAt)) {\n weakest = i\n }\n }\n const victim = this.live[weakest]!\n if (victim.priority > priority) return null\n victim.stop()\n }\n\n const gain = this.ctx.createGain()\n gain.connect(this.master)\n const id = this.nextId++\n this.slots.set(id, { gain, sources: [], nodes: [] })\n const voice: Voice = {\n id,\n kind,\n priority,\n gain,\n startedAt: this.ctx.currentTime,\n own: (source) => this.own(id, source),\n use: (node) => this.useNode(id, node),\n stop: () => this.release(id),\n }\n this.live.push(voice)\n return voice\n }\n\n private own(id: number, source: BufferSourceLike): void {\n const slot = this.slots.get(id)\n const alive = this.live.some((v) => v.id === id)\n if (!slot || !alive) {\n // A source given to a voice that has already gone: it must not play\n // through a gain that is fading out, or through nothing at all.\n stopNow(source)\n source.disconnect()\n return\n }\n slot.sources.push(source)\n source.onended = () => this.ended(id, source)\n }\n\n private useNode(id: number, node: AudioNodeLike): void {\n const slot = this.slots.get(id)\n const alive = this.live.some((v) => v.id === id)\n if (!slot || !alive) {\n // Given to a voice that has already gone: it feeds nothing, so it goes.\n node.disconnect()\n return\n }\n slot.nodes.push(node)\n }\n\n private ended(id: number, source: BufferSourceLike): void {\n source.disconnect()\n const slot = this.slots.get(id)\n if (!slot) return\n const at = slot.sources.indexOf(source)\n if (at >= 0) slot.sources.splice(at, 1)\n if (slot.sources.length > 0) return\n // The last of it. If the voice is still in the pool it finished on its\n // own — a one-shot — and is done; if it was released, its fade is over.\n if (this.live.some((v) => v.id === id)) this.release(id)\n else this.free(id)\n }\n\n /** Disconnect a voice's gain. Only once nothing that feeds it is still running. */\n private free(id: number): void {\n const slot = this.slots.get(id)\n if (!slot) return\n for (const node of slot.nodes) node.disconnect()\n slot.gain.disconnect()\n this.slots.delete(id)\n }\n\n /**\n * Return a voice to the pool, fading it out first.\n *\n * The fade is the whole reason this is not just a splice. Cutting a\n * waveform mid-cycle produces a step, and a step is a click — audible,\n * cheap-sounding, and most likely to happen exactly when the most is going\n * on, since that is when voices get stolen.\n */\n private release(id: number): void {\n const index = this.live.findIndex((v) => v.id === id)\n if (index < 0) return\n const [voice] = this.live.splice(index, 1)\n if (!voice) return\n const slot = this.slots.get(id)\n // Nothing is playing through it, so there is nothing to fade and nothing\n // to wait for.\n if (!slot || slot.sources.length === 0) {\n this.free(id)\n return\n }\n const now = this.ctx.currentTime\n voice.gain.gain.cancelScheduledValues(now)\n voice.gain.gain.setValueAtTime(voice.gain.gain.value, now)\n voice.gain.gain.linearRampToValueAtTime(0, now + STEAL_FADE)\n // Stopped at the END of the fade, which is what lets `ended` free the\n // nodes without cutting the ramp short. This is the half the first\n // version was missing: it faded the gain and never stopped a thing, so a\n // looping bed on a stolen voice ran silently forever.\n for (const source of [...slot.sources]) {\n try {\n source.stop(now + STEAL_FADE)\n } catch {\n // Never started: nothing will ever report `ended`, so do it here.\n this.ended(id, source)\n }\n }\n }\n\n /** Stop everything. The panic button, and what unmounting calls. */\n stopAll(): void {\n for (const voice of [...this.live]) voice.stop()\n }\n\n /**\n * A short tone, to prove the chain end to end.\n *\n * Deliberately part of the shipped surface rather than a test fixture:\n * \"is the audio graph actually connected\" is a question that comes up on\n * every device, and the honest answer is a sound you can hear. It uses the\n * same path everything else does — a voice from the pool, the noise buffer,\n * the master gain — so if this is audible the path works.\n */\n test(duration = 0.15): Voice | null {\n const voice = this.take('test', 1)\n if (!voice) return null\n const source = this.ctx.createBufferSource()\n source.buffer = this.noiseBuffer()\n source.connect(voice.gain)\n const now = this.ctx.currentTime\n voice.gain.gain.setValueAtTime(0, now)\n voice.gain.gain.linearRampToValueAtTime(0.4, now + 0.01)\n voice.gain.gain.linearRampToValueAtTime(0, now + duration)\n source.start(now)\n source.stop(now + duration)\n // Owned, so its end frees the voice and every node it used.\n voice.own(source)\n return voice\n }\n\n /** Release the context. Nothing survives this. */\n async dispose(): Promise<void> {\n this.stopAll()\n // Closing the context ends every source at once, and `ended` may never\n // arrive after that — so let go of everything here rather than wait.\n for (const id of [...this.slots.keys()]) this.free(id)\n this.master.disconnect()\n await this.ctx.close()\n }\n}\n\n/** Stop a source that may or may not have been started. */\nfunction stopNow(source: BufferSourceLike): void {\n try {\n source.stop()\n } catch {\n // Never started — which is the same as stopped.\n }\n}\n\n/**\n * Make a real browser context.\n *\n * Separate from the constructor so that importing this module never starts an\n * audio context — a browser shows an indicator for one, and a library that\n * lights it up on import is a library that has taken something that was not\n * offered.\n */\nexport function createAudioContext(): AudioLike {\n const Ctor = (globalThis as { AudioContext?: new () => AudioLike }).AudioContext\n if (!Ctor) throw new Error('no Web Audio support in this environment')\n return new Ctor()\n}\n","import type { FieldStats } from '../field'\nimport type { BiquadFilterLike, FxAudio, PannerLike, Voice } from './graph'\n\n/**\n * What a burning sheet sounds like, driven by the numbers that make it burn.\n *\n * Two layers, and the split is the whole design.\n *\n * A **bed**: one looping band of filtered noise whose level follows the\n * LENGTH of the burn front. Not the burnt area — a fire gets louder as its\n * edge gets longer, and a nearly-consumed sheet is quiet again, which a level\n * driven by char would get exactly backwards. It brightens as it grows too:\n * a big fire is not a small fire turned up.\n *\n * A **crackle**: one short burst of bright noise for every few texels that\n * char, so the rate is the rate the paper is actually catching. Each is a\n * different slice of the shared second of noise through its own bandpass, so\n * no two are the same sound; each ends itself.\n *\n * Both read `FieldStats` and nothing else. Sound and picture therefore cannot\n * drift apart: the number that raises the flame's glow is the number that\n * raises its voice. A seed makes a replayed burn crackle the same way, which\n * is the same property the untiered simulation exists to give.\n */\n\nexport interface FireSoundOptions {\n /** Loudest the bed gets, 0..1. */\n volume?: number\n /**\n * The front length that counts as a fire at full blast, as the fraction of\n * the sheet's texels on it. Four percent of a 64² grid is about 160 cells,\n * which is a sheet burning across its whole width.\n */\n fullFront?: number\n /** Crackles per texel that chars. */\n crackle?: number\n /** So the same burn sounds the same twice. */\n seed?: number\n}\n\nconst DEFAULTS: Required<FireSoundOptions> = { volume: 0.55, fullFront: 0.04, crackle: 0.3, seed: 5 }\n\n/** How fast the bed follows the front, in seconds. Fire breathes; it does not jump. */\nconst BED_SMOOTHING = 0.12\n\n/**\n * At most this many crackles in one frame.\n *\n * A frame that chars a hundred texels at once — a whole dry sheet catching —\n * would otherwise ask for a hundred voices, steal every one of them from the\n * others and turn the pool over twice. Past a handful at once it is a noise\n * burst anyway, which is what the bed is for.\n */\nconst CRACKLES_PER_FRAME = 4\n\n/** A place in the world, as little of one as this needs. */\nexport interface SoundAt {\n readonly x: number\n readonly y: number\n readonly z: number\n}\n\nexport class FireSound {\n private readonly audio: FxAudio\n private readonly o: Required<FireSoundOptions>\n private bed: { voice: Voice; filter: BiquadFilterLike; panner: PannerLike | null } | null = null\n private level = 0\n private debt = 0\n private state: number\n\n constructor(audio: FxAudio, options: FireSoundOptions = {}) {\n this.audio = audio\n this.o = { ...DEFAULTS, ...options }\n this.state = this.o.seed >>> 0 || 1\n }\n\n /** True while the bed is playing. */\n get burning(): boolean {\n return this.bed !== null\n }\n\n /**\n * Call once a frame with what the field just did, and where the sheet is.\n *\n * `at` is optional: without it the fire is not placed in the room, which is\n * right for a sheet filling the frame and wrong for one held at arm's\n * length.\n */\n update(dt: number, stats: FieldStats, at?: SoundAt | null): void {\n const target = stats.front > 0 ? Math.min(1, Math.sqrt(stats.front / this.o.fullFront)) : 0\n // Toward the target rather than at it, so the level cannot flicker with\n // the front's own frame-to-frame wobble.\n const follow = dt > 0 ? Math.min(1, dt / BED_SMOOTHING) : 1\n this.level += (target - this.level) * follow\n\n if (target > 0 || this.level > 0.02) this.startBed(at)\n else this.stopBed()\n if (this.bed) this.driveBed(at)\n\n this.debt += stats.charred * this.o.crackle\n let budget = CRACKLES_PER_FRAME\n while (this.debt >= 1 && budget > 0) {\n this.debt -= 1\n budget--\n this.crackle()\n }\n // Whatever the budget could not play is dropped outright, down to the\n // fraction still accumulating toward the next one. A crackle carried into\n // a frame where nothing charred is a sound with no cause — and a sheet\n // that flashes over would otherwise go on crackling after it had stopped\n // burning.\n if (budget === 0) this.debt %= 1\n }\n\n /**\n * A match struck: the scratch of the head across the box, then the hiss of\n * it flaring as the head burns off (spec §10.2). Two bursts of the shared\n * noise, one bright and short, one breathier and longer.\n */\n strike(): void {\n this.burst('fire-strike', 0.9, { type: 'bandpass', frequency: 3200, q: 1.1 }, 0.004, 0.07, 0.8)\n this.burst('fire-flare', 0.8, { type: 'highpass', frequency: 1800, q: 0.7 }, 0.03, 0.45, 0.45, 0.05)\n }\n\n /** Blown out: a soft, low breath of noise (spec §10.6). */\n puff(): void {\n this.burst('fire-puff', 0.6, { type: 'lowpass', frequency: 520, q: 0.6 }, 0.02, 0.28, 0.55)\n }\n\n /** One ember popping in the air — a tiny click on the frame it flashes (spec §8.1). */\n pop(): void {\n const f = 3000 + this.next() * 3000\n this.burst(\n 'fire-pop',\n 0.2,\n { type: 'bandpass', frequency: f, q: 3 },\n 0.001,\n 0.012 + this.next() * 0.01,\n 0.35,\n )\n }\n\n /** Silence, now — the flame blown out, or the page going away. */\n stop(): void {\n this.stopBed()\n this.level = 0\n this.debt = 0\n }\n\n private startBed(at?: SoundAt | null): void {\n if (this.bed) return\n const ctx = this.audio.context\n // Above the crackles on purpose: the bed IS the fire, and a pool full of\n // crackles that stole it would leave a burning sheet sounding like static.\n const voice = this.audio.take('fire-bed', 0.7)\n if (!voice) return\n const source = ctx.createBufferSource()\n source.buffer = this.audio.noiseBuffer()\n source.loop = true\n const filter = ctx.createBiquadFilter()\n filter.type = 'bandpass'\n filter.frequency.value = 380\n filter.Q.value = 0.7\n const panner = at ? ctx.createPanner() : null\n source.connect(filter)\n if (panner) {\n panner.panningModel = 'equalpower'\n panner.distanceModel = 'inverse'\n panner.refDistance = 1\n filter.connect(panner)\n panner.connect(voice.gain)\n } else {\n filter.connect(voice.gain)\n }\n // From silence: a bed that arrives at full level is a click.\n voice.gain.gain.setValueAtTime(0, ctx.currentTime)\n source.start(ctx.currentTime)\n voice.own(source)\n voice.use(filter)\n if (panner) voice.use(panner)\n this.bed = { voice, filter, panner }\n }\n\n private driveBed(at?: SoundAt | null): void {\n const bed = this.bed\n if (!bed) return\n const ctx = this.audio.context\n const now = ctx.currentTime\n const gain = bed.voice.gain.gain\n gain.cancelScheduledValues(now)\n gain.setValueAtTime(gain.value, now)\n gain.linearRampToValueAtTime(this.level * this.o.volume, now + BED_SMOOTHING / 2)\n // A bigger fire is brighter as well as louder.\n bed.filter.frequency.setValueAtTime(380 + 900 * this.level, now)\n if (at && bed.panner) {\n bed.panner.positionX.setValueAtTime(at.x, now)\n bed.panner.positionY.setValueAtTime(at.y, now)\n bed.panner.positionZ.setValueAtTime(at.z, now)\n }\n }\n\n private stopBed(): void {\n if (!this.bed) return\n this.bed.voice.stop()\n this.bed = null\n }\n\n private crackle(): void {\n const voice = this.audio.take('fire-crackle', 0.25)\n if (!voice) return\n const ctx = this.audio.context\n const now = ctx.currentTime\n const buffer = this.audio.noiseBuffer()\n const seconds = buffer.length / ctx.sampleRate\n const duration = 0.018 + this.next() * 0.05\n const source = ctx.createBufferSource()\n source.buffer = buffer\n const filter = ctx.createBiquadFilter()\n filter.type = 'bandpass'\n // High and narrow: a crackle is the sound of fibres letting go, not a\n // small version of the rumble.\n filter.frequency.value = 1400 + this.next() * 2800\n filter.Q.value = 1.6 + this.next() * 3\n source.connect(filter)\n filter.connect(voice.gain)\n const peak = (0.25 + this.next() * 0.5) * this.o.volume * Math.max(0.25, this.level)\n const gain = voice.gain.gain\n gain.setValueAtTime(0, now)\n gain.linearRampToValueAtTime(peak, now + 0.004)\n gain.linearRampToValueAtTime(0, now + duration)\n // A different slice of the shared noise every time, ending itself.\n source.start(now, this.next() * Math.max(0, seconds - duration), duration)\n voice.own(source)\n voice.use(filter)\n }\n\n /**\n * One shaped burst of the shared noise through one filter — what strike,\n * puff and pop are made of. `delay` starts it a moment late, which is how\n * the flare follows the scratch.\n */\n private burst(\n name: string,\n priority: number,\n filterSpec: { type: 'bandpass' | 'highpass' | 'lowpass'; frequency: number; q: number },\n attack: number,\n duration: number,\n level: number,\n delay = 0,\n ): void {\n const voice = this.audio.take(name, priority)\n if (!voice) return\n const ctx = this.audio.context\n const now = ctx.currentTime + delay\n const buffer = this.audio.noiseBuffer()\n const seconds = buffer.length / ctx.sampleRate\n const source = ctx.createBufferSource()\n source.buffer = buffer\n const filter = ctx.createBiquadFilter()\n filter.type = filterSpec.type\n filter.frequency.value = filterSpec.frequency\n filter.Q.value = filterSpec.q\n source.connect(filter)\n filter.connect(voice.gain)\n const gain = voice.gain.gain\n gain.setValueAtTime(0, now)\n gain.linearRampToValueAtTime(level * this.o.volume, now + attack)\n gain.linearRampToValueAtTime(0, now + attack + duration)\n source.start(now, this.next() * Math.max(0, seconds - attack - duration), attack + duration)\n voice.own(source)\n voice.use(filter)\n }\n\n /** xorshift32 — its own stream, so nothing else can shift the crackle. */\n private next(): number {\n let s = this.state\n s ^= s << 13\n s ^= s >>> 17\n s ^= s << 5\n this.state = s >>> 0\n return this.state / 4294967296\n }\n}\n"],"mappings":"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","names":["target","target","jsx","useFrame","useEffect","useMemo","THREE","jsx","useMemo","useEffect","useFrame","THREE","useFrame","useEffect","useMemo","useRef","jsx","useRef","useMemo","useEffect","useFrame","THREE","useFrame","useThree","useEffect","useMemo","useRef","THREE","NOISE","Fragment","jsx","useThree","useMemo","useEffect","useRef","useFrame","THREE","useFrame","useRef","Fragment","jsx","jsxs","useRef","useFrame","THREE","useFrame","useEffect","useMemo","useRef","Fragment","jsx","jsxs","useMemo","useEffect","useRef","useFrame","target","target"]}
|