vireuikit 0.2.0 → 0.4.0

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/medium/oklab.ts","../../src/medium/species.ts","../../src/medium/params.ts","../../src/medium/noise.ts","../../src/medium/seed-shader.ts","../../src/medium/resample-shader.ts","../../src/medium/advect-shader.ts","../../src/medium/composite-shader.ts","../../src/medium/emit-shader.ts","../../src/web/medium.ts","../../src/web/medium-backdrop.ts"],"names":["createProgram","toGLSL","locationCache","createTexture","createFramebuffer","setUniform","drawFullscreenTriangle","bindTextureAt","phase","FULLSCREEN_TRIANGLE_VERTEX_SOURCE"],"mappings":"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","file":"index.cjs","sourcesContent":["// Oklab / linear-sRGB — the shared math for mixing the medium's channels (mix in OKLCH/Oklab, not\n// raw RGB, or saturated diffusion hues collapse into gray). Coefficients: Björn Ottosson,\n// https://bottosson.github.io/posts/oklab/.\n//\n// The numbers live as ONE set: the shader text is assembled from the same matrices the JS side\n// computes with. While the forward direction lived in TS and the inverse was separate literals in\n// the shader, they could drift apart silently, and the medium's palette would shift by a hue\n// between the GPU and any JS-side precomputation.\ntype Mat3 = readonly [\n readonly [number, number, number],\n readonly [number, number, number],\n readonly [number, number, number],\n];\n\nexport const LINEAR_TO_LMS: Mat3 = [\n [0.4122214708, 0.5363325363, 0.0514459929],\n [0.2119034982, 0.6806995451, 0.1073969566],\n [0.0883024619, 0.2817188376, 0.6299787005],\n];\n\nexport const LMS_TO_OKLAB: Mat3 = [\n [0.2104542553, 0.793617785, -0.0040720468],\n [1.9779984951, -2.428592205, 0.4505937099],\n [0.0259040371, 0.7827717662, -0.808675766],\n];\n\nexport const OKLAB_TO_LMS: Mat3 = [\n [1, 0.3963377774, 0.2158037573],\n [1, -0.1055613458, -0.0638541728],\n [1, -0.0894841775, -1.291485548],\n];\n\nexport const LMS_TO_LINEAR: Mat3 = [\n [4.0767416621, -3.3077115913, 0.2309699292],\n [-1.2684380046, 2.6097574011, -0.3413193965],\n [-0.0041960863, -0.7034186147, 1.707614701],\n];\n\n/** GLSL integers aren't floats, so the decimal point is mandatory: `1` won't compile as a float. */\nconst glsl = (v: number) => (Number.isInteger(v) ? v.toFixed(1) : String(v));\nconst row = (m: Mat3, i: number, vars: readonly [string, string, string]) =>\n m[i].map((k, j) => `${glsl(k)} * ${vars[j]}`).join(' + ');\n\nexport const VG_OKLAB_TO_SRGB = `\nfloat3 vgLinearToSrgb(float3 c) {\n float3 lo = c * 12.92;\n float3 hi = 1.055 * pow(max(c, float3(0.0)), float3(1.0 / 2.4)) - 0.055;\n return mix(lo, hi, step(float3(0.0031308), c));\n}\n\nfloat3 vgOklabToLinear(float3 lab) {\n float l_ = ${row(OKLAB_TO_LMS, 0, ['lab.x', 'lab.y', 'lab.z'])};\n float m_ = ${row(OKLAB_TO_LMS, 1, ['lab.x', 'lab.y', 'lab.z'])};\n float s_ = ${row(OKLAB_TO_LMS, 2, ['lab.x', 'lab.y', 'lab.z'])};\n float l = l_ * l_ * l_;\n float m = m_ * m_ * m_;\n float s = s_ * s_ * s_;\n return float3(\n ${row(LMS_TO_LINEAR, 0, ['l', 'm', 's'])},\n ${row(LMS_TO_LINEAR, 1, ['l', 'm', 's'])},\n ${row(LMS_TO_LINEAR, 2, ['l', 'm', 's'])}\n );\n}\n`;\n\nconst apply = (m: Mat3, v: readonly [number, number, number]): [number, number, number] => [\n m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],\n m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],\n m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],\n];\n\nconst srgbChannelToLinear = (c: number) =>\n c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\nconst linearChannelToSrgb = (c: number) =>\n c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(Math.max(c, 0), 1 / 2.4) - 0.055;\n\nexport function srgbToOklab(rgb: readonly [number, number, number]): readonly [number, number, number] {\n const linear = rgb.map(srgbChannelToLinear) as [number, number, number];\n const lms = apply(LINEAR_TO_LMS, linear);\n return apply(LMS_TO_OKLAB, lms.map((v) => Math.cbrt(Math.max(v, 0))) as [number, number, number]);\n}\n\n/** The inverse direction in JS — the same numbers that go into the shader: only that way does a\n * round-trip test prove the GPU and the CPU compute the same thing. */\nexport function oklabToSrgb(lab: readonly [number, number, number]): readonly [number, number, number] {\n const lms = apply(OKLAB_TO_LMS, lab as [number, number, number]).map((v) => v * v * v) as [\n number,\n number,\n number,\n ];\n return apply(LMS_TO_LINEAR, lms).map(linearChannelToSrgb) as [number, number, number];\n}\n","// Vapor species tones are DERIVED from a character, not picked as three separate colors. The\n// three most common colors of a piece of cover art are not a palette: they land all around the\n// hue wheel, and the species mix with each other too — a mix of two opposing hues passes through\n// gray. A narrow arc around a single hue is the guard against that: mixes inside it simply have\n// nowhere to pass through gray.\n\nimport { oklabToSrgb, srgbToOklab } from './oklab';\nimport type { VireUIKitMediumChannel } from './params';\n\n/** The medium's character: one hue, one chroma, one lightness shared by the whole triad. */\nexport type VireUIKitMediumCharacter = {\n /** Oklch hue, degrees. */\n hue: number;\n /** Oklch chroma. For the initial state, this is the platform tone's own chroma — i.e. the floor. */\n chroma: number;\n /** Oklab lightness, shared across every species: unevenly bright tones read as separate patches\n * rather than one medium. */\n lightness: number;\n};\n\n/**\n * The medium's working lightness. Glass can't be parked on the polarity threshold (0.50…0.62) —\n * the material would flip its polarity every frame there — so the medium stays below that band.\n * The physical picture is the same either way: the chamber is a dark volume that tracks glow\n * inside it.\n */\nexport const MEDIUM_SPECIES_LIGHTNESS = 0.44;\n\n/** Arc between neighboring species, degrees (a 25–40° spread is the target). */\nexport const MEDIUM_SPECIES_HUE_SPREAD = 32;\n\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\n\n/** Hue, chroma and lightness of an sRGB color — the input for building a family around it. */\nexport function characterOf(color: VireUIKitMediumChannel): VireUIKitMediumCharacter {\n const [lightness, a, b] = srgbToOklab(color);\n return {\n hue: (Math.atan2(b, a) * 180) / Math.PI,\n chroma: Math.hypot(a, b),\n lightness,\n };\n}\n\n/**\n * A family of three species: `H − Δ`, `H`, `H + Δ`, sharing lightness and chroma.\n *\n * Cover art with no color needs no special case: on a gray cover, chroma arrives as the floor,\n * and the arc collapses on its own — all three species get the same platform tone. That is the\n * initial state, not a branch written for it.\n */\nexport function speciesFamily(\n character: VireUIKitMediumCharacter,\n spreadDeg = MEDIUM_SPECIES_HUE_SPREAD,\n): readonly [VireUIKitMediumChannel, VireUIKitMediumChannel, VireUIKitMediumChannel] {\n const { hue, chroma, lightness } = character;\n const tone = (deg: number): VireUIKitMediumChannel => {\n const rad = (deg * Math.PI) / 180;\n return oklabToSrgb([lightness, chroma * Math.cos(rad), chroma * Math.sin(rad)]).map(\n clamp01,\n ) as unknown as VireUIKitMediumChannel;\n };\n return [tone(hue - spreadDeg), tone(hue), tone(hue + spreadDeg)];\n}\n\n/**\n * The initial state: with no cover art, there is nowhere to take a color from. The family is\n * derived from the platform's own tone, so there is no hardcoded triad in the code — neither a\n * placeholder nor an example — only the platform tone and the derivation rule.\n */\nexport function neutralSpecies(\n baseColor: VireUIKitMediumChannel,\n): readonly [VireUIKitMediumChannel, VireUIKitMediumChannel, VireUIKitMediumChannel] {\n const { hue, chroma } = characterOf(baseColor);\n return speciesFamily({ hue, chroma, lightness: MEDIUM_SPECIES_LIGHTNESS });\n}\n","import { neutralSpecies } from './species';\n\n// Medium parameters — the single source of truth for the web runtime (`web/medium.ts`) and,\n// later, an Android one: the numbers are one set, the platforms only differ in how they feed them\n// into uniforms.\nexport type VireUIKitMediumChannel = readonly [number, number, number];\n\nexport type VireUIKitMediumParams = {\n /** 1/s — how fast the TRACK density decays per second. Water (vapor+condensate) does not decay:\n * it is a conserved volume, only moving between phases and being transported. */\n decay: number;\n /** Spatial frequency of the curl-noise potential, 1/px of the simulation grid. */\n curlFreq: number;\n /** Drift speed of the noise domain over time, 1/s. */\n curlSpeed: number;\n /** Velocity field scale, grid-px/s — how far the smoke can be carried in a second. */\n advectSpeed: number;\n /** Turbulence gate, 0…1 (playback drives it: playing is alive, paused cools down). The\n * per-frame value is computed by `createMediumDynamics` (`dynamics.ts`) — it arrives here\n * already resolved. */\n turbulence: number;\n /** 1/s — the fraction of vapor EXCESS above `condensationFloor` that turns into condensate every\n * second from background seeding: weak, everywhere, all the time — the chamber is never\n * perfectly clean. */\n condensationRate: number;\n /** Vapor density (in seed-density units, a spot's peak ≈1) below which condensation does not\n * happen at all — only the excess above it condenses. Without a floor, the condensate would\n * exactly retrace the vapor spot's shape (the same outline, or softer from growth blur), and\n * the two phases would have nowhere to visually diverge: vapor is already soft, so the floor\n * concentrates condensate in dense turbulence cores, leaving the sparse vapor around it\n * uncondensed. Keep it NOTICEABLY below the settled mean vapor level (turbulence stirs spots\n * into a nearly flat background of ~0.3 over a few minutes — a known limit of the numerical\n * advection): a floor above that mean eventually stops finding any excess at all, and\n * condensation dies out for good. */\n condensationFloor: number;\n /** 1/s — the fraction of condensate evaporating back into vapor. Larger than the condensation\n * rate: at equilibrium most of the water stays vapor, which is the \"supersaturated\" one. */\n evaporationRate: number;\n /** grid-px/s — condensate drift downward, on top of the shared velocity field: gravity touches\n * droplets and leaves vapor alone. */\n condensateSettleSpeed: number;\n /** 1/s — the rate at which a droplet blends with its grid neighbors. There is no separate age\n * counter: the blend repeats every frame, and whatever has survived more frames ends up more\n * blended with its neighbors on its own — that IS \"growth\". Keep it NOTICEABLY below\n * `evaporationRate`: over a droplet's average lifetime (1/evaporationRate) only a small\n * fraction should get to blend — otherwise the blur outruns evaporation and the whole grid\n * levels into flat fog before a droplet gets a chance to return to vapor (visible by eye: the\n * contrast between \"a patch of droplets\" and the vapor background disappears within seconds). */\n condensateSpreadRate: number;\n /** 0…1 — how much of the local vapor hue shows through in the condensate. Droplets carry no\n * color of their own (droplets scatter light rather than tint it) — this is only a faint hint\n * of the surroundings. */\n condensateTint: number;\n /** Condensate's visibility multiplier in the Oklab mix relative to vapor at equal density —\n * droplets hold a small share of the water's mass, but they are mostly what's visible (as in a\n * real cloud: vapor is transparent, droplets create the visibility). */\n condensateGain: number;\n /** Near-white sRGB tone of droplets with no hue seeding (`condensateTint=0`) — droplets scatter\n * light rather than color it. */\n condensateColor: VireUIKitMediumChannel;\n /** Each vapor species' hue, sRGB 0…1. Derived as a family around one tone (`speciesFamily`),\n * not picked as three separate colors — see `species.ts`. */\n channelColors: readonly [VireUIKitMediumChannel, VireUIKitMediumChannel, VireUIKitMediumChannel];\n /** The platform's neutral tone — what the medium is, alone, with no source playing. */\n baseColor: VireUIKitMediumChannel;\n /** Background weight in the Oklab mix: keeps the platform tone visible even where the smoke is dense. */\n baseWeight: number;\n /** Field-sample scale for the far depth plane, k ≥ 1 (see `MEDIUM_COMPOSITE_SHADER`): the far\n * plane reads the SAME vapor/condensate field as the near plane, at `src * k + offset` instead\n * of `src`. A larger k folds more of the field into the same screen area, so the identical\n * evolving field reads as both finer-grained structure (features occupy fewer screen-px) and,\n * because the field's own motion maps to screen motion as v/k, slower apparent motion — parallax\n * and scale-of-structure from one number, with no second simulation. Measured against\n * `check-medium.mjs`'s parallax gate (a cross-correlation best-shift, not a centroid — see the\n * gate's own comment on why): 1.6 puts the far/near screen-speed ratio at 0.630, matching the\n * naive prediction 1/1.6 = 0.625 almost exactly, comfortably inside a band that reads as depth\n * without the far plane looking frozen. */\n depthFarScale: number;\n /** Offset into the shared field for the far plane, as a FRACTION of the grid's own width/height\n * (per axis) rather than a fixed grid-px value — the field wraps (`gl.REPEAT`), so a fixed\n * grid-px offset's EFFECTIVE (wrapped) distance from zero depends on whatever grid size it\n * happens to run at, and can land close to zero by accident at a size nobody tested against.\n * (This shipped once as `[41, 67]` grid-px: harmless at the 96px grid the depth gates tested\n * against, but 67 wraps to just 5px at the 72px grid height this package's own README uses as\n * its example — the far plane nearly re-read the near one, exactly the echo depth exists to\n * avoid.) A fraction keeps the wrapped distance proportional to the grid at any size — see\n * `depthFarOffsetPx` below and `check-medium.mjs`'s decorrelation gate, which measures this\n * directly at the README's own 128x72. */\n depthFarOffsetFrac: readonly [number, number];\n /** Grid-px tap radius for the far plane's 4-tap box blur. A spatial average cannot raise local\n * variance, so the same blur that softens the far plane's edges also, by construction, lowers\n * its measured contrast — one mechanism for both aerial-perspective cues (no separate contrast\n * knob exists). 0 disables it (the near plane stays sharp). Measured: reading the far plane's\n * own scale/offset with no blur already drops contrast ~6% (a fixed offset can land on a\n * locally denser or sparser patch of a field that isn't spatially uniform); this radius takes\n * the total to 24% — see the aerial-perspective gate, whose two thresholds isolate blur's own\n * share from that baseline. */\n depthFarBlurRadius: number;\n /** The far plane's share of the mix, relative to the near plane's implicit 1 — aerial\n * perspective's other half: even at equal density the far plane must not compete with the near\n * one for attention. */\n depthFarWeight: number;\n /** grid-px/s — a small downward drift added to VAPOR's own velocity, on top of the shared\n * curl-noise field (condensate already settles faster via `condensateSettleSpeed`). An order of\n * magnitude below it so the gas reads as \"barely noticeable\" drift rather than visibly falling.\n * ADDS NO DENSITY: on this periodic (`gl.REPEAT`) grid a uniform drift only ever translates the\n * field and wraps it back in at the top — it cannot accumulate mass anywhere, the way a real\n * floor would. It is a motion cue (which way is down), not a source of \"denser at the bottom\" —\n * that reading comes entirely from `gravityBottomBoost` below. See\n * `check-medium.mjs`'s vapor-drift gate, which checks DIRECTION over a short window rather than\n * a density ratio, for exactly this reason. */\n gravityVaporDrift: number;\n /** Density boost at the very bottom of the frame, added on top of 1 — a COMPOSITING-only effect:\n * it scales how the already-conserved vapor/condensate density is DISPLAYED, never the\n * simulated buffers themselves, so it cannot threaten the water-conservation invariant by\n * construction, and — unlike `gravityVaporDrift` above — this is the mechanism that actually\n * produces \"denser at the bottom\": it is a steady-state property of the compositing math, true\n * from the very first frame, independent of any warmup or mixing time (see\n * `check-medium.mjs`'s boost gate, which measures it with zero simulated steps for exactly that\n * reason). Large in absolute terms because `baseWeight` (0.9) dominates the Oklab mix wherever\n * gas density is thin — a small boost gets diluted into an invisible change in the final color;\n * measured this large to register as a real, visible band against that dilution. */\n gravityBottomBoost: number;\n /** Fraction of frame height (0 top, 1 bottom) where the bottom boost starts ramping in via\n * `smoothstep`. The brief asks for a denser LAYER at the floor of the chamber, not a boost that\n * reaches halfway up the frame — kept in the bottom quarter. */\n gravityBottomBoostStart: number;\n};\n\nconst BASE_COLOR: VireUIKitMediumChannel = [0.07, 0.08, 0.1];\n\nexport const MEDIUM_DEFAULTS: VireUIKitMediumParams = {\n decay: 0.06,\n curlFreq: 0.045,\n curlSpeed: 0.12,\n advectSpeed: 26,\n turbulence: 1,\n condensationRate: 0.18,\n condensationFloor: 0.12,\n evaporationRate: 0.2,\n condensateSettleSpeed: 8,\n condensateSpreadRate: 0.08,\n condensateTint: 0.2,\n condensateGain: 5,\n condensateColor: [0.94, 0.95, 0.97],\n channelColors: neutralSpecies(BASE_COLOR),\n baseColor: BASE_COLOR,\n baseWeight: 0.9,\n depthFarScale: 1.6,\n depthFarOffsetFrac: [0.47, 0.53],\n depthFarBlurRadius: 5,\n depthFarWeight: 0.5,\n gravityVaporDrift: 0.6,\n gravityBottomBoost: 2,\n gravityBottomBoostStart: 0.75,\n};\n\n/**\n * Resolves the far plane's fractional offset (`depthFarOffsetFrac`) into grid-px for a GIVEN grid\n * size — pure and exported so the wrap-safety it exists for is directly testable (see\n * `medium-depth-offset.test.ts`) without spinning up a WebGL2 context. Per-axis: `gridWidth` and\n * `gridHeight` are independent, and a non-square grid (the README's own 128x72 example) shouldn't\n * distort one axis relative to the other.\n */\nexport function depthFarOffsetPx(\n offsetFrac: readonly [number, number],\n gridWidth: number,\n gridHeight: number,\n): readonly [number, number] {\n return [offsetFrac[0] * gridWidth, offsetFrac[1] * gridHeight];\n}\n\n/**\n * How far a value lands from the nearest wrap boundary (0 or `period`) on a periodic axis of the\n * given `period` — the quantity that actually matters for decorrelation, not the raw offset value\n * itself (a raw offset near the period is effectively a tiny one once the field wraps). Exported\n * for the same reason as `depthFarOffsetPx`: a plain number, testable without a GPU.\n */\nexport function wrappedDistanceFromZero(value: number, period: number): number {\n const wrapped = ((value % period) + period) % period;\n return Math.min(wrapped, period - wrapped);\n}\n\n/** The material probe can't see texture finer than this: there is no gain in a denser simulation\n * grid than the probe's own floor. */\nexport const MEDIUM_PROBE_FLOOR_PX = 22;\n\n/** Device-px per simulation grid cell by default — noticeably coarser than the probe floor;\n * sharpening the detail visible right next to the glass is a later increment (emission and\n * palette). */\nexport const MEDIUM_DEFAULT_CELL_PX = 26;\n\n/**\n * The geometry of one track stamp (`emit-shader.ts`), in FRACTIONS of `min(gridW, gridH)` rather\n * than pixels — so a preset doesn't depend on the simulation grid's resolution. Two presets:\n * alpha is a thick, short, straight track; beta is a thin, long, ragged one. The third, natural,\n * is dim and short, for the occasional background track at rest (not tied to cover art).\n */\nexport type VireUIKitMediumTrackPreset = {\n lengthFrac: number;\n headWidthFrac: number;\n tailWidthFrac: number;\n /** Amplitude of the path's sideways noise, as a fraction of `min(gridW, gridH)`; 0 = a straight thread. */\n raggedFrac: number;\n raggedFreq: number;\n /** How far the tail has settled downward, as a fraction of `min(gridW, gridH)`. */\n settleFrac: number;\n /** How much dimmer the tail is than the head, 0…1. */\n tailDim: number;\n intensity: number;\n};\n\nexport const MEDIUM_TRACK_PRESETS: Readonly<\n Record<'alpha' | 'beta' | 'natural', VireUIKitMediumTrackPreset>\n> = {\n alpha: {\n lengthFrac: 0.22,\n headWidthFrac: 0.05,\n tailWidthFrac: 0.12,\n raggedFrac: 0,\n raggedFreq: 0,\n settleFrac: 0.02,\n tailDim: 0.55,\n intensity: 1.15,\n },\n beta: {\n lengthFrac: 0.42,\n headWidthFrac: 0.018,\n tailWidthFrac: 0.05,\n raggedFrac: 0.045,\n raggedFreq: 5,\n settleFrac: 0.05,\n tailDim: 0.45,\n intensity: 0.85,\n },\n natural: {\n lengthFrac: 0.13,\n headWidthFrac: 0.02,\n tailWidthFrac: 0.04,\n raggedFrac: 0.02,\n raggedFreq: 4,\n settleFrac: 0.02,\n tailDim: 0.5,\n intensity: 0.4,\n },\n};\n\n/** Turbulence at rest/paused/with no active track — the \"slow drift\" baseline shared by all three\n * neutral states (they differ from each other in other ways, not in this number). */\n// Rest has to look like it's actually resting: mixing happens over minutes, not seconds. A\n// slower flow also accumulates less numerical diffusion.\nexport const MEDIUM_DRIFT_TURBULENCE = 0.045;\n\n/** Turbulence range during playback: the low end is a quiet stretch of the track, the high end is\n * peak amplitude. */\nexport const MEDIUM_PLAYING_TURBULENCE_RANGE: readonly [number, number] = [0.55, 1.15];\n\n/** Turbulence smoothing time constant, seconds: both ramping up on play and cooling down on pause\n * go through this filter — smooth, never a jump. */\nexport const MEDIUM_TURBULENCE_TAU = 2.5;\n\n/** Emission cadence — once every this many beats, not on every beat: at 120–180 BPM that is\n * 0.5–0.75 Hz, an order of magnitude below the WCAG 2.3.1 photosensitivity ceiling (3 Hz). */\nexport const MEDIUM_EMISSION_BEATS = 4;\n\n/** Fraction of the beats that actually get an emission — breaks up the exact periodicity (a\n * metronome-perfect pulse reads as mechanical) without moving the average rate. */\nexport const MEDIUM_EMISSION_PROBABILITY = 0.55;\n\n/** Emergency floor between two emissions, seconds — a safeguard for the same WCAG 2.3.1\n * requirement, independent of `MEDIUM_EMISSION_BEATS`/BPM: even a mistake in the other constants\n * can't flash faster than this. */\nexport const MEDIUM_MIN_EMISSION_INTERVAL = 1.2;\n\n/** Average interval between solitary \"background emission\" tracks at rest, seconds, plus a random\n * `MEDIUM_NATURAL_JITTER` on top — rare, with no discernible rhythm. */\nexport const MEDIUM_NATURAL_INTERVAL = 25;\nexport const MEDIUM_NATURAL_JITTER = 35;\n\n/** The sensitive layer — fractions of height, top to bottom. Above it there isn't enough\n * supersaturation for a track to appear at all, so background emission is only ever visible here. */\nexport const MEDIUM_SENSITIVE_TOP = 0.55;\nexport const MEDIUM_SENSITIVE_BOTTOM = 0.95;\n\n/** Per-emission depth range applied to a track's width (`headWidthFrac`/`tailWidthFrac`), far…near.\n * The same knob reads as both \"thin\" and \"soft\": a Gaussian stamp's edge steepness scales with its\n * own width (`emit-shader.ts`'s only extent parameter), so shrinking it for a far track thins and\n * softens it in the same stroke — there is no second knob to hang an independent softness number\n * on. Floored at 0.55: below that a far `alpha` track's head narrows past what MacCormack's own\n * numerical smoothing already blurs it to, and the depth cue disappears into that noise floor. */\nexport const MEDIUM_TRACK_DEPTH_WIDTH_RANGE: readonly [number, number] = [0.55, 1];\n\n/** Per-emission depth range applied to a track's intensity, far…near — \"dim\". Floored at 0.4, not\n * lower: a far track still has to clear `MEDIUM_DEFAULTS.condensationFloor`'s excess threshold\n * often enough to read as a track, or depth would look like tracks randomly failing to spawn\n * rather than fading into the distance. */\nexport const MEDIUM_TRACK_DEPTH_INTENSITY_RANGE: readonly [number, number] = [0.4, 1];\n","// Hash + value-noise — the shared noise primitive (track emission also uses it for the tail's\n// ragged spread, `emit-shader.ts`), kept apart from what's curl-specific (the potential, the curl).\nexport const VG_VALUE_NOISE = `\nfloat vgHash21(float2 p) {\n float h = dot(p, float2(127.1, 311.7));\n return fract(sin(h) * 43758.5453123);\n}\n\nfloat vgValueNoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float a = vgHash21(i);\n float b = vgHash21(i + float2(1.0, 0.0));\n float c = vgHash21(i + float2(0.0, 1.0));\n float d = vgHash21(i + float2(1.0, 1.0));\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y) * 2.0 - 1.0;\n}\n`;\n\n/** GLSL float literals need a decimal point (`600` won't compile as a float) — same convention as\n * oklab.ts's `glsl()`. */\nconst glslFloat = (v: number): string => (Number.isInteger(v) ? v.toFixed(1) : String(v));\n\n/** The field's phase wraps every this many PHASE units (curlSpeed·seconds), NOT seconds of real\n * time: `web/medium.ts`'s CPU-side accumulator does `phase += dt * curlSpeed`, so the wall-clock\n * wrap period is `MEDIUM_TIME_PERIOD / curlSpeed` — about 2.3h (8333s) at the shipped default\n * (`curlSpeed = 0.12`), verified against that default in `medium-noise-periodicity.test.ts` so\n * this comment can't silently drift out of sync with it. Chosen so every octave rate below times\n * this period lands on an integer — that integer is the z-lattice period `vgValueNoise3Periodic`\n * wraps on, which is what makes the wrap exact rather than merely small. */\nexport const MEDIUM_TIME_PERIOD = 1000;\n\ntype MediumTimeOctave = {\n /** The z-axis (phase) rate this octave's `vgValueNoise3Periodic` call advances at. */\n rate: number;\n /** Fixed phase offset — decorrelates the octaves (see the comment on `vgPotential` below) and\n * doesn't affect periodicity: shifting a periodic function along its own axis leaves its period\n * unchanged. */\n offset: number;\n /** Spatial frequency multiplier on `p`. */\n scale: number;\n};\n\n/** `vgPotential`'s four fbm octaves — the single source both the GLSL text below and the JS port\n * (`vgPotentialJS`, for tests) derive their numbers from, so the two can never drift apart. */\nexport const MEDIUM_TIME_OCTAVES: readonly MediumTimeOctave[] = [\n { rate: 0.6, offset: 0, scale: 1 },\n { rate: 1.7, offset: 11, scale: 2.03 },\n { rate: 0.9, offset: 37, scale: 4.11 },\n { rate: 0.15, offset: 5, scale: 0.35 },\n];\n\n/** The z-lattice period for one octave's rate — `rate * MEDIUM_TIME_PERIOD`, which has to land on\n * an integer or the phase wrap would jump instead of being seamless (see `MEDIUM_TIME_OCTAVES`).\n * Throws at module load rather than rendering a shader that would silently jitter on every wrap. */\nexport function mediumOctavePeriod(rate: number): number {\n const period = rate * MEDIUM_TIME_PERIOD;\n if (!Number.isInteger(period)) {\n throw new Error(\n `medium noise: octave rate ${rate} * MEDIUM_TIME_PERIOD ${MEDIUM_TIME_PERIOD} = ${period}, not an integer — the phase wrap would not be seamless`,\n );\n }\n return period;\n}\n\nconst glslOctave = (o: MediumTimeOctave) => ({\n rate: glslFloat(o.rate),\n offset: glslFloat(o.offset),\n scale: glslFloat(o.scale),\n period: glslFloat(mediumOctavePeriod(o.rate)),\n});\nconst [VG_OCT0, VG_OCT1, VG_OCT2, VG_OCT3] = MEDIUM_TIME_OCTAVES.map(glslOctave);\n\n// Curl-noise velocity field: the curl of a SCALAR potential is divergence-free by construction,\n// so no Poisson solver is needed here — unlike Stable Fluids, where incompressibility has to be\n// projected explicitly.\nexport const VG_CURL_NOISE = `\n${VG_VALUE_NOISE}\n\nfloat vgHash31(float3 p) {\n float h = dot(p, float3(127.1, 311.7, 74.7));\n return fract(sin(h) * 43758.5453123);\n}\n\n// 3D value-noise: time is a third grid axis, not a coordinate shift. A shift would just SLIDE the\n// pattern (a \"traveling\" look, when motion is meant to read as random), while trilinear\n// interpolation over t CHANGES the pattern itself between grid nodes — nearby moments look alike,\n// distant ones don't, and no repeat ever shows up.\nfloat vgValueNoise3(float3 p) {\n float3 i = floor(p);\n float3 f = fract(p);\n float3 u = f * f * (3.0 - 2.0 * f);\n float n000 = vgHash31(i);\n float n100 = vgHash31(i + float3(1.0, 0.0, 0.0));\n float n010 = vgHash31(i + float3(0.0, 1.0, 0.0));\n float n110 = vgHash31(i + float3(1.0, 1.0, 0.0));\n float n001 = vgHash31(i + float3(0.0, 0.0, 1.0));\n float n101 = vgHash31(i + float3(1.0, 0.0, 1.0));\n float n011 = vgHash31(i + float3(0.0, 1.0, 1.0));\n float n111 = vgHash31(i + float3(1.0, 1.0, 1.0));\n float nx00 = mix(n000, n100, u.x);\n float nx10 = mix(n010, n110, u.x);\n float nx01 = mix(n001, n101, u.x);\n float nx11 = mix(n011, n111, u.x);\n float nxy0 = mix(nx00, nx10, u.y);\n float nxy1 = mix(nx01, nx11, u.y);\n return mix(nxy0, nxy1, u.z) * 2.0 - 1.0;\n}\n\n// Same as vgValueNoise3, but the z axis wraps at \\`period\\`: BOTH z-lattice neighbors (i.z and\n// i.z + 1.0) are folded into [0, period) before hashing, so this is EXACTLY the same function of\n// z and of z + period — not just close. vgPotential below calls this once per octave, each with\n// the period matching its own rate (MEDIUM_TIME_OCTAVES), so a phase that wraps at\n// MEDIUM_TIME_PERIOD reproduces the identical field instead of jumping to a new one.\nfloat vgValueNoise3Periodic(float3 p, float period) {\n float3 i = floor(p);\n float3 f = fract(p);\n float3 u = f * f * (3.0 - 2.0 * f);\n float z0 = mod(i.z, period);\n float z1 = mod(i.z + 1.0, period);\n float n000 = vgHash31(float3(i.xy, z0));\n float n100 = vgHash31(float3(i.xy + float2(1.0, 0.0), z0));\n float n010 = vgHash31(float3(i.xy + float2(0.0, 1.0), z0));\n float n110 = vgHash31(float3(i.xy + float2(1.0, 1.0), z0));\n float n001 = vgHash31(float3(i.xy, z1));\n float n101 = vgHash31(float3(i.xy + float2(1.0, 0.0), z1));\n float n011 = vgHash31(float3(i.xy + float2(0.0, 1.0), z1));\n float n111 = vgHash31(float3(i.xy + float2(1.0, 1.0), z1));\n float nx00 = mix(n000, n100, u.x);\n float nx10 = mix(n010, n110, u.x);\n float nx01 = mix(n001, n101, u.x);\n float nx11 = mix(n011, n111, u.x);\n float nxy0 = mix(nx00, nx10, u.y);\n float nxy1 = mix(nx01, nx11, u.y);\n return mix(nxy0, nxy1, u.z) * 2.0 - 1.0;\n}\n\n// The potential is an fbm of three octaves, each evolving over the field's PHASE at its own speed\n// (unrelated to the others, plus a phase offset): no single phase shift can align them all at\n// once, so no traveling pattern remains. Each octave wraps its own z-lattice at the period that\n// matches its rate (see MEDIUM_TIME_OCTAVES/vgValueNoise3Periodic), so\n// vgPotential(p, phase) === vgPotential(p, phase + ${glslFloat(MEDIUM_TIME_PERIOD)}) exactly — a\n// CPU-side phase accumulator can wrap at that period with no visible jump (see web/medium.ts). The\n// large-scale factor is a slow non-uniformity layered over the sum — without it, a field that's\n// uniform by statistics still reads as a regular cell pattern.\nfloat vgPotential(float2 p, float phase) {\n float v = vgValueNoise3Periodic(float3(p * ${VG_OCT0.scale}, phase * ${VG_OCT0.rate} + ${VG_OCT0.offset}), ${VG_OCT0.period}) * 0.5;\n v += vgValueNoise3Periodic(float3(p * ${VG_OCT1.scale}, phase * ${VG_OCT1.rate} + ${VG_OCT1.offset}), ${VG_OCT1.period}) * 0.25;\n v += vgValueNoise3Periodic(float3(p * ${VG_OCT2.scale}, phase * ${VG_OCT2.rate} + ${VG_OCT2.offset}), ${VG_OCT2.period}) * 0.125;\n float macro = vgValueNoise3Periodic(float3(p * ${VG_OCT3.scale}, phase * ${VG_OCT3.rate} + ${VG_OCT3.offset}), ${VG_OCT3.period});\n return v * (0.7 + 0.3 * macro);\n}\n\n// Velocity = (dPsi/dy, -dPsi/dx) — the curl of a scalar field, solenoidal for any Psi. Central\n// differences instead of an analytic derivative: the potential is a sum of three value-noise\n// calls, and there is no cheaper way to differentiate it by hand than four extra samples.\nfloat2 vgCurlVelocity(float2 p, float phase) {\n float e = 0.6;\n float px1 = vgPotential(p + float2(e, 0.0), phase);\n float px0 = vgPotential(p - float2(e, 0.0), phase);\n float py1 = vgPotential(p + float2(0.0, e), phase);\n float py0 = vgPotential(p - float2(0.0, e), phase);\n return float2(py1 - py0, -(px1 - px0)) / (2.0 * e);\n}\n`;\n\n// --- JS port of the periodic potential, for tests that can't spin up a WebGL2 context ------------\n//\n// The exact same arithmetic as vgHash31/vgValueNoise3Periodic/vgPotential above, so a vitest can\n// prove periodicity and continuity across the phase wrap without a GPU. Kept next to the shader\n// text as the single source of truth for what \"the same constants\" means: both are generated from\n// MEDIUM_TIME_OCTAVES, and `medium-noise-periodicity.test.ts` checks the shader string itself\n// contains these numbers rather than trusting that the two never drifted apart.\n\nfunction hash31JS(x: number, y: number, z: number): number {\n const h = x * 127.1 + y * 311.7 + z * 74.7;\n const s = Math.sin(h) * 43758.5453123;\n return s - Math.floor(s);\n}\n\nconst smoothstep3JS = (f: number): number => f * f * (3 - 2 * f);\n\n/** GLSL's `mod(x, y)` for floats is `x - y * floor(x / y)` — always in `[0, y)`, unlike JS's `%`,\n * which keeps the sign of `x`. Matching it exactly here is the point: this IS the wrap the shader\n * performs. */\nconst glslMod = (x: number, y: number): number => x - y * Math.floor(x / y);\n\n/** Pure-JS port of `vgValueNoise3Periodic`. */\nexport function vgValueNoise3PeriodicJS(px: number, py: number, pz: number, period: number): number {\n const ix = Math.floor(px);\n const iy = Math.floor(py);\n const iz = Math.floor(pz);\n const ux = smoothstep3JS(px - ix);\n const uy = smoothstep3JS(py - iy);\n const uz = smoothstep3JS(pz - iz);\n const z0 = glslMod(iz, period);\n const z1 = glslMod(iz + 1, period);\n const n000 = hash31JS(ix, iy, z0);\n const n100 = hash31JS(ix + 1, iy, z0);\n const n010 = hash31JS(ix, iy + 1, z0);\n const n110 = hash31JS(ix + 1, iy + 1, z0);\n const n001 = hash31JS(ix, iy, z1);\n const n101 = hash31JS(ix + 1, iy, z1);\n const n011 = hash31JS(ix, iy + 1, z1);\n const n111 = hash31JS(ix + 1, iy + 1, z1);\n const nx00 = n000 + (n100 - n000) * ux;\n const nx10 = n010 + (n110 - n010) * ux;\n const nx01 = n001 + (n101 - n001) * ux;\n const nx11 = n011 + (n111 - n011) * ux;\n const nxy0 = nx00 + (nx10 - nx00) * uy;\n const nxy1 = nx01 + (nx11 - nx01) * uy;\n return (nxy0 + (nxy1 - nxy0) * uz) * 2 - 1;\n}\n\n/** Pure-JS port of `vgPotential`, built from the same `MEDIUM_TIME_OCTAVES` table the shader text\n * above is generated from. */\nexport function vgPotentialJS(px: number, py: number, phase: number): number {\n const [o0, o1, o2, o3] = MEDIUM_TIME_OCTAVES;\n let v =\n vgValueNoise3PeriodicJS(px * o0.scale, py * o0.scale, phase * o0.rate + o0.offset, mediumOctavePeriod(o0.rate)) *\n 0.5;\n v +=\n vgValueNoise3PeriodicJS(px * o1.scale, py * o1.scale, phase * o1.rate + o1.offset, mediumOctavePeriod(o1.rate)) *\n 0.25;\n v +=\n vgValueNoise3PeriodicJS(px * o2.scale, py * o2.scale, phase * o2.rate + o2.offset, mediumOctavePeriod(o2.rate)) *\n 0.125;\n const macro = vgValueNoise3PeriodicJS(\n px * o3.scale,\n py * o3.scale,\n phase * o3.rate + o3.offset,\n mediumOctavePeriod(o3.rate),\n );\n return v * (0.7 + 0.3 * macro);\n}\n","// Initial spots for the three GAS channels: three fixed-color channels with initial spots — this\n// is the entire water supply, gas has no other source, it only conserves and mixes through\n// advection. One pass, three channels: r/g/b are each smoke channel's density on its own, not a\n// color — composite-shader.ts paints them with a fixed palette.\nexport const MEDIUM_SEED_SHADER = `\nuniform float2 u_spot0;\nuniform float2 u_spot1;\nuniform float2 u_spot2;\nuniform float u_spotRadius;\n\nfloat vgSpot(float2 p, float2 c, float r) {\n float d = length(p - c) / max(r, 1.0);\n return exp(-d * d * 2.0);\n}\n\nhalf4 main(float2 xy) {\n float3 dye = float3(\n vgSpot(xy, u_spot0, u_spotRadius),\n vgSpot(xy, u_spot1, u_spotRadius),\n vgSpot(xy, u_spot2, u_spotRadius)\n );\n return half4(half3(dye), half(1.0));\n}\n`;\n","// Bilinear resample of one buffer into a different-sized target — used only when the simulation\n// grid changes size (`ensureGrid` in web/medium.ts resizing on a window resize or a device\n// rotation), to carry vapor/condensate/track state across instead of wiping it. A destroy-and-\n// reseed on every resize was the flicker the design forbids: the vapor jumps back to its three\n// initial spots and condensate/tracks blink out.\n//\n// Same flip convention as advect-shader.ts's `.eval()` reads: `xy` is top-down (the shader's own\n// entry coordinate), the source texture is stored bottom-up, so a straight `.eval()` without the\n// flip would resample from the wrong row. `u_src`'s own size (`u_srcSize`) comes from the\n// transpiler's auto-generated uniform for `uniform shader` (see the comment on the cross-sampler\n// in web/medium.ts) — the OLD grid's dimensions, set explicitly since bindTextureAt only binds the\n// sampler itself.\nexport const MEDIUM_RESAMPLE_SHADER = `\nuniform shader u_src;\n\nhalf4 main(float2 xy) {\n float2 uv = xy / u_resolution;\n float2 src = uv * u_srcSize;\n return u_src.eval(float2(src.x, u_srcSize.y - src.y));\n}\n`;\n","import { VG_CURL_NOISE } from './noise';\n\n// MacCormack instead of plain semi-Lagrangian transport: a single backtrace sample is heavily\n// diffusive on a coarse grid — over roughly 15-35s turbulence stirs the vapor species into a flat\n// background, and all the structure the grid exists for disappears. This scheme splits the\n// transport of EACH field (vapor, condensate) into three passes instead of one:\n// forward — the same backtrace sample the old single-pass scheme used (`MEDIUM_*_FORWARD_SHADER`);\n// correct — a reverse backtrace of the already-transported field, a correction for the\n// difference against the original, and a limiter (clamped to the source cell's\n// neighbors) — without the limiter the scheme produces overshoots that weren't in the\n// field, which read as flicker (`MEDIUM_*_CORRECT_SHADER`);\n// react — the phase exchange (condensation/evaporation/growth), applied to the already\n// corrected transport rather than the raw backtrace (`MEDIUM_*_REACT_SHADER`).\n// The vapor<->condensate exchange used to read its partner AT ITS OWN transported point; splitting\n// the passes removes the cause outright: both phases transport on SEPARATE fields with no\n// cross-reads, and the exchange happens AFTER, on values already transported and grid-aligned at\n// the same point xy — so \"how much vapor was lost\" and \"how much condensate was gained\" agree by\n// construction.\n// u_phase replaces the old u_time/u_curlSpeed pair: the CPU accumulates phase += dt * curlSpeed\n// in float64 and wraps it at MEDIUM_TIME_PERIOD (see web/medium.ts and noise.ts's\n// MEDIUM_TIME_OCTAVES) before it ever becomes a float32 uniform. Over hours of playback, a raw\n// u_time kept growing without bound and eventually lost the bits that place a point within its\n// own lattice cell — the visible symptom was the field jittering in place. A side benefit: since\n// the rate is baked into the accumulation rather than multiplied in on every frame, changing\n// curlSpeed at runtime changes the SLOPE going forward instead of rescaling everything already\n// accumulated — no jump.\nconst MEDIUM_ADVECT_UNIFORMS = `\nuniform shader u_dye;\nuniform float u_dt;\nuniform float u_phase;\nuniform float u_curlFreq;\nuniform float u_advectSpeed;\nuniform float u_turbulence;\n\n${VG_CURL_NOISE}\n`;\n\n// Reading our own previous frame, which sits in the texture bottom-to-top: xy runs top-to-bottom,\n// and `.eval()` passes the coordinate straight through as a texture coordinate. Without this flip,\n// every step would turn the field upside down, and the two states would alternate frame to frame —\n// flicker across half the refresh rate. The rule is the same for ANY read of a smoke texture via\n// `.eval()` in this file: composite-shader.ts reads these same buffers for display and has to flip\n// the same way.\nconst MEDIUM_ADVECT_VELOCITY = `\n float2 vel = vgCurlVelocity(xy * u_curlFreq, u_phase) * u_advectSpeed * u_turbulence;\n`;\n\n// Gravity touches only the condensate — an addition on top of the shared field rather than a\n// separate calculation: droplets are carried by the same wind as vapor, plus a downward drift.\nconst MEDIUM_ADVECT_VELOCITY_SETTLE = `\n${MEDIUM_ADVECT_VELOCITY}\n vel.y += u_settleSpeed;\n`;\n\n// The gas itself leans down too, just far less than condensate: \"from the side\" only reads if\n// gravity touches the medium as a whole, not only its droplets. `u_vaporDrift` is an order of\n// magnitude below `u_settleSpeed` (see MEDIUM_DEFAULTS.gravityVaporDrift) — barely noticeable\n// frame to frame, cumulative over the run.\nconst MEDIUM_ADVECT_VELOCITY_VAPOR = `\n${MEDIUM_ADVECT_VELOCITY}\n vel.y += u_vaporDrift;\n`;\n\n// The forward MacCormack half-step — ONE backtrace sample, bit-for-bit what the whole scheme used\n// to do on its own. The result goes to a temporary buffer rather than out; the `correct` pass will\n// read it again to estimate, via a reverse step, how much this single sample smeared.\nexport const MEDIUM_VAPOR_FORWARD_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform float u_vaporDrift;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY_VAPOR}\n float2 src = xy - vel * u_dt;\n return u_dye.eval(float2(src.x, u_dyeSize.y - src.y));\n}\n`;\n\nexport const MEDIUM_CONDENSATE_FORWARD_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform float u_settleSpeed;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY_SETTLE}\n float2 src = xy - vel * u_dt;\n return u_dye.eval(float2(src.x, u_dyeSize.y - src.y));\n}\n`;\n\n// Reverse half-step + correction + limiter (Selle et al. 2007). The macro expects `vel` already\n// computed (the calling shader sets it to either the forward or the settling version) and two\n// samplers: `u_dye` — the field BEFORE this frame's transport, `u_forward` — the forward pass's\n// result for the same field.\n//\n// Correction: corrected = forward + (field_before - forward_backward)/2 — the standard MacCormack\n// formula, roughly halving one backtrace's numerical diffusion in order of accuracy.\n//\n// The limiter is mandatory: without it the correction isn't LOCALLY conservative and can push a\n// value above every one of the source cell's neighbors — a new extremum that wasn't in the field.\n// On a frame-by-frame sweep that reads as flicker. The clamp is to the range of the four texels\n// neighboring the ORIGINAL field around the point the forward pass came from (not bilinearly\n// blended — exactly the texel, `.eval()` at its precise center, the same technique used for\n// droplet growth in the react shaders below).\nconst MEDIUM_MACCORMACK_CORRECT = `\n float2 srcForward = xy - vel * u_dt;\n float2 srcReverse = xy + vel * u_dt;\n half4 forwardSelf = u_forward.eval(float2(xy.x, u_forwardSize.y - xy.y));\n half4 forwardReverse = u_forward.eval(float2(srcReverse.x, u_forwardSize.y - srcReverse.y));\n half4 prevSelf = u_dye.eval(float2(xy.x, u_dyeSize.y - xy.y));\n half4 corrected = forwardSelf + half4(0.5) * (prevSelf - forwardReverse);\n\n float2 src = float2(srcForward.x, u_dyeSize.y - srcForward.y);\n float2 base = floor(src - 0.5);\n half4 c00 = u_dye.eval(base + float2(0.5, 0.5));\n half4 c10 = u_dye.eval(base + float2(1.5, 0.5));\n half4 c01 = u_dye.eval(base + float2(0.5, 1.5));\n half4 c11 = u_dye.eval(base + float2(1.5, 1.5));\n half4 lo = min(min(c00, c10), min(c01, c11));\n half4 hi = max(max(c00, c10), max(c01, c11));\n half4 limited = clamp(corrected, lo, hi);\n`;\n\nexport const MEDIUM_VAPOR_CORRECT_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform shader u_forward;\nuniform float u_vaporDrift;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY_VAPOR}\n${MEDIUM_MACCORMACK_CORRECT}\n return limited;\n}\n`;\n\nexport const MEDIUM_CONDENSATE_CORRECT_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform float u_settleSpeed;\nuniform shader u_forward;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY_SETTLE}\n${MEDIUM_MACCORMACK_CORRECT}\n return limited;\n}\n`;\n\n// Water is a conserved volume: the sum of vapor and condensate has no source and no sink, only\n// transport and a transition between phases. `u_totalScale` is not water physics but a method\n// correction (see `web/medium.ts`): transport alone doesn't hold the sum exactly (residual\n// numerical diffusion/dispersion), so the runtime checks the total water (vapor+condensate) once a\n// second against the target and folds a correction into both buffers — the same one for each.\n//\n// The reaction operates on the already CORRECTED (MacCormack + limiter) transport of both phases,\n// not the raw backtrace: `u_transported`/`u_condensateTransported` are the correct pass's output,\n// grid-aligned at the same point xy, so the exchange reads its partner with no backtrace of its\n// own and no desync (see the comment at the top of this file).\n//\n// Vapor carries color (three channels r/g/b, as before).\nexport const MEDIUM_VAPOR_REACT_SHADER = `\nuniform shader u_transported;\nuniform shader u_condensateTransported;\nuniform float u_dt;\nuniform float u_condensationRate;\nuniform float u_condensationFloor;\nuniform float u_evaporationRate;\nuniform float u_totalScale;\n\nhalf4 main(float2 xy) {\n float3 prevVapor = float3(u_transported.eval(float2(xy.x, u_transportedSize.y - xy.y)).rgb);\n float condensateAtXy = float(u_condensateTransported.eval(float2(xy.x, u_condensateTransportedSize.y - xy.y)).r);\n float vaporTotal = prevVapor.r + prevVapor.g + prevVapor.b;\n\n // Only the EXCESS of vapor above the background floor condenses, not any nonzero density —\n // otherwise condensate would exactly retrace the vapor spot's shape (the same outline, or\n // softer from blur), leaving the two phases nowhere to visually diverge: vapor is already soft,\n // and condensate needs to read brighter and sharper than it. The floor concentrates condensate\n // in dense turbulence cores, leaving the sparse vapor around it uncondensed — that's where the\n // contrast comes from.\n float excess = max(0.0, vaporTotal - u_condensationFloor);\n float condensedAmount = excess * (1.0 - exp(-u_condensationRate * u_dt));\n float lossFraction = vaporTotal > 1e-6 ? condensedAmount / vaporTotal : 0.0;\n float3 afterLoss = prevVapor * (1.0 - lossFraction);\n\n float condKeep = exp(-u_evaporationRate * u_dt);\n float returned = condensateAtXy * (1.0 - condKeep);\n\n // Evaporated water is distributed by the CURRENT vapor proportion — the condensation/\n // evaporation cycle doesn't shift the hue, water only oscillates between phases (color belongs\n // to vapor).\n float sumAfterLoss = afterLoss.r + afterLoss.g + afterLoss.b;\n float3 hueShare = sumAfterLoss > 1e-6 ? afterLoss / sumAfterLoss : float3(1.0 / 3.0);\n float3 vapor = (afterLoss + hueShare * returned) * u_totalScale;\n return half4(half3(vapor), half(1.0));\n}\n`;\n\n// Condensate is droplets: a scalar (density lives in .r, g/b unused — it has no color of its own,\n// composite-shader.ts tints it with the local vapor hue). Two processes on top of transport:\n// exchange with vapor (mirrors the exchange in MEDIUM_VAPOR_REACT_SHADER, the same\n// excess/condensedAmount from the same transported vaporTotal) and growth (blending with grid\n// neighbors every frame with no age counter: whatever survived more steps ends up more blended\n// with its neighbors on its own). Settling isn't here — it's a velocity addition in\n// *_FORWARD_SHADER/*_CORRECT_SHADER.\nexport const MEDIUM_CONDENSATE_REACT_SHADER = `\nuniform shader u_transported;\nuniform shader u_vaporTransported;\nuniform shader u_dye;\nuniform float u_dt;\nuniform float u_condensationRate;\nuniform float u_condensationFloor;\nuniform float u_evaporationRate;\nuniform float u_spreadRate;\nuniform float u_totalScale;\n\nhalf4 main(float2 xy) {\n float prevCondensate = float(u_transported.eval(float2(xy.x, u_transportedSize.y - xy.y)).r);\n float3 vaporAtXy = float3(u_vaporTransported.eval(float2(xy.x, u_vaporTransportedSize.y - xy.y)).rgb);\n float vaporTotal = vaporAtXy.r + vaporAtXy.g + vaporAtXy.b;\n\n float excess = max(0.0, vaporTotal - u_condensationFloor);\n float condensed = excess * (1.0 - exp(-u_condensationRate * u_dt));\n float condKeep = exp(-u_evaporationRate * u_dt);\n float afterReaction = prevCondensate * condKeep + condensed;\n\n // Growth is an Euler step separate from transport: the neighbors are sampled at THIS cell's own\n // xy in the ORIGINAL (pre-transport-this-frame) field u_dye, not the already\n // transported/corrected value. On a non-uniform flow, warping compresses the domain in some\n // places and stretches it in others, and blurring at a warped point stacks those distortions —\n // the error accumulates frame over frame (measured: condensate steadily gained about 5% extra\n // water alongside its own mass growth). Right on the grid, with no warp, the same blur is an\n // honest averaging kernel that conserves the sum on a periodic domain.\n float2 selfSrc = float2(xy.x, u_dyeSize.y - xy.y);\n float n0 = float(u_dye.eval(float2(selfSrc.x + 1.0, selfSrc.y)).r);\n float n1 = float(u_dye.eval(float2(selfSrc.x - 1.0, selfSrc.y)).r);\n float n2 = float(u_dye.eval(float2(selfSrc.x, selfSrc.y - 1.0)).r);\n float n3 = float(u_dye.eval(float2(selfSrc.x, selfSrc.y + 1.0)).r);\n float neighborAvg = (n0 + n1 + n2 + n3) * 0.25;\n float spreadWeight = 1.0 - exp(-u_spreadRate * u_dt);\n float grown = mix(afterReaction, neighborAvg, spreadWeight) * u_totalScale;\n\n return half4(half3(grown, 0.0, 0.0), half(1.0));\n}\n`;\n\n// Tracks are not this layer's water: an event stamps a burst (`emit-shader.ts`), and it fades on\n// its own. Decay is a property of THIS layer, so it lives here rather than in the vapor/condensate\n// passes. Track transport stays a single backtrace: it doesn't need MacCormack — tracks aren't\n// conserved, they live for seconds and their own decay extinguishes them before numerical\n// diffusion becomes noticeable.\nexport const MEDIUM_TRACK_ADVECT_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform float u_decay;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY}\n float2 back = xy - vel * u_dt;\n half4 prev = u_dye.eval(float2(back.x, u_dyeSize.y - back.y));\n float3 decayed = float3(prev.rgb) * exp(-u_decay * u_dt);\n return half4(half3(decayed), half(1.0));\n}\n`;\n","import { VG_OKLAB_TO_SRGB } from './oklab';\n\n// Composites vapor, condensate and track density into color — DIRECTLY into the buffer the lens\n// samples: this is not a separate pass drawn over the glass, it fills the same contentTexture an\n// ordinary 2D scene would, just from the GPU side.\n//\n// The mix happens in Oklab: two saturated channels averaged in raw RGB collapse into muddy gray;\n// in Oklab, lightness and chroma don't bleed into each other, and the sum stays vivid. The palette\n// arrives already in Oklab (`u_lab0/1/2`, `u_labBg`, `u_labCondensate`) — the forward direction\n// (sRGB → Oklab) is computed once per frame by `srgbToOklab` in `web/medium.ts`, not here per pixel.\n//\n// `u_dyeScale` converts a frame coordinate into SIMULATION-GRID pixels — deliberately coarser than\n// the frame — and only here, once, does the resolution become what the eye actually sees again,\n// through `.eval()`'s bilinear sample. NO extra Y flip here, unlike the grid-to-grid reads in\n// advect-shader.ts: those flip because they read a buffer through `.eval()` while still inside the\n// same top-down \"xy\" frame the buffer was written in. This is the one read that leaves that frame\n// for the screen's own — `xy` here already is screen space (this shader's entry point is\n// transpiled exactly like the lens/surface shaders), so the coordinate handed to `u_vapor` etc.\n// only needs the grid/frame resolution scale, not another flip. Measured with a headless render:\n// adding the extra flip (as a very early version of this file did) made settled condensate drift\n// toward the TOP of the screen instead of the bottom.\n//\n// Three buffers, not one: vapor is conserved, condensate transitions from vapor and back\n// (`MEDIUM_VAPOR_REACT_SHADER`/`MEDIUM_CONDENSATE_REACT_SHADER`, see `advect-shader.ts`), tracks\n// fade out (`MEDIUM_TRACK_ADVECT_SHADER`) — a channel's density for the mix is the sum of vapor and\n// tracks, while condensate mixes separately (it carries its own, near-white, only lightly tinted\n// paint).\n//\n// DEPTH: a far plane is not a second simulation — it is the SAME vapor/condensate buffers, read a\n// second time at a different scale and offset (see MEDIUM_DEFAULTS.depthFarScale/depthFarOffsetFrac\n// — u_farOffset itself arrives already resolved to grid-px, see depthFarOffsetPx in params.ts).\n// Reading the identical evolving field through two different \"lenses\" gives parallax and finer\n// far-plane structure from one number (`depthFarScale`) with no extra grid to step, and a cheap\n// 4-tap blur gives the far plane aerial perspective's blur AND lower contrast from one mechanism\n// (a spatial average cannot raise local variance). Tracks are NOT re-sampled per plane — a track's\n// own depth is baked into its stamped width/intensity at emission time (`dynamics.ts`), not into a\n// second read here.\n//\n// The far plane folds into the SAME weighted-average-in-Oklab this file already used for species:\n// its weights simply add to the near plane's before the one division, exactly the technique species\n// already use to combine three channels without the sum ever pulling toward gray.\nexport const MEDIUM_COMPOSITE_SHADER = `\nuniform shader u_vapor;\nuniform shader u_condensate;\nuniform shader u_track;\nuniform float2 u_dyeScale;\nuniform float3 u_lab0;\nuniform float3 u_lab1;\nuniform float3 u_lab2;\nuniform float3 u_labBg;\nuniform float3 u_labCondensate;\nuniform float u_baseWeight;\nuniform float u_condensateTint;\nuniform float u_condensateGain;\nuniform float u_farScale;\nuniform float2 u_farOffset;\nuniform float u_farBlurRadius;\nuniform float u_farWeight;\nuniform float u_gravityBoost;\nuniform float u_gravityBoostStart;\n\n${VG_OKLAB_TO_SRGB}\n\nhalf4 main(float2 xy) {\n float2 baseSrc = xy * u_dyeScale;\n\n // Near plane — sampled exactly as before the depth work; the calibrated look lives here.\n half4 vapor = u_vapor.eval(baseSrc);\n half4 condensate = u_condensate.eval(baseSrc);\n half4 track = u_track.eval(baseSrc);\n\n // Far plane — the identical field, scaled and offset (see the file header). A 4-tap box blur\n // (no separate center sample, the cheapest isotropic blur available) supplies aerial perspective's\n // blur and, as a side effect of averaging, its lower contrast too.\n float2 farSrc = baseSrc * u_farScale + u_farOffset;\n float2 blurX = float2(u_farBlurRadius, 0.0);\n float2 blurY = float2(0.0, u_farBlurRadius);\n half4 vaporFar = (u_vapor.eval(farSrc + blurX) + u_vapor.eval(farSrc - blurX) +\n u_vapor.eval(farSrc + blurY) + u_vapor.eval(farSrc - blurY)) * half4(0.25);\n half4 condensateFar = (u_condensate.eval(farSrc + blurX) + u_condensate.eval(farSrc - blurX) +\n u_condensate.eval(farSrc + blurY) + u_condensate.eval(farSrc - blurY)) * half4(0.25);\n\n // Gravity's \"denser layer at the bottom of the chamber\" — a compositing-only boost (see\n // MEDIUM_DEFAULTS.gravityBottomBoost): it scales how density already in the conserved buffers is\n // DISPLAYED, never the buffers themselves, so it can't threaten water conservation. Shared by\n // both planes: the chamber's floor is denser everywhere along its depth, not just up close.\n //\n // Verified with a headless render, not assumed: ramping this with xy.y directly lit up the TOP\n // of the frame, not the bottom (contentTexture's own orientation contract flips y once more\n // between this pass and the final scene — see the file header) — hence the 1.0 - below.\n float bottomFrac = 1.0 - xy.y / u_resolution.y;\n float bottomT = smoothstep(u_gravityBoostStart, 1.0, bottomFrac);\n float bottomBoost = 1.0 + u_gravityBoost * bottomT;\n\n float gasR = (float(vapor.r) + float(vaporFar.r) * u_farWeight) * bottomBoost;\n float gasG = (float(vapor.g) + float(vaporFar.g) * u_farWeight) * bottomBoost;\n float gasB = (float(vapor.b) + float(vaporFar.b) * u_farWeight) * bottomBoost;\n float w0 = gasR + float(track.r);\n float w1 = gasG + float(track.g);\n float w2 = gasB + float(track.b);\n float wBg = u_baseWeight;\n\n // A faint condensate hint from the LOCAL vapor, not its own color (droplets scatter light\n // rather than tint it) — a fraction of the way from white to the vapor tone sampled right here.\n float vaporLocal = max(w0 + w1 + w2, 1e-4);\n float3 vaporHue = (u_lab0 * w0 + u_lab1 * w1 + u_lab2 * w2) / vaporLocal;\n float3 condensateLab = mix(u_labCondensate, vaporHue, u_condensateTint);\n float wCondensate =\n (float(condensate.r) + float(condensateFar.r) * u_farWeight) * u_condensateGain * bottomBoost;\n\n float total = max(w0 + w1 + w2 + wBg + wCondensate, 1e-4);\n float3 mixLab =\n (u_labBg * wBg + u_lab0 * w0 + u_lab1 * w1 + u_lab2 * w2 + condensateLab * wCondensate) / total;\n float3 rgbLinear = clamp(vgOklabToLinear(mixLab), float3(0.0), float3(1.0));\n float3 srgb = vgLinearToSrgb(rgbLinear);\n return half4(half3(srgb), half(1.0));\n}\n`;\n","import { VG_VALUE_NOISE } from './noise';\n\n// Stamps a track into the vapor: one pass that stamps a density burst DIRECTLY into the dye buffer\n// — the same one that carries the curl-noise field and that composite-shader.ts reads. From there\n// the track lives and fades through exactly the same code as the gas: advection carries and spins\n// it, exponential decay extinguishes it. It has no position of its own and no lifetime timer here —\n// the track's entire physics is the shape of this ONE stamped frame.\n//\n// The head (s=1, at u_head) is sharp, narrow, bright — fresh ionization. The tail (s=0, at\n// u_source) is wider, dimmer and has settled downward — those droplets had time to grow earlier.\n// The path's noise-driven spread also grows toward the tail and fades toward the head, so the\n// thread stays straight and crisp at its leading end.\nexport const MEDIUM_EMIT_SHADER = `\nuniform float2 u_source;\nuniform float2 u_head;\nuniform float u_headWidth;\nuniform float u_tailWidth;\nuniform float u_raggedAmp;\nuniform float u_raggedFreq;\nuniform float u_settle;\nuniform float u_tailDim;\nuniform float u_intensity;\nuniform float u_seed;\nuniform float3 u_channelMask;\n\n${VG_VALUE_NOISE}\n\nhalf4 main(float2 xy) {\n float2 dir = u_head - u_source;\n float len = max(length(dir), 1.0);\n float2 side = float2(-dir.y, dir.x) / len;\n\n float best = 0.0;\n const int STEPS = 12;\n for (int i = 0; i < STEPS; i++) {\n float s = float(i) / float(STEPS - 1);\n float tailness = 1.0 - s;\n float ragged = u_raggedAmp * tailness * vgValueNoise(float2(s * u_raggedFreq + u_seed, u_seed * 3.7));\n float2 p = mix(u_source, u_head, s) + side * ragged + float2(0.0, u_settle * tailness * tailness);\n float w = max(mix(u_tailWidth, u_headWidth, s), 0.5);\n float amp = mix(u_tailDim, 1.0, s);\n float d = length(xy - p);\n float v = amp * exp(-(d * d) / (2.0 * w * w));\n best = max(best, v);\n }\n\n float3 dye = u_channelMask * (best * u_intensity);\n return half4(half3(dye), half(1.0));\n}\n`;\n","// The medium's runtime layer: ping-pong FBOs for the simulation grid and its GPU passes. Shader\n// text and parameters live in the package's model (`../medium`) — this file is WebGL only.\n//\n// Three independent buffers, not one: vapor (a conserved color reserve), condensate (droplets —\n// they grow, settle, and exchange with vapor) and tracks (born from a stamp, fade through decay).\n// Vapor and condensate form ONE conserved sum — water; tracks aren't part of it, and their decay\n// and stamping haven't been folded into that accounting.\nimport {\n depthFarOffsetPx,\n MEDIUM_COMPOSITE_SHADER,\n MEDIUM_CONDENSATE_CORRECT_SHADER,\n MEDIUM_CONDENSATE_FORWARD_SHADER,\n MEDIUM_CONDENSATE_REACT_SHADER,\n MEDIUM_DEFAULTS,\n MEDIUM_EMIT_SHADER,\n MEDIUM_RESAMPLE_SHADER,\n MEDIUM_SEED_SHADER,\n MEDIUM_TIME_PERIOD,\n MEDIUM_TRACK_ADVECT_SHADER,\n MEDIUM_VAPOR_CORRECT_SHADER,\n MEDIUM_VAPOR_FORWARD_SHADER,\n MEDIUM_VAPOR_REACT_SHADER,\n srgbToOklab,\n type VireUIKitMediumEmission,\n type VireUIKitMediumParams,\n} from '../medium';\nimport { toGLSL } from 'vireglass';\nimport {\n bindTextureAt,\n createFramebuffer,\n createProgram,\n createTexture,\n drawFullscreenTriangle,\n locationCache,\n setUniform,\n} from 'vireglass/web';\n\nexport type MediumRuntime = {\n /** Recreates the grid on the first call (seeding it); on a later call with a different size,\n * RESAMPLES the old vapor/condensate/track state into the new size instead of reseeding — see\n * `resizeGrid` below for why. A no-op when the size hasn't changed. */\n ensureGrid(gridWidth: number, gridHeight: number): void;\n /** One transport step for all three buffers (vapor and condensate via MacCormack:\n * forward/correct/react, see `advect-shader.ts`; tracks via a single backtrace plus decay),\n * ping-ponging their own FBOs. The field's phase (curl-noise's time axis) is this runtime's own\n * state, accumulated from `dt` — see `u_phase` below — not derived from a caller-supplied clock. */\n step(dt: number, params?: Partial<VireUIKitMediumParams>): void;\n /**\n * Stamps tracks into the TRACK buffer just refreshed by advection — via additive blending,\n * rather than a separate pass or its own position list. From there the same field carries them\n * and their own decay extinguishes them; there is no lifetime timer here.\n */\n emit(emissions: readonly VireUIKitMediumEmission[]): void;\n /**\n * Composites the current density (vapor + condensate + tracks) into a buffer already bound by\n * the caller (the renderer's `contentFbo`) — this is exactly how the medium acts as the second\n * backdrop path: the result lands in the same `contentTexture` the lens samples, not a separate\n * pass drawn over the glass.\n */\n composite(contentWidth: number, contentHeight: number, params?: Partial<VireUIKitMediumParams>): void;\n /** The raw density sum across every cell — a measurement hook, not part of the frame. Balance\n * and structure gates read THIS, not a composited pixel: compositing muddies things with Oklab\n * nonlinearity, and the water (vapor+condensate) sum and contrast need to be measurable on\n * their own. */\n readTotals(): { vapor: number; condensate: number; track: number } | null;\n /** The raw VAPOR grid, at simulation-grid resolution, `data` as r/g/b triples (three species)\n * per cell, not a sum. Used by the structure gate (contrast BETWEEN species across the grid). */\n readVaporGrid(): { cols: number; rows: number; data: number[] } | null;\n /** The raw CONDENSATE grid, at simulation-grid resolution, `data` as one density value per\n * cell (condensate has no color — see `readVaporGrid`'s r/g/b for the contrast). Used by the\n * gravity gate to find the density-weighted row where the mass sits. */\n readCondensateGrid(): { cols: number; rows: number; data: number[] } | null;\n destroy(): void;\n};\n\ntype DyeTarget = { texture: WebGLTexture; fbo: WebGLFramebuffer };\ntype DyePair = readonly [DyeTarget, DyeTarget];\n\n/** RGBA16F keeps decay from banding on its long tail; without `EXT_color_buffer_float` (float\n * render targets need it) this falls back to RGBA8 — coarser, but works everywhere. */\nfunction pickDyeFormat(gl: WebGL2RenderingContext): { internalFormat: number; type: number } {\n const hasFloat = gl.getExtension('EXT_color_buffer_float') !== null;\n return hasFloat\n ? { internalFormat: gl.RGBA16F, type: gl.HALF_FLOAT }\n : { internalFormat: gl.RGBA8, type: gl.UNSIGNED_BYTE };\n}\n\nexport function createMediumRuntime(gl: WebGL2RenderingContext, vertexSource: string): MediumRuntime {\n const format = pickDyeFormat(gl);\n\n const vaporForwardProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_VAPOR_FORWARD_SHADER));\n const vaporCorrectProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_VAPOR_CORRECT_SHADER));\n const vaporReactProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_VAPOR_REACT_SHADER));\n const condensateForwardProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_CONDENSATE_FORWARD_SHADER));\n const condensateCorrectProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_CONDENSATE_CORRECT_SHADER));\n const condensateReactProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_CONDENSATE_REACT_SHADER));\n const trackAdvectProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_TRACK_ADVECT_SHADER));\n const compositeProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_COMPOSITE_SHADER));\n const seedProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_SEED_SHADER));\n const emitProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_EMIT_SHADER));\n const resampleProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_RESAMPLE_SHADER));\n\n const vaporForwardLoc = locationCache(gl, vaporForwardProgram);\n const vaporCorrectLoc = locationCache(gl, vaporCorrectProgram);\n const vaporReactLoc = locationCache(gl, vaporReactProgram);\n const condensateForwardLoc = locationCache(gl, condensateForwardProgram);\n const condensateCorrectLoc = locationCache(gl, condensateCorrectProgram);\n const condensateReactLoc = locationCache(gl, condensateReactProgram);\n const trackAdvectLoc = locationCache(gl, trackAdvectProgram);\n const compositeLoc = locationCache(gl, compositeProgram);\n const seedLoc = locationCache(gl, seedProgram);\n const emitLoc = locationCache(gl, emitProgram);\n const resampleLoc = locationCache(gl, resampleProgram);\n\n let gridW = 0;\n let gridH = 0;\n let vapor: DyePair | null = null;\n let condensate: DyePair | null = null;\n let track: DyePair | null = null;\n // MacCormack scratch buffers for a single frame, not ping-pong: each is fully overwritten by its\n // own pass and read again within the SAME step — no persistent state carried between frames.\n let vaporForward: DyeTarget | null = null;\n let vaporCorrected: DyeTarget | null = null;\n let condensateForward: DyeTarget | null = null;\n let condensateCorrected: DyeTarget | null = null;\n let front = 0;\n\n function makeTarget(w: number, h: number): DyeTarget {\n const texture = createTexture(gl, {\n width: w,\n height: h,\n internalFormat: format.internalFormat,\n type: format.type,\n // REPEAT: a domain with no border — see TextureOptions.wrap. All buffers carry the same\n // field, so the border needs to behave identically for each of them, or vapor would wrap\n // while condensate or a track at the same coordinate cuts off. The MacCormack limiter also\n // reads neighbors across this same border — it needs REPEAT exactly as much as transport does.\n wrap: gl.REPEAT,\n });\n const fbo = createFramebuffer(gl, texture);\n return { texture, fbo };\n }\n\n function clearTarget(target: DyeTarget, w: number, h: number): void {\n gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);\n gl.viewport(0, 0, w, h);\n gl.clearColor(0, 0, 0, 1);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n }\n\n function destroyPair(pair: DyePair | null): void {\n if (!pair) return;\n for (const t of pair) {\n gl.deleteTexture(t.texture);\n gl.deleteFramebuffer(t.fbo);\n }\n }\n\n function destroyTarget(target: DyeTarget | null): void {\n if (!target) return;\n gl.deleteTexture(target.texture);\n gl.deleteFramebuffer(target.fbo);\n }\n\n function destroyTargets(): void {\n destroyPair(vapor);\n destroyPair(condensate);\n destroyPair(track);\n destroyTarget(vaporForward);\n destroyTarget(vaporCorrected);\n destroyTarget(condensateForward);\n destroyTarget(condensateCorrected);\n vapor = null;\n condensate = null;\n track = null;\n vaporForward = null;\n vaporCorrected = null;\n condensateForward = null;\n condensateCorrected = null;\n }\n\n // Three spots at fixed, spread-out grid points — the \"initial spots\", and the entire water\n // supply: it has no other source (the sum of vapor and condensate is conserved). Condensate and\n // tracks seed empty: only background condensation stamps into condensate on the first step, and\n // only `emit` stamps into tracks.\n function seed(w: number, h: number): void {\n if (!vapor || !condensate || !track) return;\n gl.bindFramebuffer(gl.FRAMEBUFFER, vapor[front].fbo);\n gl.viewport(0, 0, w, h);\n gl.disable(gl.BLEND);\n gl.useProgram(seedProgram);\n setUniform(gl, seedLoc('u_resolution'), [w, h]);\n setUniform(gl, seedLoc('u_spot0'), [w * 0.28, h * 0.64]);\n setUniform(gl, seedLoc('u_spot1'), [w * 0.7, h * 0.32]);\n setUniform(gl, seedLoc('u_spot2'), [w * 0.48, h * 0.84]);\n setUniform(gl, seedLoc('u_spotRadius'), Math.max(w, h) * 0.18);\n drawFullscreenTriangle(gl);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n clearTarget(condensate[0], w, h);\n clearTarget(condensate[1], w, h);\n clearTarget(track[0], w, h);\n clearTarget(track[1], w, h);\n }\n\n /** Bilinear-resamples one buffer into a differently-sized target — see MEDIUM_RESAMPLE_SHADER. */\n function resample(src: DyeTarget, srcW: number, srcH: number, dst: DyeTarget, dstW: number, dstH: number): void {\n gl.bindFramebuffer(gl.FRAMEBUFFER, dst.fbo);\n gl.viewport(0, 0, dstW, dstH);\n gl.disable(gl.BLEND);\n gl.useProgram(resampleProgram);\n bindTextureAt(gl, 0, src.texture, resampleProgram, 'u_src');\n setUniform(gl, resampleLoc('u_srcSize'), [srcW, srcH]);\n setUniform(gl, resampleLoc('u_resolution'), [dstW, dstH]);\n drawFullscreenTriangle(gl);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n }\n\n // A resize used to destroy every target and reseed from scratch — a phone rotation or a window\n // resize wiped the vapor back to its three initial spots and blinked condensate/tracks out, the\n // flicker the design forbids. Instead: bilinear-resample the OLD front buffers (the only ones\n // that carry real state — see the comment on the MacCormack scratch buffers above) into NEW,\n // differently-sized targets, then delete the old ones. Seeding stays a first-call-only thing.\n function resizeGrid(w: number, h: number): void {\n if (!vapor || !condensate || !track) return; // narrows for TS; callers already checked\n const oldVapor = vapor;\n const oldCondensate = condensate;\n const oldTrack = track;\n const oldW = gridW;\n const oldH = gridH;\n\n const newVaporFront = makeTarget(w, h);\n const newCondensateFront = makeTarget(w, h);\n const newTrackFront = makeTarget(w, h);\n resample(oldVapor[front], oldW, oldH, newVaporFront, w, h);\n resample(oldCondensate[front], oldW, oldH, newCondensateFront, w, h);\n resample(oldTrack[front], oldW, oldH, newTrackFront, w, h);\n\n destroyTargets();\n gridW = w;\n gridH = h;\n vapor = [newVaporFront, makeTarget(w, h)];\n condensate = [newCondensateFront, makeTarget(w, h)];\n track = [newTrackFront, makeTarget(w, h)];\n vaporForward = makeTarget(w, h);\n vaporCorrected = makeTarget(w, h);\n condensateForward = makeTarget(w, h);\n condensateCorrected = makeTarget(w, h);\n front = 0;\n\n // Total water scales with cell count, not just density: a bilinear resample roughly preserves\n // the AVERAGE value per cell, so the SUM over every cell scales with how many cells there now\n // are. Rescaling the EXISTING target (rather than re-measuring the just-resampled buffers)\n // keeps the invariant exact even if the resample itself is slightly lossy at the edges — the\n // periodic renormalization below already self-corrects any residual, and it needs a real\n // target to correct TOWARD rather than one that already baked in this resize's own error.\n // `timeSinceRenorm`/`framesSinceRenorm` are left running: the interval they measure doesn't\n // care about grid size, only about how much correction accumulated frame to frame.\n const areaRatio = (w * h) / (oldW * oldH);\n waterTargetTotal *= areaRatio;\n }\n\n function ensureGrid(gridWidth: number, gridHeight: number): void {\n const w = Math.max(2, Math.round(gridWidth));\n const h = Math.max(2, Math.round(gridHeight));\n if (vapor && condensate && track && w === gridW && h === gridH) return;\n\n if (!vapor || !condensate || !track) {\n gridW = w;\n gridH = h;\n vapor = [makeTarget(w, h), makeTarget(w, h)];\n condensate = [makeTarget(w, h), makeTarget(w, h)];\n track = [makeTarget(w, h), makeTarget(w, h)];\n vaporForward = makeTarget(w, h);\n vaporCorrected = makeTarget(w, h);\n condensateForward = makeTarget(w, h);\n condensateCorrected = makeTarget(w, h);\n front = 0;\n seed(w, h);\n // The target water total — right after seeding, before the first step: this is \"the entire\n // supply\" (see the comment on `seed`), and every later renormalization checks against it.\n // Right after seeding condensate is empty, so the target equals the vapor sum — but it's\n // re-verified every time as vapor+condensate, not just vapor, in case the initial state ever\n // changes.\n waterTargetTotal = readTarget(vapor[front]) + readTarget(condensate[front]);\n waterScale = 1;\n timeSinceRenorm = 0;\n framesSinceRenorm = 0;\n return;\n }\n\n resizeGrid(w, h);\n }\n\n /** Shared velocity-field uniforms — one set for both phases' forward/correct passes and for\n * tracks (condensate adds its own gravity on top — see `u_settleSpeed` where it's declared).\n * The react passes don't mind them either: the extra `loc()` calls for curl uniforms that don't\n * exist there just return null, and `setUniform` silently skips them. */\n function bindVelocityUniforms(\n loc: (name: string) => WebGLUniformLocation | null,\n dt: number,\n phase: number,\n p: VireUIKitMediumParams,\n ): void {\n setUniform(gl, loc('u_dyeSize'), [gridW, gridH]);\n setUniform(gl, loc('u_resolution'), [gridW, gridH]);\n setUniform(gl, loc('u_dt'), dt);\n setUniform(gl, loc('u_phase'), phase);\n setUniform(gl, loc('u_curlFreq'), p.curlFreq);\n setUniform(gl, loc('u_advectSpeed'), p.advectSpeed);\n setUniform(gl, loc('u_turbulence'), p.turbulence);\n // Only the vapor forward/correct programs declare u_vaporDrift; elsewhere loc() returns null\n // and setUniform silently skips it, same as the curl uniforms already do for the react passes.\n setUniform(gl, loc('u_vaporDrift'), p.gravityVaporDrift);\n }\n\n // The field's phase — curl-noise's time axis, accumulated here in float64 rather than derived\n // from an ever-growing caller-supplied clock, and wrapped at MEDIUM_TIME_PERIOD (noise.ts's\n // MEDIUM_TIME_OCTAVES makes that wrap exact, not approximate). Sending a raw, ever-growing\n // `time * curlSpeed` product to the GPU as a float32 uniform eventually loses the bits that\n // place a point within its own lattice cell — after hours of playback the field would visibly\n // jitter in place. Wrapping the ACCUMULATOR keeps its magnitude bounded, and because the rate is\n // baked in incrementally rather than multiplied in fresh every frame, changing `curlSpeed` at\n // runtime changes the slope going forward instead of rescaling everything already accumulated —\n // no jump.\n let phase = 0;\n\n function wrapPhase(value: number): number {\n const wrapped = value % MEDIUM_TIME_PERIOD;\n return wrapped < 0 ? wrapped + MEDIUM_TIME_PERIOD : wrapped;\n }\n\n // A semi-Lagrangian gather over a non-uniform velocity has no obligation to conserve the sum by\n // itself, and neither does the vapor<->condensate exchange at non-integer rounding exponents\n // (not a boundary or step-size issue — at zero advection and zero exchange there's no loss at\n // all); rather than change the scheme, once a second the WATER sum (vapor+condensate) is checked\n // against its target and the correction is spread as an EQUAL multiplier over the frames of the\n // next interval (an Nth root — otherwise an N-fold ratio would overshoot the target instead of\n // reaching it).\n const WATER_RENORM_INTERVAL_S = 1;\n let waterTargetTotal = 0;\n let waterScale = 1;\n let timeSinceRenorm = 0;\n let framesSinceRenorm = 0;\n\n function step(dt: number, params: Partial<VireUIKitMediumParams> = {}): void {\n if (\n !vapor ||\n !condensate ||\n !track ||\n !vaporForward ||\n !vaporCorrected ||\n !condensateForward ||\n !condensateCorrected\n ) {\n return;\n }\n const p = { ...MEDIUM_DEFAULTS, ...params };\n // dt is clamped: a tab returning from the background would otherwise carry advection past the\n // grid's bounds in one jump, sweeping all the smoke to an edge in a single frame.\n const clampedDt = Math.min(Math.max(dt, 0), 1 / 15);\n // Advance and wrap the phase BEFORE using it this frame — see the comment on `phase` above.\n phase = wrapPhase(phase + clampedDt * p.curlSpeed);\n const back = front === 0 ? 1 : 0;\n const gridSize: readonly [number, number] = [gridW, gridH];\n\n // --- Transport: MacCormack splits each phase into forward/correct, both phases independently\n // (only the react pass below reads the partner, and only on already grid-aligned values). ---\n\n gl.viewport(0, 0, gridW, gridH);\n gl.disable(gl.BLEND);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, vaporForward.fbo);\n gl.useProgram(vaporForwardProgram);\n bindTextureAt(gl, 0, vapor[front].texture, vaporForwardProgram, 'u_dye');\n bindVelocityUniforms(vaporForwardLoc, clampedDt, phase, p);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, condensateForward.fbo);\n gl.useProgram(condensateForwardProgram);\n bindTextureAt(gl, 0, condensate[front].texture, condensateForwardProgram, 'u_dye');\n bindVelocityUniforms(condensateForwardLoc, clampedDt, phase, p);\n setUniform(gl, condensateForwardLoc('u_settleSpeed'), p.condensateSettleSpeed);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, vaporCorrected.fbo);\n gl.useProgram(vaporCorrectProgram);\n bindTextureAt(gl, 0, vapor[front].texture, vaporCorrectProgram, 'u_dye');\n bindTextureAt(gl, 1, vaporForward.texture, vaporCorrectProgram, 'u_forward');\n // bindTextureAt only binds the sampler, not its auto uniform size (the transpiler generates a\n // <name>Size for every `uniform shader`) — the second (cross-) sampler needs its own size set\n // separately, or it silently stays (0,0) and `.eval()` divides the coordinate by zero.\n setUniform(gl, vaporCorrectLoc('u_forwardSize'), gridSize);\n bindVelocityUniforms(vaporCorrectLoc, clampedDt, phase, p);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, condensateCorrected.fbo);\n gl.useProgram(condensateCorrectProgram);\n bindTextureAt(gl, 0, condensate[front].texture, condensateCorrectProgram, 'u_dye');\n bindTextureAt(gl, 1, condensateForward.texture, condensateCorrectProgram, 'u_forward');\n setUniform(gl, condensateCorrectLoc('u_forwardSize'), gridSize);\n bindVelocityUniforms(condensateCorrectLoc, clampedDt, phase, p);\n setUniform(gl, condensateCorrectLoc('u_settleSpeed'), p.condensateSettleSpeed);\n drawFullscreenTriangle(gl);\n\n // --- Reaction: vapor<->condensate exchange and droplet growth, on the already transported\n // (MacCormack) fields, with the partner read directly at its OWN point xy — both fields are\n // already grid-aligned. ---\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, vapor[back].fbo);\n gl.useProgram(vaporReactProgram);\n bindTextureAt(gl, 0, vaporCorrected.texture, vaporReactProgram, 'u_transported');\n bindTextureAt(gl, 1, condensateCorrected.texture, vaporReactProgram, 'u_condensateTransported');\n setUniform(gl, vaporReactLoc('u_transportedSize'), gridSize);\n setUniform(gl, vaporReactLoc('u_condensateTransportedSize'), gridSize);\n setUniform(gl, vaporReactLoc('u_resolution'), gridSize);\n setUniform(gl, vaporReactLoc('u_dt'), clampedDt);\n setUniform(gl, vaporReactLoc('u_condensationRate'), p.condensationRate);\n setUniform(gl, vaporReactLoc('u_condensationFloor'), p.condensationFloor);\n setUniform(gl, vaporReactLoc('u_evaporationRate'), p.evaporationRate);\n setUniform(gl, vaporReactLoc('u_totalScale'), waterScale);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, condensate[back].fbo);\n gl.useProgram(condensateReactProgram);\n bindTextureAt(gl, 0, condensateCorrected.texture, condensateReactProgram, 'u_transported');\n bindTextureAt(gl, 1, vaporCorrected.texture, condensateReactProgram, 'u_vaporTransported');\n // Growth reads the neighbors of its OWN cell xy in the ORIGINAL (pre-transport-this-frame)\n // condensate — see the comment in advect-shader.ts on MEDIUM_CONDENSATE_REACT_SHADER — so the\n // third sampler is the old front buffer.\n bindTextureAt(gl, 2, condensate[front].texture, condensateReactProgram, 'u_dye');\n setUniform(gl, condensateReactLoc('u_transportedSize'), gridSize);\n setUniform(gl, condensateReactLoc('u_vaporTransportedSize'), gridSize);\n setUniform(gl, condensateReactLoc('u_dyeSize'), gridSize);\n setUniform(gl, condensateReactLoc('u_resolution'), gridSize);\n setUniform(gl, condensateReactLoc('u_dt'), clampedDt);\n setUniform(gl, condensateReactLoc('u_condensationRate'), p.condensationRate);\n setUniform(gl, condensateReactLoc('u_condensationFloor'), p.condensationFloor);\n setUniform(gl, condensateReactLoc('u_evaporationRate'), p.evaporationRate);\n setUniform(gl, condensateReactLoc('u_spreadRate'), p.condensateSpreadRate);\n setUniform(gl, condensateReactLoc('u_totalScale'), waterScale);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, track[back].fbo);\n gl.useProgram(trackAdvectProgram);\n bindTextureAt(gl, 0, track[front].texture, trackAdvectProgram, 'u_dye');\n bindVelocityUniforms(trackAdvectLoc, clampedDt, phase, p);\n setUniform(gl, trackAdvectLoc('u_decay'), p.decay);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n front = back;\n framesSinceRenorm += 1;\n\n timeSinceRenorm += clampedDt;\n if (waterTargetTotal > 0 && timeSinceRenorm >= WATER_RENORM_INTERVAL_S && framesSinceRenorm > 0) {\n const current = readTarget(vapor[front]) + readTarget(condensate[front]);\n if (current > 1e-6) {\n // The Nth root: applied N times in a row (N frames of the past interval) has to PRODUCE\n // the ratio target/current, not apply that ratio in EACH frame — otherwise one second's\n // correction would fire N times and overshoot the target instead of reaching it.\n const totalRatio = waterTargetTotal / current;\n const perFrame = Math.pow(totalRatio, 1 / framesSinceRenorm);\n waterScale = Math.min(1.05, Math.max(0.95, perFrame));\n }\n timeSinceRenorm = 0;\n framesSinceRenorm = 0;\n }\n }\n\n // Stamping goes into the TRACK buffer (the one advection just wrote) via additive blending,\n // exactly as track stamping requires: not a separate pass, not its own buffer, not water.\n function emit(emissions: readonly VireUIKitMediumEmission[]): void {\n if (!track || emissions.length === 0) return;\n const minDim = Math.min(gridW, gridH);\n gl.bindFramebuffer(gl.FRAMEBUFFER, track[front].fbo);\n gl.viewport(0, 0, gridW, gridH);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE);\n gl.useProgram(emitProgram);\n // The flip-convention comment above doesn't apply here: the shader never reads its own\n // previous frame (no `.eval()`), so there's nothing to flip — but the transpiler always\n // requires u_resolution regardless (see `targets/glsl.ts`: the entry coordinate goes through\n // it unconditionally).\n setUniform(gl, emitLoc('u_resolution'), [gridW, gridH]);\n for (const e of emissions) {\n const source: [number, number] = [e.source[0] * gridW, e.source[1] * gridH];\n const length = e.lengthFrac * minDim;\n const head: [number, number] = [\n source[0] + Math.cos(e.angle) * length,\n source[1] + Math.sin(e.angle) * length,\n ];\n setUniform(gl, emitLoc('u_source'), source);\n setUniform(gl, emitLoc('u_head'), head);\n setUniform(gl, emitLoc('u_headWidth'), e.headWidthFrac * minDim);\n setUniform(gl, emitLoc('u_tailWidth'), e.tailWidthFrac * minDim);\n setUniform(gl, emitLoc('u_raggedAmp'), e.raggedFrac * minDim);\n setUniform(gl, emitLoc('u_raggedFreq'), e.raggedFreq);\n setUniform(gl, emitLoc('u_settle'), e.settleFrac * minDim);\n setUniform(gl, emitLoc('u_tailDim'), e.tailDim);\n setUniform(gl, emitLoc('u_intensity'), e.intensity);\n setUniform(gl, emitLoc('u_seed'), e.seed);\n setUniform(gl, emitLoc('u_channelMask'), e.channel);\n drawFullscreenTriangle(gl);\n }\n gl.disable(gl.BLEND);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n }\n\n function composite(\n contentWidth: number,\n contentHeight: number,\n params: Partial<VireUIKitMediumParams> = {},\n ): void {\n if (!vapor || !condensate || !track) return;\n const p = { ...MEDIUM_DEFAULTS, ...params };\n gl.viewport(0, 0, contentWidth, contentHeight);\n gl.disable(gl.BLEND);\n gl.useProgram(compositeProgram);\n bindTextureAt(gl, 0, vapor[front].texture, compositeProgram, 'u_vapor');\n bindTextureAt(gl, 1, condensate[front].texture, compositeProgram, 'u_condensate');\n bindTextureAt(gl, 2, track[front].texture, compositeProgram, 'u_track');\n setUniform(gl, compositeLoc('u_vaporSize'), [gridW, gridH]);\n setUniform(gl, compositeLoc('u_condensateSize'), [gridW, gridH]);\n setUniform(gl, compositeLoc('u_trackSize'), [gridW, gridH]);\n setUniform(gl, compositeLoc('u_resolution'), [contentWidth, contentHeight]);\n setUniform(gl, compositeLoc('u_dyeScale'), [gridW / contentWidth, gridH / contentHeight]);\n // The palette's forward direction (sRGB → Oklab) — here, once per frame (frame constants),\n // not in the shader per pixel (see the comment in composite-shader.ts).\n setUniform(gl, compositeLoc('u_lab0'), srgbToOklab(p.channelColors[0]));\n setUniform(gl, compositeLoc('u_lab1'), srgbToOklab(p.channelColors[1]));\n setUniform(gl, compositeLoc('u_lab2'), srgbToOklab(p.channelColors[2]));\n setUniform(gl, compositeLoc('u_labBg'), srgbToOklab(p.baseColor));\n setUniform(gl, compositeLoc('u_labCondensate'), srgbToOklab(p.condensateColor));\n setUniform(gl, compositeLoc('u_baseWeight'), p.baseWeight);\n setUniform(gl, compositeLoc('u_condensateTint'), p.condensateTint);\n setUniform(gl, compositeLoc('u_condensateGain'), p.condensateGain);\n setUniform(gl, compositeLoc('u_farScale'), p.depthFarScale);\n // Resolved against the GRID's own size (baseSrc is already grid-px), not content size — a\n // fixed fraction of a wrapping axis, not a fixed pixel count (see depthFarOffsetPx's own\n // comment for why a fixed grid-px value doesn't survive every grid size).\n setUniform(gl, compositeLoc('u_farOffset'), depthFarOffsetPx(p.depthFarOffsetFrac, gridW, gridH));\n setUniform(gl, compositeLoc('u_farBlurRadius'), p.depthFarBlurRadius);\n setUniform(gl, compositeLoc('u_farWeight'), p.depthFarWeight);\n setUniform(gl, compositeLoc('u_gravityBoost'), p.gravityBottomBoost);\n setUniform(gl, compositeLoc('u_gravityBoostStart'), p.gravityBottomBoostStart);\n drawFullscreenTriangle(gl);\n }\n\n function readTarget(target: DyeTarget): number {\n gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);\n let sum = 0;\n if (format.type === gl.HALF_FLOAT) {\n const buf = new Float32Array(gridW * gridH * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.FLOAT, buf);\n for (let i = 0; i < buf.length; i += 4) sum += buf[i] + buf[i + 1] + buf[i + 2];\n } else {\n const buf = new Uint8Array(gridW * gridH * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.UNSIGNED_BYTE, buf);\n for (let i = 0; i < buf.length; i += 4) sum += (buf[i] + buf[i + 1] + buf[i + 2]) / 255;\n }\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return sum;\n }\n\n function readTotals(): { vapor: number; condensate: number; track: number } | null {\n if (!vapor || !condensate || !track) return null;\n return {\n vapor: readTarget(vapor[front]),\n condensate: readTarget(condensate[front]),\n track: readTarget(track[front]),\n };\n }\n\n // Three numbers per cell (r/g/b of the three vapor species), not a sum: the structure gate\n // (medium-structure-gate.mjs) measures contrast BETWEEN species — a summed density would lose it.\n function readVaporGrid(): { cols: number; rows: number; data: number[] } | null {\n if (!vapor) return null;\n gl.bindFramebuffer(gl.FRAMEBUFFER, vapor[front].fbo);\n const cells = gridW * gridH;\n const data = new Array(cells * 3);\n if (format.type === gl.HALF_FLOAT) {\n const buf = new Float32Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.FLOAT, buf);\n for (let i = 0; i < cells; i += 1) {\n data[i * 3] = buf[i * 4];\n data[i * 3 + 1] = buf[i * 4 + 1];\n data[i * 3 + 2] = buf[i * 4 + 2];\n }\n } else {\n const buf = new Uint8Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.UNSIGNED_BYTE, buf);\n for (let i = 0; i < cells; i += 1) {\n data[i * 3] = buf[i * 4] / 255;\n data[i * 3 + 1] = buf[i * 4 + 1] / 255;\n data[i * 3 + 2] = buf[i * 4 + 2] / 255;\n }\n }\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return { cols: gridW, rows: gridH, data };\n }\n\n // One number per cell, not three: condensate has no color of its own (density lives in .r,\n // g/b unused — see MEDIUM_CONDENSATE_REACT_SHADER). A gravity gate reads THIS, row-weighting the\n // density to find where the mass sits, without decoding an RGB triple that's two-thirds padding.\n function readCondensateGrid(): { cols: number; rows: number; data: number[] } | null {\n if (!condensate) return null;\n gl.bindFramebuffer(gl.FRAMEBUFFER, condensate[front].fbo);\n const cells = gridW * gridH;\n const data = new Array(cells);\n if (format.type === gl.HALF_FLOAT) {\n const buf = new Float32Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.FLOAT, buf);\n for (let i = 0; i < cells; i += 1) data[i] = buf[i * 4];\n } else {\n const buf = new Uint8Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.UNSIGNED_BYTE, buf);\n for (let i = 0; i < cells; i += 1) data[i] = buf[i * 4] / 255;\n }\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return { cols: gridW, rows: gridH, data };\n }\n\n function destroy(): void {\n destroyTargets();\n gl.deleteProgram(vaporForwardProgram);\n gl.deleteProgram(vaporCorrectProgram);\n gl.deleteProgram(vaporReactProgram);\n gl.deleteProgram(condensateForwardProgram);\n gl.deleteProgram(condensateCorrectProgram);\n gl.deleteProgram(condensateReactProgram);\n gl.deleteProgram(trackAdvectProgram);\n gl.deleteProgram(compositeProgram);\n gl.deleteProgram(seedProgram);\n gl.deleteProgram(emitProgram);\n gl.deleteProgram(resampleProgram);\n }\n\n return { ensureGrid, step, emit, composite, readTotals, readVaporGrid, readCondensateGrid, destroy };\n}\n","import {\n FULLSCREEN_TRIANGLE_VERTEX_SOURCE,\n type VireGlassBackdropPass,\n} from 'vireglass/web';\nimport type { VireUIKitMediumEmission, VireUIKitMediumParams } from '../medium';\nimport { createMediumRuntime, type MediumRuntime } from './medium';\n\n/** The simulation grid — its own resolution, coarser than the frame. `dt` is the caller's clock:\n * pausing on `prefers-reduced-motion` and testability both stay on its side. There is no `time`\n * field — the field's phase is the runtime's own accumulated state (see `u_phase` in\n * `web/medium.ts`), not something the caller has to track across an arbitrarily long session. */\nexport type MediumFrame = {\n gridWidth: number;\n gridHeight: number;\n dt: number;\n params?: Partial<VireUIKitMediumParams>;\n /** Tracks stamped this frame; `createMediumDynamics` schedules them. */\n emissions?: readonly VireUIKitMediumEmission[];\n};\n\nexport type MediumBackdrop = {\n /** The VireGlass renderer's backdrop for this frame: steps the medium and composites it into\n * the lens's texture. */\n pass(frame: MediumFrame): VireGlassBackdropPass;\n /** Measurement hooks for balance and structure gates; `null` until the medium has rendered once. */\n readTotals(): { vapor: number; condensate: number; track: number } | null;\n readVaporGrid(): { cols: number; rows: number; data: number[] } | null;\n readCondensateGrid(): { cols: number; rows: number; data: number[] } | null;\n destroy(): void;\n};\n\nexport function createMediumBackdrop(): MediumBackdrop {\n let runtime: MediumRuntime | null = null;\n let owner: WebGL2RenderingContext | null = null;\n\n return {\n pass: (frame) => (gl, target) => {\n if (owner && owner !== gl) throw new Error('vireuikit: the medium is already bound to another WebGL context');\n owner = gl;\n runtime ??= createMediumRuntime(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE);\n runtime.ensureGrid(frame.gridWidth, frame.gridHeight);\n runtime.step(frame.dt, frame.params);\n runtime.emit(frame.emissions ?? []);\n gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);\n runtime.composite(target.width, target.height, frame.params);\n },\n readTotals: () => runtime?.readTotals() ?? null,\n readVaporGrid: () => runtime?.readVaporGrid() ?? null,\n readCondensateGrid: () => runtime?.readCondensateGrid() ?? null,\n destroy: () => {\n runtime?.destroy();\n runtime = null;\n owner = null;\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/medium/noise.ts","../../src/medium/oklab.ts","../../src/medium/species.ts","../../src/medium/params.ts","../../src/medium/seed-shader.ts","../../src/medium/resample-shader.ts","../../src/medium/advect-shader.ts","../../src/medium/composite-shader.ts","../../src/medium/emit-shader.ts","../../src/medium/shadow-shader.ts","../../src/web/track-layer.ts","../../src/web/track-layer-gl.ts","../../src/web/medium.ts","../../src/web/medium-backdrop.ts"],"names":["locationCache","setUniform","bindTextureAt","createProgram","toGLSL","createTexture","createFramebuffer","drawFullscreenTriangle","phase","FULLSCREEN_TRIANGLE_VERTEX_SOURCE"],"mappings":"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","file":"index.cjs","sourcesContent":["// Hash + value-noise — the shared noise primitive (track emission also uses it for the tail's\n// ragged spread, `emit-shader.ts`), kept apart from what's curl-specific (the potential, the curl).\nexport const VG_VALUE_NOISE = `\nfloat vgHash21(float2 p) {\n float h = dot(p, float2(127.1, 311.7));\n return fract(sin(h) * 43758.5453123);\n}\n\nfloat vgValueNoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float a = vgHash21(i);\n float b = vgHash21(i + float2(1.0, 0.0));\n float c = vgHash21(i + float2(0.0, 1.0));\n float d = vgHash21(i + float2(1.0, 1.0));\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y) * 2.0 - 1.0;\n}\n`;\n\n/** GLSL float literals need a decimal point (`600` won't compile as a float) — same convention as\n * oklab.ts's `glsl()`. */\nconst glslFloat = (v: number): string => (Number.isInteger(v) ? v.toFixed(1) : String(v));\n\n/** The field's phase wraps every this many PHASE units (curlSpeed·seconds), NOT seconds of real\n * time: `web/medium.ts`'s CPU-side accumulator does `phase += dt * curlSpeed`, so the wall-clock\n * wrap period is `MEDIUM_TIME_PERIOD / curlSpeed` — about 2.3h (8333s) at the shipped default\n * (`curlSpeed = 0.12`), verified against that default in `medium-noise-periodicity.test.ts` so\n * this comment can't silently drift out of sync with it. Chosen so every octave rate below times\n * this period lands on an integer — that integer is the z-lattice period `vgValueNoise3Periodic`\n * wraps on, which is what makes the wrap exact rather than merely small. */\nexport const MEDIUM_TIME_PERIOD = 1000;\n\ntype MediumTimeOctave = {\n /** The z-axis (phase) rate this octave's `vgValueNoise3Periodic` call advances at. */\n rate: number;\n /** Fixed phase offset — decorrelates the octaves (see the comment on `vgPotential` below) and\n * doesn't affect periodicity: shifting a periodic function along its own axis leaves its period\n * unchanged. */\n offset: number;\n /** Spatial frequency multiplier on `p`. */\n scale: number;\n};\n\n/** `vgPotential`'s four fbm octaves — the single source both the GLSL text below and the JS port\n * (`vgPotentialJS`, for tests) derive their numbers from, so the two can never drift apart. */\nexport const MEDIUM_TIME_OCTAVES: readonly MediumTimeOctave[] = [\n { rate: 0.6, offset: 0, scale: 1 },\n { rate: 1.7, offset: 11, scale: 2.03 },\n { rate: 0.9, offset: 37, scale: 4.11 },\n { rate: 0.15, offset: 5, scale: 0.35 },\n];\n\n/** The z-lattice period for one octave's rate — `rate * MEDIUM_TIME_PERIOD`, which has to land on\n * an integer or the phase wrap would jump instead of being seamless (see `MEDIUM_TIME_OCTAVES`).\n * Throws at module load rather than rendering a shader that would silently jitter on every wrap. */\nexport function mediumOctavePeriod(rate: number): number {\n const period = rate * MEDIUM_TIME_PERIOD;\n if (!Number.isInteger(period)) {\n throw new Error(\n `medium noise: octave rate ${rate} * MEDIUM_TIME_PERIOD ${MEDIUM_TIME_PERIOD} = ${period}, not an integer — the phase wrap would not be seamless`,\n );\n }\n return period;\n}\n\nconst glslOctave = (o: MediumTimeOctave) => ({\n rate: glslFloat(o.rate),\n offset: glslFloat(o.offset),\n scale: glslFloat(o.scale),\n period: glslFloat(mediumOctavePeriod(o.rate)),\n});\nconst [VG_OCT0, VG_OCT1, VG_OCT2, VG_OCT3] = MEDIUM_TIME_OCTAVES.map(glslOctave);\n\n// Curl-noise velocity field: the curl of a SCALAR potential is divergence-free by construction,\n// so no Poisson solver is needed here — unlike Stable Fluids, where incompressibility has to be\n// projected explicitly.\nexport const VG_CURL_NOISE = `\n${VG_VALUE_NOISE}\n\nfloat vgHash31(float3 p) {\n float h = dot(p, float3(127.1, 311.7, 74.7));\n return fract(sin(h) * 43758.5453123);\n}\n\n// 3D value-noise: time is a third grid axis, not a coordinate shift. A shift would just SLIDE the\n// pattern (a \"traveling\" look, when motion is meant to read as random), while trilinear\n// interpolation over t CHANGES the pattern itself between grid nodes — nearby moments look alike,\n// distant ones don't, and no repeat ever shows up.\nfloat vgValueNoise3(float3 p) {\n float3 i = floor(p);\n float3 f = fract(p);\n float3 u = f * f * (3.0 - 2.0 * f);\n float n000 = vgHash31(i);\n float n100 = vgHash31(i + float3(1.0, 0.0, 0.0));\n float n010 = vgHash31(i + float3(0.0, 1.0, 0.0));\n float n110 = vgHash31(i + float3(1.0, 1.0, 0.0));\n float n001 = vgHash31(i + float3(0.0, 0.0, 1.0));\n float n101 = vgHash31(i + float3(1.0, 0.0, 1.0));\n float n011 = vgHash31(i + float3(0.0, 1.0, 1.0));\n float n111 = vgHash31(i + float3(1.0, 1.0, 1.0));\n float nx00 = mix(n000, n100, u.x);\n float nx10 = mix(n010, n110, u.x);\n float nx01 = mix(n001, n101, u.x);\n float nx11 = mix(n011, n111, u.x);\n float nxy0 = mix(nx00, nx10, u.y);\n float nxy1 = mix(nx01, nx11, u.y);\n return mix(nxy0, nxy1, u.z) * 2.0 - 1.0;\n}\n\n// Same as vgValueNoise3, but the z axis wraps at \\`period\\`: BOTH z-lattice neighbors (i.z and\n// i.z + 1.0) are folded into [0, period) before hashing, so this is EXACTLY the same function of\n// z and of z + period — not just close. vgPotential below calls this once per octave, each with\n// the period matching its own rate (MEDIUM_TIME_OCTAVES), so a phase that wraps at\n// MEDIUM_TIME_PERIOD reproduces the identical field instead of jumping to a new one.\nfloat vgValueNoise3Periodic(float3 p, float period) {\n float3 i = floor(p);\n float3 f = fract(p);\n float3 u = f * f * (3.0 - 2.0 * f);\n float z0 = mod(i.z, period);\n float z1 = mod(i.z + 1.0, period);\n float n000 = vgHash31(float3(i.xy, z0));\n float n100 = vgHash31(float3(i.xy + float2(1.0, 0.0), z0));\n float n010 = vgHash31(float3(i.xy + float2(0.0, 1.0), z0));\n float n110 = vgHash31(float3(i.xy + float2(1.0, 1.0), z0));\n float n001 = vgHash31(float3(i.xy, z1));\n float n101 = vgHash31(float3(i.xy + float2(1.0, 0.0), z1));\n float n011 = vgHash31(float3(i.xy + float2(0.0, 1.0), z1));\n float n111 = vgHash31(float3(i.xy + float2(1.0, 1.0), z1));\n float nx00 = mix(n000, n100, u.x);\n float nx10 = mix(n010, n110, u.x);\n float nx01 = mix(n001, n101, u.x);\n float nx11 = mix(n011, n111, u.x);\n float nxy0 = mix(nx00, nx10, u.y);\n float nxy1 = mix(nx01, nx11, u.y);\n return mix(nxy0, nxy1, u.z) * 2.0 - 1.0;\n}\n\n// The potential is an fbm of three octaves, each evolving over the field's PHASE at its own speed\n// (unrelated to the others, plus a phase offset): no single phase shift can align them all at\n// once, so no traveling pattern remains. Each octave wraps its own z-lattice at the period that\n// matches its rate (see MEDIUM_TIME_OCTAVES/vgValueNoise3Periodic), so\n// vgPotential(p, phase) === vgPotential(p, phase + ${glslFloat(MEDIUM_TIME_PERIOD)}) exactly — a\n// CPU-side phase accumulator can wrap at that period with no visible jump (see web/medium.ts). The\n// large-scale factor is a slow non-uniformity layered over the sum — without it, a field that's\n// uniform by statistics still reads as a regular cell pattern.\nfloat vgPotential(float2 p, float phase) {\n float v = vgValueNoise3Periodic(float3(p * ${VG_OCT0.scale}, phase * ${VG_OCT0.rate} + ${VG_OCT0.offset}), ${VG_OCT0.period}) * 0.5;\n v += vgValueNoise3Periodic(float3(p * ${VG_OCT1.scale}, phase * ${VG_OCT1.rate} + ${VG_OCT1.offset}), ${VG_OCT1.period}) * 0.25;\n v += vgValueNoise3Periodic(float3(p * ${VG_OCT2.scale}, phase * ${VG_OCT2.rate} + ${VG_OCT2.offset}), ${VG_OCT2.period}) * 0.125;\n float macro = vgValueNoise3Periodic(float3(p * ${VG_OCT3.scale}, phase * ${VG_OCT3.rate} + ${VG_OCT3.offset}), ${VG_OCT3.period});\n return v * (0.7 + 0.3 * macro);\n}\n\n// Velocity = (dPsi/dy, -dPsi/dx) — the curl of a scalar field, solenoidal for any Psi. Central\n// differences instead of an analytic derivative: the potential is a sum of three value-noise\n// calls, and there is no cheaper way to differentiate it by hand than four extra samples.\nfloat2 vgCurlVelocity(float2 p, float phase) {\n float e = 0.6;\n float px1 = vgPotential(p + float2(e, 0.0), phase);\n float px0 = vgPotential(p - float2(e, 0.0), phase);\n float py1 = vgPotential(p + float2(0.0, e), phase);\n float py0 = vgPotential(p - float2(0.0, e), phase);\n return float2(py1 - py0, -(px1 - px0)) / (2.0 * e);\n}\n\n// vgPotential above is periodic in TIME only: the simulation domain is a torus (gl.REPEAT), but\n// the lattice itself never wraps in x/y, so velocity jumps at the domain seam and dye on either\n// side of it is transported differently — a straight seam, made worse wherever something re-reads\n// the field at a different scale (the far depth plane's tile boundary). vgValueNoise3Seamless\n// wraps ALL THREE lattice axes: xyPeriod/zPeriod fold BOTH neighbors of a cell into their own\n// period before hashing, so the field is EXACTLY the same one period over, in space exactly as it\n// already was in time.\n//\n// The period has to be an integer number of lattice cells, or the wrap would jump instead of\n// closing seamlessly (the same requirement MEDIUM_TIME_OCTAVES/mediumOctavePeriod enforces for\n// time) — but unlike time, the spatial period depends on the GRID SIZE, which this shader has no\n// notion of. So xyPeriod travels together with a per-octave spatial frequency the CPU computes to\n// make gridSize * frequency land on that exact integer (see computeMediumSpatialPeriods below);\n// both arrive together as one uniform per octave (u_spatialN.xy = frequency, .zw = period),\n// recomputed by web/medium.ts whenever the grid resizes.\nfloat vgValueNoise3Seamless(float3 p, float2 xyPeriod, float zPeriod) {\n float3 i = floor(p);\n float3 f = fract(p);\n float3 u = f * f * (3.0 - 2.0 * f);\n float x0 = mod(i.x, xyPeriod.x);\n float x1 = mod(i.x + 1.0, xyPeriod.x);\n float y0 = mod(i.y, xyPeriod.y);\n float y1 = mod(i.y + 1.0, xyPeriod.y);\n float z0 = mod(i.z, zPeriod);\n float z1 = mod(i.z + 1.0, zPeriod);\n float n000 = vgHash31(float3(x0, y0, z0));\n float n100 = vgHash31(float3(x1, y0, z0));\n float n010 = vgHash31(float3(x0, y1, z0));\n float n110 = vgHash31(float3(x1, y1, z0));\n float n001 = vgHash31(float3(x0, y0, z1));\n float n101 = vgHash31(float3(x1, y0, z1));\n float n011 = vgHash31(float3(x0, y1, z1));\n float n111 = vgHash31(float3(x1, y1, z1));\n float nx00 = mix(n000, n100, u.x);\n float nx10 = mix(n010, n110, u.x);\n float nx01 = mix(n001, n101, u.x);\n float nx11 = mix(n011, n111, u.x);\n float nxy0 = mix(nx00, nx10, u.y);\n float nxy1 = mix(nx01, nx11, u.y);\n return mix(nxy0, nxy1, u.z) * 2.0 - 1.0;\n}\n\n// Same four-octave fbm as vgPotential, but every octave reads the seamless lattice above at its\n// OWN (frequency, period) pair — sN.xy replaces that octave's compile-time \\`scale\\` constant (it\n// has to be a uniform now: the adjustment depends on the live grid size), sN.zw is the matching\n// integer period. \\`p\\` is still curlFreq-scaled exactly as vgPotential's caller scales it — only\n// the per-octave multiplier changed from a constant to a grid-dependent uniform, so the finite\n// difference in vgCurlVelocitySeamless below needs no epsilon change of its own.\nfloat vgPotentialSeamless(float2 p, float phase, float4 s0, float4 s1, float4 s2, float4 s3) {\n float v = vgValueNoise3Seamless(float3(p * s0.xy, phase * ${VG_OCT0.rate} + ${VG_OCT0.offset}), s0.zw, ${VG_OCT0.period}) * 0.5;\n v += vgValueNoise3Seamless(float3(p * s1.xy, phase * ${VG_OCT1.rate} + ${VG_OCT1.offset}), s1.zw, ${VG_OCT1.period}) * 0.25;\n v += vgValueNoise3Seamless(float3(p * s2.xy, phase * ${VG_OCT2.rate} + ${VG_OCT2.offset}), s2.zw, ${VG_OCT2.period}) * 0.125;\n float macro = vgValueNoise3Seamless(float3(p * s3.xy, phase * ${VG_OCT3.rate} + ${VG_OCT3.offset}), s3.zw, ${VG_OCT3.period});\n return v * (0.7 + 0.3 * macro);\n}\n\nfloat2 vgCurlVelocitySeamless(float2 p, float phase, float4 s0, float4 s1, float4 s2, float4 s3) {\n float e = 0.6;\n float px1 = vgPotentialSeamless(p + float2(e, 0.0), phase, s0, s1, s2, s3);\n float px0 = vgPotentialSeamless(p - float2(e, 0.0), phase, s0, s1, s2, s3);\n float py1 = vgPotentialSeamless(p + float2(0.0, e), phase, s0, s1, s2, s3);\n float py0 = vgPotentialSeamless(p - float2(0.0, e), phase, s0, s1, s2, s3);\n return float2(py1 - py0, -(px1 - px0)) / (2.0 * e);\n}\n`;\n\n// --- JS port of the periodic potential, for tests that can't spin up a WebGL2 context ------------\n//\n// The exact same arithmetic as vgHash31/vgValueNoise3Periodic/vgPotential above, so a vitest can\n// prove periodicity and continuity across the phase wrap without a GPU. Kept next to the shader\n// text as the single source of truth for what \"the same constants\" means: both are generated from\n// MEDIUM_TIME_OCTAVES, and `medium-noise-periodicity.test.ts` checks the shader string itself\n// contains these numbers rather than trusting that the two never drifted apart.\n\nfunction hash31JS(x: number, y: number, z: number): number {\n const h = x * 127.1 + y * 311.7 + z * 74.7;\n const s = Math.sin(h) * 43758.5453123;\n return s - Math.floor(s);\n}\n\nconst smoothstep3JS = (f: number): number => f * f * (3 - 2 * f);\n\n/** GLSL's `mod(x, y)` for floats is `x - y * floor(x / y)` — always in `[0, y)`, unlike JS's `%`,\n * which keeps the sign of `x`. Matching it exactly here is the point: this IS the wrap the shader\n * performs. */\nconst glslMod = (x: number, y: number): number => x - y * Math.floor(x / y);\n\n/** Pure-JS port of `vgValueNoise3Periodic`. */\nexport function vgValueNoise3PeriodicJS(px: number, py: number, pz: number, period: number): number {\n const ix = Math.floor(px);\n const iy = Math.floor(py);\n const iz = Math.floor(pz);\n const ux = smoothstep3JS(px - ix);\n const uy = smoothstep3JS(py - iy);\n const uz = smoothstep3JS(pz - iz);\n const z0 = glslMod(iz, period);\n const z1 = glslMod(iz + 1, period);\n const n000 = hash31JS(ix, iy, z0);\n const n100 = hash31JS(ix + 1, iy, z0);\n const n010 = hash31JS(ix, iy + 1, z0);\n const n110 = hash31JS(ix + 1, iy + 1, z0);\n const n001 = hash31JS(ix, iy, z1);\n const n101 = hash31JS(ix + 1, iy, z1);\n const n011 = hash31JS(ix, iy + 1, z1);\n const n111 = hash31JS(ix + 1, iy + 1, z1);\n const nx00 = n000 + (n100 - n000) * ux;\n const nx10 = n010 + (n110 - n010) * ux;\n const nx01 = n001 + (n101 - n001) * ux;\n const nx11 = n011 + (n111 - n011) * ux;\n const nxy0 = nx00 + (nx10 - nx00) * uy;\n const nxy1 = nx01 + (nx11 - nx01) * uy;\n return (nxy0 + (nxy1 - nxy0) * uz) * 2 - 1;\n}\n\n/** Pure-JS port of `vgPotential`, built from the same `MEDIUM_TIME_OCTAVES` table the shader text\n * above is generated from. */\nexport function vgPotentialJS(px: number, py: number, phase: number): number {\n const [o0, o1, o2, o3] = MEDIUM_TIME_OCTAVES;\n let v =\n vgValueNoise3PeriodicJS(px * o0.scale, py * o0.scale, phase * o0.rate + o0.offset, mediumOctavePeriod(o0.rate)) *\n 0.5;\n v +=\n vgValueNoise3PeriodicJS(px * o1.scale, py * o1.scale, phase * o1.rate + o1.offset, mediumOctavePeriod(o1.rate)) *\n 0.25;\n v +=\n vgValueNoise3PeriodicJS(px * o2.scale, py * o2.scale, phase * o2.rate + o2.offset, mediumOctavePeriod(o2.rate)) *\n 0.125;\n const macro = vgValueNoise3PeriodicJS(\n px * o3.scale,\n py * o3.scale,\n phase * o3.rate + o3.offset,\n mediumOctavePeriod(o3.rate),\n );\n return v * (0.7 + 0.3 * macro);\n}\n\n// --- JS port of the seamless (space + time periodic) potential, and the CPU-side period math -----\n\n/** Pure-JS port of `vgValueNoise3Seamless`. */\nexport function vgValueNoise3SeamlessJS(\n px: number,\n py: number,\n pz: number,\n xPeriod: number,\n yPeriod: number,\n zPeriod: number,\n): number {\n const ix = Math.floor(px);\n const iy = Math.floor(py);\n const iz = Math.floor(pz);\n const ux = smoothstep3JS(px - ix);\n const uy = smoothstep3JS(py - iy);\n const uz = smoothstep3JS(pz - iz);\n const x0 = glslMod(ix, xPeriod);\n const x1 = glslMod(ix + 1, xPeriod);\n const y0 = glslMod(iy, yPeriod);\n const y1 = glslMod(iy + 1, yPeriod);\n const z0 = glslMod(iz, zPeriod);\n const z1 = glslMod(iz + 1, zPeriod);\n const n000 = hash31JS(x0, y0, z0);\n const n100 = hash31JS(x1, y0, z0);\n const n010 = hash31JS(x0, y1, z0);\n const n110 = hash31JS(x1, y1, z0);\n const n001 = hash31JS(x0, y0, z1);\n const n101 = hash31JS(x1, y0, z1);\n const n011 = hash31JS(x0, y1, z1);\n const n111 = hash31JS(x1, y1, z1);\n const nx00 = n000 + (n100 - n000) * ux;\n const nx10 = n010 + (n110 - n010) * ux;\n const nx01 = n001 + (n101 - n001) * ux;\n const nx11 = n011 + (n111 - n011) * ux;\n const nxy0 = nx00 + (nx10 - nx00) * uy;\n const nxy1 = nx01 + (nx11 - nx01) * uy;\n return (nxy0 + (nxy1 - nxy0) * uz) * 2 - 1;\n}\n\n/** One octave's seamless spatial setup: `freq` is what replaces that octave's compile-time\n * `scale` constant, adjusted off `curlFreq * scale` to the nearest value making `gridSize * freq`\n * land on the integer `period` — the same requirement `mediumOctavePeriod` enforces on the time\n * axis, just recomputed per grid size instead of fixed at module load. */\nexport type MediumSpatialOctave = {\n freqX: number;\n freqY: number;\n periodX: number;\n periodY: number;\n};\n\n/** The macro octave (`MEDIUM_TIME_OCTAVES[3]`, scale 0.35 — \"a slow non-uniformity layered over\n * the sum\") would round to a period of 0 or 1 lattice cell at this package's default grid\n * (74x37 at the shipped `MEDIUM_DEFAULT_CELL_PX`): a period of 1 collapses the octave to a\n * SPATIAL CONSTANT (every integer x hashes to the same node), trading one axis-aligned artifact\n * for another — a flat band instead of a seam. Flooring keeps at least one full cycle across the\n * grid, at the cost of the octave being coarser than its own frequency would imply at very small\n * grids. */\nconst MIN_SPATIAL_PERIOD = 2;\n\n/**\n * Computes, for every entry of `MEDIUM_TIME_OCTAVES`, the seamless (frequency, period) pair the\n * shader's `u_spatialN` uniforms need — pure and exported so the wrap-safety it exists for is\n * directly testable without a GPU (`medium-noise-spatial-periodicity.test.ts`), the same reasoning\n * as `depthFarOffsetPx`. Depends on the LIVE grid size (unlike the time octaves' periods, fixed at\n * module load): `web/medium.ts` recomputes this every frame from the current grid dimensions, since\n * a resize changes what \"an integer number of cells across the grid\" even means.\n *\n * `rawX`/`rawY` (the UNROUNDED cell counts, `gridSize * curlFreq * scale`) keep the grid's own\n * aspect ratio exactly — at this package's default 74x37 (2:1), `rawY` is always `rawX / 2`. Simply\n * flooring EACH axis's independently-rounded count at `MIN_SPATIAL_PERIOD` breaks that: the macro\n * octave's raw counts (1.17, 0.58) both round to 1 and both get floored to the SAME value (2), so\n * `freqY` (divided by the smaller `gridHeight`) comes out exactly 2x `freqX` — a genuinely\n * ANISOTROPIC lattice, not a seamless-but-otherwise-unchanged one. Measured directly against\n * `check-medium.mjs`'s isotropy gate: this asymmetry, not the seam it replaced, was the dominant\n * source of axis-aligned gradient energy — worse than the pre-fix (non-periodic, but isotropic)\n * noise. The fix scales BOTH raw counts by the SAME factor (enough to bring the smaller one up to\n * the floor) before rounding, so a floored octave stays isotropic (both axes land on the same\n * frequency, up to independent rounding of the now-larger counts) instead of stretching one axis.\n */\nexport function computeMediumSpatialPeriods(\n gridWidth: number,\n gridHeight: number,\n curlFreq: number,\n): readonly MediumSpatialOctave[] {\n // A zero frequency would divide by zero below and hand the shader Infinity.\n const freq = Math.max(curlFreq, 1e-6);\n return MEDIUM_TIME_OCTAVES.map((o) => {\n const rawX = gridWidth * freq * o.scale;\n const rawY = gridHeight * freq * o.scale;\n const minRaw = Math.min(rawX, rawY);\n const scaleUp = minRaw > 0 && minRaw < MIN_SPATIAL_PERIOD ? MIN_SPATIAL_PERIOD / minRaw : 1;\n const periodX = Math.max(MIN_SPATIAL_PERIOD, Math.round(rawX * scaleUp));\n const periodY = Math.max(MIN_SPATIAL_PERIOD, Math.round(rawY * scaleUp));\n return {\n freqX: periodX / (gridWidth * freq),\n freqY: periodY / (gridHeight * freq),\n periodX,\n periodY,\n };\n });\n}\n\n/** Pure-JS port of `vgPotentialSeamless`, built from `computeMediumSpatialPeriods`'s output the\n * same way `vgPotentialJS` is built from `MEDIUM_TIME_OCTAVES` directly. */\nexport function vgPotentialSeamlessJS(\n px: number,\n py: number,\n phase: number,\n spatial: readonly MediumSpatialOctave[],\n): number {\n const [o0, o1, o2, o3] = MEDIUM_TIME_OCTAVES;\n const [s0, s1, s2, s3] = spatial;\n let v =\n vgValueNoise3SeamlessJS(\n px * s0.freqX,\n py * s0.freqY,\n phase * o0.rate + o0.offset,\n s0.periodX,\n s0.periodY,\n mediumOctavePeriod(o0.rate),\n ) * 0.5;\n v +=\n vgValueNoise3SeamlessJS(\n px * s1.freqX,\n py * s1.freqY,\n phase * o1.rate + o1.offset,\n s1.periodX,\n s1.periodY,\n mediumOctavePeriod(o1.rate),\n ) * 0.25;\n v +=\n vgValueNoise3SeamlessJS(\n px * s2.freqX,\n py * s2.freqY,\n phase * o2.rate + o2.offset,\n s2.periodX,\n s2.periodY,\n mediumOctavePeriod(o2.rate),\n ) * 0.125;\n const macro = vgValueNoise3SeamlessJS(\n px * s3.freqX,\n py * s3.freqY,\n phase * o3.rate + o3.offset,\n s3.periodX,\n s3.periodY,\n mediumOctavePeriod(o3.rate),\n );\n return v * (0.7 + 0.3 * macro);\n}\n","// Oklab / linear-sRGB — the shared math for mixing the medium's channels (mix in OKLCH/Oklab, not\n// raw RGB, or saturated diffusion hues collapse into gray). Coefficients: Björn Ottosson,\n// https://bottosson.github.io/posts/oklab/.\n//\n// The numbers live as ONE set: the shader text is assembled from the same matrices the JS side\n// computes with. While the forward direction lived in TS and the inverse was separate literals in\n// the shader, they could drift apart silently, and the medium's palette would shift by a hue\n// between the GPU and any JS-side precomputation.\ntype Mat3 = readonly [\n readonly [number, number, number],\n readonly [number, number, number],\n readonly [number, number, number],\n];\n\nexport const LINEAR_TO_LMS: Mat3 = [\n [0.4122214708, 0.5363325363, 0.0514459929],\n [0.2119034982, 0.6806995451, 0.1073969566],\n [0.0883024619, 0.2817188376, 0.6299787005],\n];\n\nexport const LMS_TO_OKLAB: Mat3 = [\n [0.2104542553, 0.793617785, -0.0040720468],\n [1.9779984951, -2.428592205, 0.4505937099],\n [0.0259040371, 0.7827717662, -0.808675766],\n];\n\nexport const OKLAB_TO_LMS: Mat3 = [\n [1, 0.3963377774, 0.2158037573],\n [1, -0.1055613458, -0.0638541728],\n [1, -0.0894841775, -1.291485548],\n];\n\nexport const LMS_TO_LINEAR: Mat3 = [\n [4.0767416621, -3.3077115913, 0.2309699292],\n [-1.2684380046, 2.6097574011, -0.3413193965],\n [-0.0041960863, -0.7034186147, 1.707614701],\n];\n\n/** GLSL integers aren't floats, so the decimal point is mandatory: `1` won't compile as a float. */\nconst glsl = (v: number) => (Number.isInteger(v) ? v.toFixed(1) : String(v));\nconst row = (m: Mat3, i: number, vars: readonly [string, string, string]) =>\n m[i].map((k, j) => `${glsl(k)} * ${vars[j]}`).join(' + ');\n\nexport const VG_OKLAB_TO_SRGB = `\nfloat3 vgLinearToSrgb(float3 c) {\n float3 lo = c * 12.92;\n float3 hi = 1.055 * pow(max(c, float3(0.0)), float3(1.0 / 2.4)) - 0.055;\n return mix(lo, hi, step(float3(0.0031308), c));\n}\n\nfloat3 vgOklabToLinear(float3 lab) {\n float l_ = ${row(OKLAB_TO_LMS, 0, ['lab.x', 'lab.y', 'lab.z'])};\n float m_ = ${row(OKLAB_TO_LMS, 1, ['lab.x', 'lab.y', 'lab.z'])};\n float s_ = ${row(OKLAB_TO_LMS, 2, ['lab.x', 'lab.y', 'lab.z'])};\n float l = l_ * l_ * l_;\n float m = m_ * m_ * m_;\n float s = s_ * s_ * s_;\n return float3(\n ${row(LMS_TO_LINEAR, 0, ['l', 'm', 's'])},\n ${row(LMS_TO_LINEAR, 1, ['l', 'm', 's'])},\n ${row(LMS_TO_LINEAR, 2, ['l', 'm', 's'])}\n );\n}\n`;\n\nconst apply = (m: Mat3, v: readonly [number, number, number]): [number, number, number] => [\n m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],\n m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],\n m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],\n];\n\nconst srgbChannelToLinear = (c: number) =>\n c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\nconst linearChannelToSrgb = (c: number) =>\n c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(Math.max(c, 0), 1 / 2.4) - 0.055;\n\nexport function srgbToOklab(rgb: readonly [number, number, number]): readonly [number, number, number] {\n const linear = rgb.map(srgbChannelToLinear) as [number, number, number];\n const lms = apply(LINEAR_TO_LMS, linear);\n return apply(LMS_TO_OKLAB, lms.map((v) => Math.cbrt(Math.max(v, 0))) as [number, number, number]);\n}\n\n/** The inverse direction in JS — the same numbers that go into the shader: only that way does a\n * round-trip test prove the GPU and the CPU compute the same thing. */\nexport function oklabToSrgb(lab: readonly [number, number, number]): readonly [number, number, number] {\n const lms = apply(OKLAB_TO_LMS, lab as [number, number, number]).map((v) => v * v * v) as [\n number,\n number,\n number,\n ];\n return apply(LMS_TO_LINEAR, lms).map(linearChannelToSrgb) as [number, number, number];\n}\n","// Tones are DERIVED from a character, not picked as separate colors. The three most common colors\n// of a piece of cover art are not a palette: they land all around the hue wheel, and mixing two\n// opposing hues passes through gray. A narrow arc around a single hue is the guard against that —\n// mixes inside it simply have nowhere to pass through gray.\n//\n// This derivation now typically feeds `lights` (params.ts), not `channelColors`: a chamber's gas is\n// invisible, only what scatters light reads at all (see MEDIUM_COMPOSITE_SHADER) — a caller building\n// a light rig from cover art applies `characterOf`/`speciesFamily` to the LIGHTS' colors. The\n// functions stay general (any N-tone family around a character), so nothing here changed; only\n// where a consumer plugs the result in did.\n\nimport { oklabToSrgb, srgbToOklab } from './oklab';\nimport type { VireUIKitMediumChannel } from './params';\n\n/** The medium's character: one hue, one chroma, one lightness shared by the whole triad. */\nexport type VireUIKitMediumCharacter = {\n /** Oklch hue, degrees. */\n hue: number;\n /** Oklch chroma. For the initial state, this is the platform tone's own chroma — i.e. the floor. */\n chroma: number;\n /** Oklab lightness, shared across every species: unevenly bright tones read as separate patches\n * rather than one medium. */\n lightness: number;\n};\n\n/**\n * The medium's working lightness. Glass can't be parked on the polarity threshold (0.50…0.62) —\n * the material would flip its polarity every frame there — so the medium stays below that band.\n * The physical picture is the same either way: the chamber is a dark volume that tracks glow\n * inside it.\n */\nexport const MEDIUM_SPECIES_LIGHTNESS = 0.44;\n\n/** Arc between neighboring species, degrees (a 25–40° spread is the target). */\nexport const MEDIUM_SPECIES_HUE_SPREAD = 32;\n\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\n\n/** Hue, chroma and lightness of an sRGB color — the input for building a family around it. */\nexport function characterOf(color: VireUIKitMediumChannel): VireUIKitMediumCharacter {\n const [lightness, a, b] = srgbToOklab(color);\n return {\n hue: (Math.atan2(b, a) * 180) / Math.PI,\n chroma: Math.hypot(a, b),\n lightness,\n };\n}\n\n/**\n * A family of three species: `H − Δ`, `H`, `H + Δ`, sharing lightness and chroma.\n *\n * Cover art with no color needs no special case: on a gray cover, chroma arrives as the floor,\n * and the arc collapses on its own — all three species get the same platform tone. That is the\n * initial state, not a branch written for it.\n */\nexport function speciesFamily(\n character: VireUIKitMediumCharacter,\n spreadDeg = MEDIUM_SPECIES_HUE_SPREAD,\n): readonly [VireUIKitMediumChannel, VireUIKitMediumChannel, VireUIKitMediumChannel] {\n const { hue, chroma, lightness } = character;\n const tone = (deg: number): VireUIKitMediumChannel => {\n const rad = (deg * Math.PI) / 180;\n return oklabToSrgb([lightness, chroma * Math.cos(rad), chroma * Math.sin(rad)]).map(\n clamp01,\n ) as unknown as VireUIKitMediumChannel;\n };\n return [tone(hue - spreadDeg), tone(hue), tone(hue + spreadDeg)];\n}\n\n/**\n * The initial state: with no cover art, there is nowhere to take a color from. The family is\n * derived from the platform's own tone, so there is no hardcoded triad in the code — neither a\n * placeholder nor an example — only the platform tone and the derivation rule.\n */\nexport function neutralSpecies(\n baseColor: VireUIKitMediumChannel,\n): readonly [VireUIKitMediumChannel, VireUIKitMediumChannel, VireUIKitMediumChannel] {\n const { hue, chroma } = characterOf(baseColor);\n return speciesFamily({ hue, chroma, lightness: MEDIUM_SPECIES_LIGHTNESS });\n}\n","import { neutralSpecies } from './species';\n\n// Medium parameters — the single source of truth for the web runtime (`web/medium.ts`) and,\n// later, an Android one: the numbers are one set, the platforms only differ in how they feed them\n// into uniforms.\nexport type VireUIKitMediumChannel = readonly [number, number, number];\n\n/**\n * A static light source, as data — the chamber's illumination is a separate entity from the gas it\n * lights (see `MEDIUM_COMPOSITE_SHADER`): the medium itself is dark, and everything visible is\n * light scattered by mist, condensate and tracks inside a light's reach.\n */\nexport type VireUIKitMediumLight = {\n /** Fraction of the frame, 0…1 both axes. `y=0` is the chamber's visual BOTTOM edge, `y=1` the\n * top — the same convention `gravityBottomBoost` already uses in `MEDIUM_COMPOSITE_SHADER`. */\n position: readonly [number, number];\n /** Unit direction (any nonzero vector is normalized before upload) the light points into the\n * chamber, in the SAME up-is-positive-y convention as `position`. Kept off the exact\n * vertical/horizontal axes by default: an on-axis beam edge is itself axis-aligned, which is\n * exactly the straight-line artifact `check:medium`'s isotropy gate polices, and an angled shaft\n * also reads more like a real illuminated volume than a spotlight aimed straight down a wall. */\n direction: readonly [number, number];\n /** Half-angle of the light's cone, radians. Wide (close to a right angle or beyond) for an edge\n * strip that washes the whole chamber width — there the shaping comes from distance falloff, not\n * a cone edge — narrow for a spotlight, where the cone edge IS the visible beam. */\n coneAngle: number;\n /** 1/(fraction of frame height) — how fast illumination drops off with distance from the light.\n * Distance is normalized by frame HEIGHT (not a raw fraction of each axis) so a circular pool of\n * light stays circular on screen regardless of the frame's aspect ratio. */\n falloff: number;\n /** sRGB 0…1. Defaults to a cool neutral; a caller deriving a light rig from cover art applies\n * `speciesFamily`/`characterOf` (`species.ts`) to THESE colors now, not to `channelColors` —\n * see the comment on `channelColors` below. */\n color: VireUIKitMediumChannel;\n /** ≥0 multiplier on this light's contribution. 0 turns it off without removing it from the array. */\n intensity: number;\n};\n\n/** How many lights `MEDIUM_COMPOSITE_SHADER` reads — a fixed, small count of flat uniforms\n * (`u_light0*`/`u_light1*`/`u_light2*`), the same pattern `u_lab0/1/2` already uses for species:\n * `setUniform` (vireglass/web) has no array form, only scalars up to vec4. A `lights` array\n * shorter than this is padded with intensity-0 entries; a longer one is truncated. */\nexport const MEDIUM_MAX_LIGHTS = 3;\n\nexport type VireUIKitMediumParams = {\n /** 1/s — how fast the TRACK density decays per second. Water (vapor+condensate) does not decay:\n * it is a conserved volume, only moving between phases and being transported. */\n decay: number;\n /** Spatial frequency of the curl-noise potential, 1/px of the simulation grid. */\n curlFreq: number;\n /** Drift speed of the noise domain over time, 1/s. */\n curlSpeed: number;\n /** Velocity field scale, grid-px/s — how far the smoke can be carried in a second. */\n advectSpeed: number;\n /** Turbulence gate, 0…1 (playback drives it: playing is alive, paused cools down). The\n * per-frame value is computed by `createMediumDynamics` (`dynamics.ts`) — it arrives here\n * already resolved. */\n turbulence: number;\n /** 1/s — the fraction of vapor EXCESS above `condensationFloor` that turns into condensate every\n * second from background seeding: weak, everywhere, all the time — the chamber is never\n * perfectly clean. */\n condensationRate: number;\n /** Vapor density (in seed-density units, a spot's peak ≈1) below which condensation does not\n * happen at all — only the excess above it condenses. Without a floor, the condensate would\n * exactly retrace the vapor spot's shape (the same outline, or softer from growth blur), and\n * the two phases would have nowhere to visually diverge: vapor is already soft, so the floor\n * concentrates condensate in dense turbulence cores, leaving the sparse vapor around it\n * uncondensed. Keep it NOTICEABLY below the settled mean vapor level (turbulence stirs spots\n * into a nearly flat background of ~0.3 over a few minutes — a known limit of the numerical\n * advection): a floor above that mean eventually stops finding any excess at all, and\n * condensation dies out for good. */\n condensationFloor: number;\n /** 1/s — the fraction of condensate evaporating back into vapor. Larger than the condensation\n * rate: at equilibrium most of the water stays vapor, which is the \"supersaturated\" one. */\n evaporationRate: number;\n /** grid-px/s — condensate drift downward, on top of the shared velocity field: gravity touches\n * droplets and leaves vapor alone. */\n condensateSettleSpeed: number;\n /** Multiplier on the ambient curl field's OWN x-component, added to settling UNCONDITIONALLY\n * (not gated by `turbulence` the way the shared velocity field is) — see\n * MEDIUM_ADVECT_VELOCITY_SETTLE in advect-shader.ts. Without it, a droplet falling at a constant\n * `condensateSettleSpeed` with only a turbulence-gated sideways push traces a near-straight\n * vertical line whenever turbulence is low (rest/paused: 0.045, over 20x below the gated push\n * needed to bend the fall) — many droplets in nearby columns then read as parallel vertical\n * streaks, one of the axis-aligned artifacts `check-medium.mjs`'s isotropy gate measures. Reuses\n * the SAME seamless curl field `vel` already reads rather than a second noise system, so it adds\n * no new seam of its own. Calibrated against that gate at the product's default grid (1920x952,\n * 74x37) — see the gate's own report for the measured ratio at this value. */\n condensateSettleWiggle: number;\n /** 1/s — the rate at which a droplet blends with its grid neighbors. There is no separate age\n * counter: the blend repeats every frame, and whatever has survived more frames ends up more\n * blended with its neighbors on its own — that IS \"growth\". Keep it NOTICEABLY below\n * `evaporationRate`: over a droplet's average lifetime (1/evaporationRate) only a small\n * fraction should get to blend — otherwise the blur outruns evaporation and the whole grid\n * levels into flat fog before a droplet gets a chance to return to vapor (visible by eye: the\n * contrast between \"a patch of droplets\" and the vapor background disappears within seconds). */\n condensateSpreadRate: number;\n /** 0…1 — how much of the local vapor hue shows through in the condensate. Droplets carry no\n * color of their own (droplets scatter light rather than tint it) — this is only a faint hint\n * of the surroundings. */\n condensateTint: number;\n /** Condensate's visibility multiplier in the Oklab mix relative to vapor at equal density —\n * droplets hold a small share of the water's mass, but they are mostly what's visible (as in a\n * real cloud: vapor is transparent, droplets create the visibility). */\n condensateGain: number;\n /** Near-white sRGB tone of droplets with no hue seeding (`condensateTint=0`) — droplets scatter\n * light rather than color it. */\n condensateColor: VireUIKitMediumChannel;\n /** Each vapor species' hue, sRGB 0…1, folded into the dark ambient mix underneath the lit layer\n * (see `MEDIUM_COMPOSITE_SHADER`) — a minor, near-neutral hint by default. Colour now lives on\n * `lights`, not here: a real chamber's gas is invisible, only what scatters light reads at all\n * (see `channelScatter`). Kept for callers that still want a tinted ambient base, and because the\n * isotropy/contrast/depth gates in `check-medium.mjs` measure structure through it directly. */\n channelColors: readonly [VireUIKitMediumChannel, VireUIKitMediumChannel, VireUIKitMediumChannel];\n /** How strongly each vapor species scatters light, relative to the others — what a species IS,\n * now that colour lives on `lights` (see `channelColors`). `[1, 1, 1]` (equal) by default; a\n * caller can make one species read as denser mist than another without touching its hue. */\n channelScatter: VireUIKitMediumChannel;\n /** The platform's neutral tone — what the medium is, alone, with no source playing. */\n baseColor: VireUIKitMediumChannel;\n /** Background weight in the Oklab mix: keeps the platform tone visible even where the smoke is dense. */\n baseWeight: number;\n /** Field-sample scale for the far depth plane, k ≥ 1 (see `MEDIUM_COMPOSITE_SHADER`): the far\n * plane reads the SAME vapor/condensate field as the near plane, at `src * k + offset` instead\n * of `src`. A larger k folds more of the field into the same screen area, so the identical\n * evolving field reads as both finer-grained structure (features occupy fewer screen-px) and,\n * because the field's own motion maps to screen motion as v/k, slower apparent motion — parallax\n * and scale-of-structure from one number, with no second simulation. Measured against\n * `check-medium.mjs`'s parallax gate (a cross-correlation best-shift, not a centroid — see the\n * gate's own comment on why): 1.6 puts the far/near screen-speed ratio at 0.630, matching the\n * naive prediction 1/1.6 = 0.625 almost exactly, comfortably inside a band that reads as depth\n * without the far plane looking frozen. */\n depthFarScale: number;\n /** Offset into the shared field for the far plane, as a FRACTION of the grid's own width/height\n * (per axis) rather than a fixed grid-px value — the field wraps (`gl.REPEAT`), so a fixed\n * grid-px offset's EFFECTIVE (wrapped) distance from zero depends on whatever grid size it\n * happens to run at, and can land close to zero by accident at a size nobody tested against.\n * (This shipped once as `[41, 67]` grid-px: harmless at the 96px grid the depth gates tested\n * against, but 67 wraps to just 5px at the 72px grid height this package's own README uses as\n * its example — the far plane nearly re-read the near one, exactly the echo depth exists to\n * avoid.) A fraction keeps the wrapped distance proportional to the grid at any size — see\n * `depthFarOffsetPx` below and `check-medium.mjs`'s decorrelation gate, which measures this\n * directly at the README's own 128x72. */\n depthFarOffsetFrac: readonly [number, number];\n /** Grid-px radius of the far plane's 8-tap ring blur, rotated so no tap pair lies on a grid\n * axis. A spatial average cannot raise local\n * variance, so the same blur that softens the far plane's edges also, by construction, lowers\n * its measured contrast — one mechanism for both aerial-perspective cues (no separate contrast\n * knob exists). 0 disables it (the near plane stays sharp). Measured: reading the far plane's\n * own scale/offset with no blur already drops contrast ~6% (a fixed offset can land on a\n * locally denser or sparser patch of a field that isn't spatially uniform); this radius takes\n * the total to 24% — see the aerial-perspective gate, whose two thresholds isolate blur's own\n * share from that baseline. */\n depthFarBlurRadius: number;\n /** The far plane's share of the mix, relative to the near plane's implicit 1 — aerial\n * perspective's other half: even at equal density the far plane must not compete with the near\n * one for attention. */\n depthFarWeight: number;\n /** grid-px/s — a small downward drift added to VAPOR's own velocity, on top of the shared\n * curl-noise field (condensate already settles faster via `condensateSettleSpeed`). An order of\n * magnitude below it so the gas reads as \"barely noticeable\" drift rather than visibly falling.\n * ADDS NO DENSITY: on this periodic (`gl.REPEAT`) grid a uniform drift only ever translates the\n * field and wraps it back in at the top — it cannot accumulate mass anywhere, the way a real\n * floor would. It is a motion cue (which way is down), not a source of \"denser at the bottom\" —\n * that reading comes entirely from `gravityBottomBoost` below. See\n * `check-medium.mjs`'s vapor-drift gate, which checks DIRECTION over a short window rather than\n * a density ratio, for exactly this reason. */\n gravityVaporDrift: number;\n /** Density boost at the very bottom of the frame, added on top of 1 — a COMPOSITING-only effect:\n * it scales how the already-conserved vapor/condensate density is DISPLAYED, never the\n * simulated buffers themselves, so it cannot threaten the water-conservation invariant by\n * construction, and — unlike `gravityVaporDrift` above — this is the mechanism that actually\n * produces \"denser at the bottom\": it is a steady-state property of the compositing math, true\n * from the very first frame, independent of any warmup or mixing time (see\n * `check-medium.mjs`'s boost gate, which measures it with zero simulated steps for exactly that\n * reason). Large in absolute terms because `baseWeight` (0.9) dominates the Oklab mix wherever\n * gas density is thin — a small boost gets diluted into an invisible change in the final color;\n * measured this large to register as a real, visible band against that dilution. */\n gravityBottomBoost: number;\n /** Fraction of frame height (0 top, 1 bottom) where the bottom boost starts ramping in via\n * `smoothstep`. The brief asks for a denser LAYER at the floor of the chamber, not a boost that\n * reaches halfway up the frame — kept in the bottom quarter. */\n gravityBottomBoostStart: number;\n /** Static lights — the chamber's illumination, entirely separate from the gas (see\n * `VireUIKitMediumLight`). Padded/truncated to `MEDIUM_MAX_LIGHTS` by the web runtime. */\n lights: readonly VireUIKitMediumLight[];\n /** How strongly the gas between a spotlight and a point dims it, per frame height of dense gas. */\n shadowExtinction: number;\n /** 0…1 — a track's minimum visibility regardless of local illumination, added to the light\n * reaching it. Tracks are lit the same way as mist, but a fresh ionization trail is bright enough\n * in a real chamber to read even where the ambient light is weak — this is that floor, not a\n * second light source. */\n trackLightFloor: number;\n /** ≥0 — master strength of the crisp, screen-space track layer (`web/track-layer.ts`): a grid\n * cell cannot carry a thin sharp line (see that module's own header), so the grid stamp this\n * file's `decay`/`trackLightFloor` govern stays the soft, gas-carried residue, and THIS is the\n * bright, sharp geometry drawn over it in content pixels. 0 disables it outright — the canary\n * `check:medium` uses to prove its \"sharp at birth\" gate is actually sensitive to the layer,\n * not to the residue underneath it. */\n trackLayerAmount: number;\n};\n\nconst BASE_COLOR: VireUIKitMediumChannel = [0.07, 0.08, 0.1];\n\nconst normalize2 = (v: readonly [number, number]): readonly [number, number] => {\n const len = Math.hypot(v[0], v[1]) || 1;\n return [v[0] / len, v[1] / len];\n};\n\n/** Cool neutral — the reference's fluorescent-tube/LED-strip look, not a warm incandescent one. */\nconst MEDIUM_DEFAULT_LIGHT_COLOR: VireUIKitMediumChannel = [0.78, 0.85, 0.94];\n\n/**\n * An edge strip along the chamber floor plus two spotlights above the frame, aimed down across it\n * toward the source point. The lamps stay off screen, so a beam never shows its apex, and the two\n * differ slightly in position, angle and strength: an exact mirror reads as staged.\n */\nexport const MEDIUM_DEFAULT_LIGHTS: readonly VireUIKitMediumLight[] = [\n {\n position: [0.5, 0.0],\n direction: [0, 1],\n coneAngle: Math.PI,\n falloff: 16,\n color: MEDIUM_DEFAULT_LIGHT_COLOR,\n intensity: 0.28,\n },\n {\n position: [0.12, 1.45],\n direction: normalize2([0.38, -1.03]),\n coneAngle: (9 * Math.PI) / 180,\n falloff: 0.7,\n color: MEDIUM_DEFAULT_LIGHT_COLOR,\n intensity: 1.1,\n },\n {\n position: [0.9, 1.4],\n direction: normalize2([-0.4, -0.98]),\n coneAngle: (8 * Math.PI) / 180,\n falloff: 0.75,\n color: MEDIUM_DEFAULT_LIGHT_COLOR,\n intensity: 0.9,\n },\n];\n\nexport const MEDIUM_DEFAULTS: VireUIKitMediumParams = {\n // A stamped track has to visibly broaden and fade within the reference's \"roughly one to two\n // seconds\", not the ~11s half-life a 0.06 decay gives — measured against check-medium.mjs's track\n // gate (birth vs +1.5s cross-section width and total density): 1.1 leaves ~30% of the peak at 1s\n // and ~17% at 1.5s, comfortably faded without dying before MacCormack's own diffusion has time to\n // visibly broaden the stamp.\n decay: 1.1,\n curlFreq: 0.045,\n curlSpeed: 0.12,\n advectSpeed: 26,\n turbulence: 1,\n condensationRate: 0.18,\n condensationFloor: 0.12,\n evaporationRate: 0.2,\n condensateSettleSpeed: 8,\n condensateSettleWiggle: 0.4,\n condensateSpreadRate: 0.08,\n condensateTint: 0.2,\n condensateGain: 5,\n condensateColor: [0.94, 0.95, 0.97],\n channelColors: neutralSpecies(BASE_COLOR),\n channelScatter: [1, 1, 1],\n baseColor: BASE_COLOR,\n baseWeight: 0.9,\n depthFarScale: 1.6,\n depthFarOffsetFrac: [0.47, 0.53],\n depthFarBlurRadius: 5,\n depthFarWeight: 0.5,\n gravityVaporDrift: 0.6,\n gravityBottomBoost: 2,\n gravityBottomBoostStart: 0.75,\n lights: MEDIUM_DEFAULT_LIGHTS,\n shadowExtinction: 3,\n trackLightFloor: 0.8,\n trackLayerAmount: 1,\n};\n\n/**\n * Resolves the far plane's fractional offset (`depthFarOffsetFrac`) into grid-px for a GIVEN grid\n * size — pure and exported so the wrap-safety it exists for is directly testable (see\n * `medium-depth-offset.test.ts`) without spinning up a WebGL2 context. Per-axis: `gridWidth` and\n * `gridHeight` are independent, and a non-square grid (the README's own 128x72 example) shouldn't\n * distort one axis relative to the other.\n */\nexport function depthFarOffsetPx(\n offsetFrac: readonly [number, number],\n gridWidth: number,\n gridHeight: number,\n): readonly [number, number] {\n return [offsetFrac[0] * gridWidth, offsetFrac[1] * gridHeight];\n}\n\n/**\n * How far a value lands from the nearest wrap boundary (0 or `period`) on a periodic axis of the\n * given `period` — the quantity that actually matters for decorrelation, not the raw offset value\n * itself (a raw offset near the period is effectively a tiny one once the field wraps). Exported\n * for the same reason as `depthFarOffsetPx`: a plain number, testable without a GPU.\n */\nexport function wrappedDistanceFromZero(value: number, period: number): number {\n const wrapped = ((value % period) + period) % period;\n return Math.min(wrapped, period - wrapped);\n}\n\n/** The material probe can't see texture finer than this: there is no gain in a denser simulation\n * grid than the probe's own floor. */\nexport const MEDIUM_PROBE_FLOOR_PX = 22;\n\n/** Device-px per simulation grid cell by default — noticeably coarser than the probe floor;\n * sharpening the detail visible right next to the glass is a later increment (emission and\n * palette). */\nexport const MEDIUM_DEFAULT_CELL_PX = 26;\n\n/**\n * The geometry of one track stamp (`emit-shader.ts`), in FRACTIONS of `min(gridW, gridH)` rather\n * than pixels — so a preset doesn't depend on the simulation grid's resolution. Three presets, the\n * reference's three visible kinds: `alpha` is a thick, short, straight ray — `createMediumDynamics`\n * stamps a dozen or so of these at once per event, radiating from one point, so a single ray is\n * short on purpose. `electron` is thin, long-ish and ragged (wiggly, granular). `muon` is long,\n * straight and thin — a cosmic ray crossing the whole chamber, unrelated to any source.\n */\nexport type VireUIKitMediumTrackPreset = {\n lengthFrac: number;\n headWidthFrac: number;\n tailWidthFrac: number;\n /** Amplitude of the path's sideways noise, as a fraction of `min(gridW, gridH)`; 0 = a straight thread. */\n raggedFrac: number;\n raggedFreq: number;\n /** How far the tail has settled downward, as a fraction of `min(gridW, gridH)`. */\n settleFrac: number;\n /** How much dimmer the tail is than the head, 0…1. */\n tailDim: number;\n intensity: number;\n};\n\nexport const MEDIUM_TRACK_PRESETS: Readonly<\n Record<'alpha' | 'electron' | 'muon', VireUIKitMediumTrackPreset>\n> = {\n alpha: {\n lengthFrac: 0.22,\n headWidthFrac: 0.05,\n tailWidthFrac: 0.12,\n raggedFrac: 0,\n raggedFreq: 0,\n settleFrac: 0.02,\n tailDim: 0.55,\n intensity: 1.15,\n },\n electron: {\n lengthFrac: 0.42,\n headWidthFrac: 0.018,\n tailWidthFrac: 0.05,\n raggedFrac: 0.045,\n raggedFreq: 5,\n settleFrac: 0.05,\n tailDim: 0.45,\n intensity: 0.6,\n },\n muon: {\n lengthFrac: 0.7,\n headWidthFrac: 0.012,\n tailWidthFrac: 0.02,\n raggedFrac: 0,\n raggedFreq: 0,\n settleFrac: 0.01,\n tailDim: 0.75,\n intensity: 0.5,\n },\n};\n\n/** Turbulence at rest/paused/with no active track — the \"slow drift\" baseline shared by all three\n * neutral states (they differ from each other in other ways, not in this number). */\n// Rest has to look like it's actually resting: mixing happens over minutes, not seconds. A\n// slower flow also accumulates less numerical diffusion.\nexport const MEDIUM_DRIFT_TURBULENCE = 0.045;\n\n/** Turbulence range during playback: the low end is a quiet stretch of the track, the high end is\n * peak amplitude. */\nexport const MEDIUM_PLAYING_TURBULENCE_RANGE: readonly [number, number] = [0.55, 1.15];\n\n/** Turbulence smoothing time constant, seconds: both ramping up on play and cooling down on pause\n * go through this filter — smooth, never a jump. */\nexport const MEDIUM_TURBULENCE_TAU = 2.5;\n\n/** Emission cadence — once every this many beats, not on every beat: at 120–180 BPM that is\n * 0.5–0.75 Hz, an order of magnitude below the WCAG 2.3.1 photosensitivity ceiling (3 Hz). */\nexport const MEDIUM_EMISSION_BEATS = 4;\n\n/** Fraction of the beats that actually get an emission — breaks up the exact periodicity (a\n * metronome-perfect pulse reads as mechanical) without moving the average rate. */\nexport const MEDIUM_EMISSION_PROBABILITY = 0.55;\n\n/** Emergency floor between two emissions, seconds — a safeguard for the same WCAG 2.3.1\n * requirement, independent of `MEDIUM_EMISSION_BEATS`/BPM: even a mistake in the other constants\n * can't flash faster than this. */\nexport const MEDIUM_MIN_EMISSION_INTERVAL = 1.2;\n\n/** Average interval between solitary \"background emission\" tracks at rest, seconds, plus a random\n * `MEDIUM_NATURAL_JITTER` on top — rare, with no discernible rhythm. */\nexport const MEDIUM_NATURAL_INTERVAL = 8;\nexport const MEDIUM_NATURAL_JITTER = 12;\n\n/** The sensitive layer — fractions of height, top to bottom. Above it there isn't enough\n * supersaturation for a track to appear at all, so background emission is only ever visible here. */\nexport const MEDIUM_SENSITIVE_TOP = 0.55;\nexport const MEDIUM_SENSITIVE_BOTTOM = 0.95;\n\n/** How many alpha rays a single decay event stamps at once, radiating from the same source point —\n * the reference's starburst, not a lone track (`MEDIUM_TRACK_PRESETS.alpha` is the shape of ONE\n * ray). Randomized per event within this range. */\nexport const MEDIUM_ALPHA_BURST_RAYS: readonly [number, number] = [8, 16];\n\n/** Per-ray length multiplier within a burst, so the rays read as an organic spray rather than a\n * wheel of identical spokes — the reference's rays visibly differ in reach. */\nexport const MEDIUM_ALPHA_RAY_LENGTH_SCALE: readonly [number, number] = [0.55, 1];\n\n/** Per-emission depth range applied to a track's width (`headWidthFrac`/`tailWidthFrac`), far…near.\n * The same knob reads as both \"thin\" and \"soft\": a Gaussian stamp's edge steepness scales with its\n * own width (`emit-shader.ts`'s only extent parameter), so shrinking it for a far track thins and\n * softens it in the same stroke — there is no second knob to hang an independent softness number\n * on. Floored at 0.55: below that a far `alpha` track's head narrows past what MacCormack's own\n * numerical smoothing already blurs it to, and the depth cue disappears into that noise floor.\n * The near end now runs past 1 (1.2, not 1): depth of field needs the NEAREST tracks a little\n * LARGER than the base preset too, not just full-size — the reference shows near tracks blurring\n * into big soft shapes rather than merely reading \"as sharp as it gets\". The same knob does both\n * jobs again: a wider-than-base stamp is also a softer-edged one. */\nexport const MEDIUM_TRACK_DEPTH_WIDTH_RANGE: readonly [number, number] = [0.55, 1.2];\n\n/** Per-emission depth range applied to a track's intensity, far…near — \"dim\". Floored at 0.4, not\n * lower: a far track still has to clear `MEDIUM_DEFAULTS.condensationFloor`'s excess threshold\n * often enough to read as a track, or depth would look like tracks randomly failing to spawn\n * rather than fading into the distance. */\nexport const MEDIUM_TRACK_DEPTH_INTENSITY_RANGE: readonly [number, number] = [0.4, 1];\n\n/**\n * How long a live track stays in the crisp screen-space layer (`web/track-layer.ts`), seconds, by\n * preset — tuned by eye against the reference's own \"roughly one to two seconds\"\n * (`docs`/the brief's own reference frames): `alpha` is the shortest, thickest burst ray; `muon`,\n * a cosmic ray crossing the whole chamber, lingers longest. Independent of `MEDIUM_DEFAULTS.decay`,\n * which times the SOFT gas-carried residue left behind in the grid, not this layer.\n */\nexport const MEDIUM_TRACK_LAYER_LIFE_SECONDS: Readonly<Record<keyof typeof MEDIUM_TRACK_PRESETS, number>> = {\n alpha: 1.8,\n electron: 1.4,\n muon: 2.2,\n};\n\n/** Oldest-out cap on the live-track list (`web/track-layer.ts`) — a burst of a dozen-plus alpha\n * rays every few seconds would otherwise grow the instanced draw without bound over a long\n * session; 64 is comfortably above what a few overlapping bursts plus background radiation ever\n * need alive at once. */\nexport const MEDIUM_TRACK_LAYER_MAX_TRACKS = 64;\n\n/**\n * How a track looks in the crisp layer, per kind. Lengths are fractions of\n * `min(contentWidth, contentHeight)`: the grid presets' widths are sized for a 26 px cell and\n * would be tens of pixels wide here.\n */\nexport type VireUIKitTrackLayerLook = {\n /** Core Gaussian sigma at birth, at the source end. */\n sigmaFrac: number;\n /** Sigma multiplier at the far end: an alpha thickens toward the end of its range. */\n endSigmaMul: number;\n /** Diffusion: sigma(age) = sigma * sqrt(1 + broaden * age). */\n broaden: number;\n /** Sideways wiggle amplitude, for kinds whose preset has a ragged path. */\n wiggleFrac: number;\n /** Period of the brightness beads along the track. */\n beadFrac: number;\n /** Mean spacing of stray droplets along the track, and the share of slots that hold one. */\n dropletFrac: number;\n dropletChance: number;\n /** Gap between the source point and where the track starts. */\n startFrac: number;\n /** Brightness at birth, before depth and fade. */\n gain: number;\n};\n\nexport const MEDIUM_TRACK_LAYER_LOOK: Readonly<Record<keyof typeof MEDIUM_TRACK_PRESETS, VireUIKitTrackLayerLook>> = {\n alpha: {\n sigmaFrac: 0.0022,\n endSigmaMul: 1.9,\n broaden: 3.2,\n wiggleFrac: 0,\n beadFrac: 0.006,\n dropletFrac: 0.0035,\n dropletChance: 0.5,\n startFrac: 0.035,\n gain: 1.8,\n },\n electron: {\n sigmaFrac: 0.0009,\n endSigmaMul: 1.3,\n broaden: 6,\n wiggleFrac: 0.012,\n beadFrac: 0.008,\n dropletFrac: 0.006,\n dropletChance: 0.55,\n startFrac: 0,\n gain: 2.2,\n },\n muon: {\n sigmaFrac: 0.0008,\n endSigmaMul: 1,\n broaden: 5,\n wiggleFrac: 0,\n beadFrac: 0.007,\n dropletFrac: 0.005,\n dropletChance: 0.5,\n startFrac: 0,\n gain: 2.2,\n },\n};\n\n/** Seconds over which one alpha burst's rays are born: a strong source sprays rays one after\n * another, not all at the same instant. The first `MEDIUM_TRACK_LAYER_BURST_ACCENT` rays land on\n * the beat itself. */\nexport const MEDIUM_TRACK_LAYER_BURST_SPREAD = 1.6;\nexport const MEDIUM_TRACK_LAYER_BURST_ACCENT = 3;\n\n/** Shapes the brightness-vs-age curve as `(1 - age/life) ** gamma`: reaches exactly 0 at removal\n * (no pop when a track leaves the list) while staying close to full brightness for the first part\n * of its life (>1 skews the fade toward the end rather than a flat linear ramp down). */\nexport const MEDIUM_TRACK_LAYER_FADE_GAMMA = 1.6;\n\n/** Downward drift, as a fraction of `min(contentWidth, contentHeight)` per second — droplets that\n * formed along a track keep settling after the stamp itself, the same physical idea as\n * `MEDIUM_DEFAULTS.condensateSettleSpeed` but for this layer's own screen-space geometry rather\n * than the simulation grid. Small: over a track's longest life (muon, 2.2s) this alone moves it\n * well under a tenth of the frame. */\nexport const MEDIUM_TRACK_LAYER_GRAVITY_SAG = 0.018;\n\n/** Scales the curl field's push on a live track (see `web/track-layer.ts`'s `curlVelocityJS`),\n * units of `min(contentWidth, contentHeight)` per second per unit of curl velocity — small enough\n * to read as \"nudged\", not carried the way the gas itself is by `MEDIUM_DEFAULTS.advectSpeed`. */\nexport const MEDIUM_TRACK_LAYER_CURL_PUSH = 0.05;\n","// Initial spots for the three GAS channels: three fixed-color channels with initial spots — this\n// is the entire water supply, gas has no other source, it only conserves and mixes through\n// advection. One pass, three channels: r/g/b are each smoke channel's density on its own, not a\n// color — composite-shader.ts paints them with a fixed palette.\nexport const MEDIUM_SEED_SHADER = `\nuniform float2 u_spot0;\nuniform float2 u_spot1;\nuniform float2 u_spot2;\nuniform float u_spotRadius;\n\nfloat vgSpot(float2 p, float2 c, float r) {\n float d = length(p - c) / max(r, 1.0);\n return exp(-d * d * 2.0);\n}\n\nhalf4 main(float2 xy) {\n float3 dye = float3(\n vgSpot(xy, u_spot0, u_spotRadius),\n vgSpot(xy, u_spot1, u_spotRadius),\n vgSpot(xy, u_spot2, u_spotRadius)\n );\n return half4(half3(dye), half(1.0));\n}\n`;\n","// Bilinear resample of one buffer into a different-sized target — used only when the simulation\n// grid changes size (`ensureGrid` in web/medium.ts resizing on a window resize or a device\n// rotation), to carry vapor/condensate/track state across instead of wiping it. A destroy-and-\n// reseed on every resize was the flicker the design forbids: the vapor jumps back to its three\n// initial spots and condensate/tracks blink out.\n//\n// Same flip convention as advect-shader.ts's `.eval()` reads: `xy` is top-down (the shader's own\n// entry coordinate), the source texture is stored bottom-up, so a straight `.eval()` without the\n// flip would resample from the wrong row. `u_src`'s own size (`u_srcSize`) comes from the\n// transpiler's auto-generated uniform for `uniform shader` (see the comment on the cross-sampler\n// in web/medium.ts) — the OLD grid's dimensions, set explicitly since bindTextureAt only binds the\n// sampler itself.\nexport const MEDIUM_RESAMPLE_SHADER = `\nuniform shader u_src;\n\nhalf4 main(float2 xy) {\n float2 uv = xy / u_resolution;\n float2 src = uv * u_srcSize;\n return u_src.eval(float2(src.x, u_srcSize.y - src.y));\n}\n`;\n","import { VG_CURL_NOISE } from './noise';\n\n// MacCormack instead of plain semi-Lagrangian transport: a single backtrace sample is heavily\n// diffusive on a coarse grid — over roughly 15-35s turbulence stirs the vapor species into a flat\n// background, and all the structure the grid exists for disappears. This scheme splits the\n// transport of EACH field (vapor, condensate) into three passes instead of one:\n// forward — the same backtrace sample the old single-pass scheme used (`MEDIUM_*_FORWARD_SHADER`);\n// correct — a reverse backtrace of the already-transported field, a correction for the\n// difference against the original, and a limiter (clamped to the source cell's\n// neighbors) — without the limiter the scheme produces overshoots that weren't in the\n// field, which read as flicker (`MEDIUM_*_CORRECT_SHADER`);\n// react — the phase exchange (condensation/evaporation/growth), applied to the already\n// corrected transport rather than the raw backtrace (`MEDIUM_*_REACT_SHADER`).\n// The vapor<->condensate exchange used to read its partner AT ITS OWN transported point; splitting\n// the passes removes the cause outright: both phases transport on SEPARATE fields with no\n// cross-reads, and the exchange happens AFTER, on values already transported and grid-aligned at\n// the same point xy — so \"how much vapor was lost\" and \"how much condensate was gained\" agree by\n// construction.\n// u_phase replaces the old u_time/u_curlSpeed pair: the CPU accumulates phase += dt * curlSpeed\n// in float64 and wraps it at MEDIUM_TIME_PERIOD (see web/medium.ts and noise.ts's\n// MEDIUM_TIME_OCTAVES) before it ever becomes a float32 uniform. Over hours of playback, a raw\n// u_time kept growing without bound and eventually lost the bits that place a point within its\n// own lattice cell — the visible symptom was the field jittering in place. A side benefit: since\n// the rate is baked into the accumulation rather than multiplied in on every frame, changing\n// curlSpeed at runtime changes the SLOPE going forward instead of rescaling everything already\n// accumulated — no jump.\nconst MEDIUM_ADVECT_UNIFORMS = `\nuniform shader u_dye;\nuniform float u_dt;\nuniform float u_phase;\nuniform float u_curlFreq;\nuniform float u_advectSpeed;\nuniform float u_turbulence;\n// One float4 per MEDIUM_TIME_OCTAVES entry: xy is that octave's seamless spatial frequency, zw its\n// matching integer lattice period — both grid-size-dependent, so they can't be compile-time\n// constants (see vgPotentialSeamless/computeMediumSpatialPeriods in noise.ts). Recomputed by\n// web/medium.ts's bindVelocityUniforms every frame from the live grid dimensions.\nuniform float4 u_spatial0;\nuniform float4 u_spatial1;\nuniform float4 u_spatial2;\nuniform float4 u_spatial3;\n\n${VG_CURL_NOISE}\n`;\n\n// Reading our own previous frame, which sits in the texture bottom-to-top: xy runs top-to-bottom,\n// and `.eval()` passes the coordinate straight through as a texture coordinate. Without this flip,\n// every step would turn the field upside down, and the two states would alternate frame to frame —\n// flicker across half the refresh rate. The rule is the same for ANY read of a smoke texture via\n// `.eval()` in this file: composite-shader.ts reads these same buffers for display and has to flip\n// the same way.\n//\n// `curl` is kept as its own variable (not folded straight into `vel`) so MEDIUM_ADVECT_VELOCITY_SETTLE\n// below can reuse its x component directly, at full (untouched by `u_turbulence`) strength.\nconst MEDIUM_ADVECT_VELOCITY = `\n float2 curl = vgCurlVelocitySeamless(xy * u_curlFreq, u_phase, u_spatial0, u_spatial1, u_spatial2, u_spatial3);\n float2 vel = curl * u_advectSpeed * u_turbulence;\n`;\n\n// Gravity touches only the condensate — an addition on top of the shared field rather than a\n// separate calculation: droplets are carried by the same wind as vapor, plus a downward drift.\n//\n// `u_settleWiggle` adds a SECOND, unconditional share of the ambient curl's own sideways push —\n// unlike `vel`'s turbulence-gated share above, this one doesn't vanish when turbulence is low. A\n// constant per-cell downward speed with no lateral term traces a dead-straight vertical line: at\n// rest (turbulence ~0.045) the turbulence-gated curl is over an order of magnitude below\n// `u_settleSpeed` and does nothing to break that line up, which is exactly the artifact the\n// isotropy gate exists to catch (see check-medium.mjs and MEDIUM_DEFAULTS.condensateSettleWiggle's\n// own comment for the calibration). Reusing `curl` needs no new noise field and is periodic for\n// free — it is the same seamless field `vel` already reads.\nconst MEDIUM_ADVECT_VELOCITY_SETTLE = `\n${MEDIUM_ADVECT_VELOCITY}\n vel.y += u_settleSpeed;\n vel.x += curl.x * u_advectSpeed * u_settleWiggle;\n`;\n\n// The gas itself leans down too, just far less than condensate: \"from the side\" only reads if\n// gravity touches the medium as a whole, not only its droplets. `u_vaporDrift` is an order of\n// magnitude below `u_settleSpeed` (see MEDIUM_DEFAULTS.gravityVaporDrift) — barely noticeable\n// frame to frame, cumulative over the run.\nconst MEDIUM_ADVECT_VELOCITY_VAPOR = `\n${MEDIUM_ADVECT_VELOCITY}\n vel.y += u_vaporDrift;\n`;\n\n// The forward MacCormack half-step — ONE backtrace sample, bit-for-bit what the whole scheme used\n// to do on its own. The result goes to a temporary buffer rather than out; the `correct` pass will\n// read it again to estimate, via a reverse step, how much this single sample smeared.\nexport const MEDIUM_VAPOR_FORWARD_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform float u_vaporDrift;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY_VAPOR}\n float2 src = xy - vel * u_dt;\n return u_dye.eval(float2(src.x, u_dyeSize.y - src.y));\n}\n`;\n\nexport const MEDIUM_CONDENSATE_FORWARD_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform float u_settleSpeed;\nuniform float u_settleWiggle;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY_SETTLE}\n float2 src = xy - vel * u_dt;\n return u_dye.eval(float2(src.x, u_dyeSize.y - src.y));\n}\n`;\n\n// Reverse half-step + correction + limiter (Selle et al. 2007). The macro expects `vel` already\n// computed (the calling shader sets it to either the forward or the settling version) and two\n// samplers: `u_dye` — the field BEFORE this frame's transport, `u_forward` — the forward pass's\n// result for the same field.\n//\n// Correction: corrected = forward + (field_before - forward_backward)/2 — the standard MacCormack\n// formula, roughly halving one backtrace's numerical diffusion in order of accuracy.\n//\n// The limiter is mandatory: without it the correction isn't LOCALLY conservative and can push a\n// value above every one of the source cell's neighbors — a new extremum that wasn't in the field.\n// On a frame-by-frame sweep that reads as flicker. The clamp is to the range of the four texels\n// neighboring the ORIGINAL field around the point the forward pass came from (not bilinearly\n// blended — exactly the texel, `.eval()` at its precise center, the same technique used for\n// droplet growth in the react shaders below).\nconst MEDIUM_MACCORMACK_CORRECT = `\n float2 srcForward = xy - vel * u_dt;\n float2 srcReverse = xy + vel * u_dt;\n half4 forwardSelf = u_forward.eval(float2(xy.x, u_forwardSize.y - xy.y));\n half4 forwardReverse = u_forward.eval(float2(srcReverse.x, u_forwardSize.y - srcReverse.y));\n half4 prevSelf = u_dye.eval(float2(xy.x, u_dyeSize.y - xy.y));\n half4 corrected = forwardSelf + half4(0.5) * (prevSelf - forwardReverse);\n\n float2 src = float2(srcForward.x, u_dyeSize.y - srcForward.y);\n float2 base = floor(src - 0.5);\n half4 c00 = u_dye.eval(base + float2(0.5, 0.5));\n half4 c10 = u_dye.eval(base + float2(1.5, 0.5));\n half4 c01 = u_dye.eval(base + float2(0.5, 1.5));\n half4 c11 = u_dye.eval(base + float2(1.5, 1.5));\n half4 lo = min(min(c00, c10), min(c01, c11));\n half4 hi = max(max(c00, c10), max(c01, c11));\n half4 limited = clamp(corrected, lo, hi);\n`;\n\nexport const MEDIUM_VAPOR_CORRECT_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform shader u_forward;\nuniform float u_vaporDrift;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY_VAPOR}\n${MEDIUM_MACCORMACK_CORRECT}\n return limited;\n}\n`;\n\nexport const MEDIUM_CONDENSATE_CORRECT_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform float u_settleSpeed;\nuniform float u_settleWiggle;\nuniform shader u_forward;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY_SETTLE}\n${MEDIUM_MACCORMACK_CORRECT}\n return limited;\n}\n`;\n\n// Water is a conserved volume: the sum of vapor and condensate has no source and no sink, only\n// transport and a transition between phases. `u_totalScale` is not water physics but a method\n// correction (see `web/medium.ts`): transport alone doesn't hold the sum exactly (residual\n// numerical diffusion/dispersion), so the runtime checks the total water (vapor+condensate) once a\n// second against the target and folds a correction into both buffers — the same one for each.\n//\n// The reaction operates on the already CORRECTED (MacCormack + limiter) transport of both phases,\n// not the raw backtrace: `u_transported`/`u_condensateTransported` are the correct pass's output,\n// grid-aligned at the same point xy, so the exchange reads its partner with no backtrace of its\n// own and no desync (see the comment at the top of this file).\n//\n// Vapor carries color (three channels r/g/b, as before).\nexport const MEDIUM_VAPOR_REACT_SHADER = `\nuniform shader u_transported;\nuniform shader u_condensateTransported;\nuniform float u_dt;\nuniform float u_condensationRate;\nuniform float u_condensationFloor;\nuniform float u_evaporationRate;\nuniform float u_totalScale;\n\nhalf4 main(float2 xy) {\n float3 prevVapor = float3(u_transported.eval(float2(xy.x, u_transportedSize.y - xy.y)).rgb);\n float condensateAtXy = float(u_condensateTransported.eval(float2(xy.x, u_condensateTransportedSize.y - xy.y)).r);\n float vaporTotal = prevVapor.r + prevVapor.g + prevVapor.b;\n\n // Only the EXCESS of vapor above the background floor condenses, not any nonzero density —\n // otherwise condensate would exactly retrace the vapor spot's shape (the same outline, or\n // softer from blur), leaving the two phases nowhere to visually diverge: vapor is already soft,\n // and condensate needs to read brighter and sharper than it. The floor concentrates condensate\n // in dense turbulence cores, leaving the sparse vapor around it uncondensed — that's where the\n // contrast comes from.\n float excess = max(0.0, vaporTotal - u_condensationFloor);\n float condensedAmount = excess * (1.0 - exp(-u_condensationRate * u_dt));\n float lossFraction = vaporTotal > 1e-6 ? condensedAmount / vaporTotal : 0.0;\n float3 afterLoss = prevVapor * (1.0 - lossFraction);\n\n float condKeep = exp(-u_evaporationRate * u_dt);\n float returned = condensateAtXy * (1.0 - condKeep);\n\n // Evaporated water is distributed by the CURRENT vapor proportion — the condensation/\n // evaporation cycle doesn't shift the hue, water only oscillates between phases (color belongs\n // to vapor).\n float sumAfterLoss = afterLoss.r + afterLoss.g + afterLoss.b;\n float3 hueShare = sumAfterLoss > 1e-6 ? afterLoss / sumAfterLoss : float3(1.0 / 3.0);\n float3 vapor = (afterLoss + hueShare * returned) * u_totalScale;\n return half4(half3(vapor), half(1.0));\n}\n`;\n\n// Condensate is droplets: a scalar (density lives in .r, g/b unused — it has no color of its own,\n// composite-shader.ts tints it with the local vapor hue). Two processes on top of transport:\n// exchange with vapor (mirrors the exchange in MEDIUM_VAPOR_REACT_SHADER, the same\n// excess/condensedAmount from the same transported vaporTotal) and growth (blending with grid\n// neighbors every frame with no age counter: whatever survived more steps ends up more blended\n// with its neighbors on its own). Settling isn't here — it's a velocity addition in\n// *_FORWARD_SHADER/*_CORRECT_SHADER.\nexport const MEDIUM_CONDENSATE_REACT_SHADER = `\nuniform shader u_transported;\nuniform shader u_vaporTransported;\nuniform shader u_dye;\nuniform float u_dt;\nuniform float u_condensationRate;\nuniform float u_condensationFloor;\nuniform float u_evaporationRate;\nuniform float u_spreadRate;\nuniform float u_totalScale;\n\nhalf4 main(float2 xy) {\n float prevCondensate = float(u_transported.eval(float2(xy.x, u_transportedSize.y - xy.y)).r);\n float3 vaporAtXy = float3(u_vaporTransported.eval(float2(xy.x, u_vaporTransportedSize.y - xy.y)).rgb);\n float vaporTotal = vaporAtXy.r + vaporAtXy.g + vaporAtXy.b;\n\n float excess = max(0.0, vaporTotal - u_condensationFloor);\n float condensed = excess * (1.0 - exp(-u_condensationRate * u_dt));\n float condKeep = exp(-u_evaporationRate * u_dt);\n float afterReaction = prevCondensate * condKeep + condensed;\n\n // Growth is an Euler step separate from transport: the neighbors are sampled at THIS cell's own\n // xy in the ORIGINAL (pre-transport-this-frame) field u_dye, not the already\n // transported/corrected value. On a non-uniform flow, warping compresses the domain in some\n // places and stretches it in others, and blurring at a warped point stacks those distortions —\n // the error accumulates frame over frame (measured: condensate steadily gained about 5% extra\n // water alongside its own mass growth). Right on the grid, with no warp, the same blur is an\n // honest averaging kernel that conserves the sum on a periodic domain.\n float2 selfSrc = float2(xy.x, u_dyeSize.y - xy.y);\n float n0 = float(u_dye.eval(float2(selfSrc.x + 1.0, selfSrc.y)).r);\n float n1 = float(u_dye.eval(float2(selfSrc.x - 1.0, selfSrc.y)).r);\n float n2 = float(u_dye.eval(float2(selfSrc.x, selfSrc.y - 1.0)).r);\n float n3 = float(u_dye.eval(float2(selfSrc.x, selfSrc.y + 1.0)).r);\n float neighborAvg = (n0 + n1 + n2 + n3) * 0.25;\n float spreadWeight = 1.0 - exp(-u_spreadRate * u_dt);\n float grown = mix(afterReaction, neighborAvg, spreadWeight) * u_totalScale;\n\n return half4(half3(grown, 0.0, 0.0), half(1.0));\n}\n`;\n\n// Tracks are not this layer's water: an event stamps a burst (`emit-shader.ts`), and it fades on\n// its own. Decay is a property of THIS layer, so it lives here rather than in the vapor/condensate\n// passes. Track transport stays a single backtrace: it doesn't need MacCormack — tracks aren't\n// conserved, they live for seconds and their own decay extinguishes them before numerical\n// diffusion becomes noticeable.\nexport const MEDIUM_TRACK_ADVECT_SHADER = `\n${MEDIUM_ADVECT_UNIFORMS}\nuniform float u_decay;\n\nhalf4 main(float2 xy) {\n${MEDIUM_ADVECT_VELOCITY}\n float2 back = xy - vel * u_dt;\n half4 prev = u_dye.eval(float2(back.x, u_dyeSize.y - back.y));\n float3 decayed = float3(prev.rgb) * exp(-u_decay * u_dt);\n return half4(half3(decayed), half(1.0));\n}\n`;\n","import { VG_OKLAB_TO_SRGB } from './oklab';\nimport { VG_VALUE_NOISE } from './noise';\n\n// Composites vapor, condensate and track density into color — DIRECTLY into the buffer the lens\n// samples: this is not a separate pass drawn over the glass, it fills the same contentTexture an\n// ordinary 2D scene would, just from the GPU side.\n//\n// The mix happens in Oklab: two saturated channels averaged in raw RGB collapse into muddy gray;\n// in Oklab, lightness and chroma don't bleed into each other, and the sum stays vivid. The palette\n// arrives already in Oklab (`u_lab0/1/2`, `u_labBg`, `u_labCondensate`) — the forward direction\n// (sRGB → Oklab) is computed once per frame by `srgbToOklab` in `web/medium.ts`, not here per pixel.\n//\n// `u_dyeScale` converts a frame coordinate into SIMULATION-GRID pixels — deliberately coarser than\n// the frame — and only here, once, does the resolution become what the eye actually sees again,\n// through `.eval()`'s bilinear sample. NO extra Y flip here, unlike the grid-to-grid reads in\n// advect-shader.ts: those flip because they read a buffer through `.eval()` while still inside the\n// same top-down \"xy\" frame the buffer was written in. This is the one read that leaves that frame\n// for the screen's own — `xy` here already is screen space (this shader's entry point is\n// transpiled exactly like the lens/surface shaders), so the coordinate handed to `u_vapor` etc.\n// only needs the grid/frame resolution scale, not another flip. Measured with a headless render:\n// adding the extra flip (as a very early version of this file did) made settled condensate drift\n// toward the TOP of the screen instead of the bottom.\n//\n// Three buffers, not one: vapor is conserved, condensate transitions from vapor and back\n// (`MEDIUM_VAPOR_REACT_SHADER`/`MEDIUM_CONDENSATE_REACT_SHADER`, see `advect-shader.ts`), tracks\n// fade out (`MEDIUM_TRACK_ADVECT_SHADER`) — a channel's density for the mix is the sum of vapor and\n// tracks, while condensate mixes separately (it carries its own, near-white, only lightly tinted\n// paint).\n//\n// DEPTH: a far plane is not a second simulation — it is the SAME vapor/condensate buffers, read a\n// second time at a different scale and offset (see MEDIUM_DEFAULTS.depthFarScale/depthFarOffsetFrac\n// — u_farOffset itself arrives already resolved to grid-px, see depthFarOffsetPx in params.ts).\n// Reading the identical evolving field through two different \"lenses\" gives parallax and finer\n// far-plane structure from one number (`depthFarScale`) with no extra grid to step, and an 8-tap\n// ring blur gives the far plane aerial perspective's blur AND lower contrast from one mechanism\n// (a spatial average cannot raise local variance). Tracks are NOT re-sampled per plane — a track's\n// own depth is baked into its stamped width/intensity at emission time (`dynamics.ts`), not into a\n// second read here.\n//\n// The far plane folds into the SAME weighted-average-in-Oklab this file already used for species:\n// its weights simply add to the near plane's before the one division, exactly the technique species\n// already use to combine three channels without the sum ever pulling toward gray.\n// Cubic B-spline reconstruction from 4 bilinear taps (Sigg & Hadwiger 2005) — the standard trick\n// that gets a smooth surface out of the grid's own bilinear filtering instead of 16 point samples.\n// Needed because check-medium.mjs's isotropy gate measures it directly: at the product's default\n// coarse grid (74x37 at 1920x952), plain bilinear plus this shader's own nonlinear Oklab mix shows\n// the grid's cells as squares with staircase edges (see the gate's report for the measured ratio).\n// `vgCubicWeights` computes the four B-spline basis weights at fractional position `v`; each of\n// the two `vgBicubicN` functions below is the SAME sampling formula against a DIFFERENT texture —\n// the transpiler's `.eval()` rewrite (vireglass's targets/glsl.ts) matches call sites by the\n// literal declared uniform name, not by a function parameter, so a texture can't be passed in as an\n// argument; the formula is instead generated once per texture name by `vgBicubicSampler` in JS,\n// the same way noise.ts's octave text is generated from MEDIUM_TIME_OCTAVES rather than retyped.\n// Applied to vapor and condensate (the two fields that make up the visible \"gas\" body); NOT to the\n// far plane, which is already blurred (a spatial average already hides blockiness on its own), or\n// to tracks (thin, high-contrast, already-sharp stamps — bicubic softening would blur the \"fresh\n// ionization\" look the head/tail intensity split exists for, at a cost with no matching benefit).\nconst VG_CUBIC_WEIGHTS = `\nfloat4 vgCubicWeights(float v) {\n float4 n = float4(1.0, 2.0, 3.0, 4.0) - v;\n float4 s = n * n * n;\n float x = s.x;\n float y = s.y - 4.0 * s.x;\n float z = s.z - 4.0 * s.y + 6.0 * s.x;\n float w = 6.0 - x - y - z;\n return float4(x, y, z, w) * (1.0 / 6.0);\n}\n`;\n\nfunction vgBicubicSampler(fnName: string, textureName: string): string {\n return `\nhalf4 ${fnName}(float2 px) {\n float2 shifted = px - 0.5;\n float2 fxy = fract(shifted);\n float2 base = shifted - fxy;\n float4 xw = vgCubicWeights(fxy.x);\n float4 yw = vgCubicWeights(fxy.y);\n float2 sx = float2(xw.x + xw.y, xw.z + xw.w);\n float2 sy = float2(yw.x + yw.y, yw.z + yw.w);\n float2 offsetX = float2(base.x - 0.5 + xw.y / sx.x, base.x + 1.5 + xw.w / sx.y);\n float2 offsetY = float2(base.y - 0.5 + yw.y / sy.x, base.y + 1.5 + yw.w / sy.y);\n half4 s00 = ${textureName}.eval(float2(offsetX.x, offsetY.x));\n half4 s10 = ${textureName}.eval(float2(offsetX.y, offsetY.x));\n half4 s01 = ${textureName}.eval(float2(offsetX.x, offsetY.y));\n half4 s11 = ${textureName}.eval(float2(offsetX.y, offsetY.y));\n float wx = sx.x / (sx.x + sx.y);\n float wy = sy.x / (sy.x + sy.y);\n return mix(mix(s11, s01, wx), mix(s10, s00, wx), wy);\n}\n`;\n}\n\n// LIGHT: the gas above is the OLD \"colored vapor\" mechanism — kept exactly as it was\n// (still driving the isotropy/contrast/decorrelation/parallax/gravity gates in check-medium.mjs,\n// none of which know about lights) so it stays a valid, near-neutral ambient base by default\n// (`channelColors` defaults to a near-black family — see params.ts). What actually reads as the\n// reference does — a dark volume where light is a separate, visible thing — is ADDITIVE on top of\n// it, in linear light, so a rig that never binds the uniforms below (their GL default is 0) renders\n// bit-for-bit as before.\n//\n// A light is a soft lobe from a fixed point (`vgLightAmount`): position/direction arrive already in the\n// SAME up-is-positive-y fraction space `gravityBottomBoost` above uses, converted to real content\n// pixels here so a circular pool of light stays circular regardless of the frame's aspect ratio\n// (distance is normalized by height alone, not by each axis separately, for the same reason).\n// `u_light0..2` are flat, fixed-slot uniforms, not an array — `setUniform` (vireglass/web) has no\n// array form, only scalars up to vec4 (the same reason species already use `u_lab0/1/2` instead of\n// one array uniform); an unused slot is simply intensity 0.\n//\n// `channelColors` used to be the species' hue; now `u_channelScatter` is how strongly each species\n// SCATTERS the lights' color instead — hue moved to the lights, species are what they scatter, not\n// what they tint (see params.ts's `channelScatter`).\n\nconst VG_LIGHT = `\nfloat vgLightAmount(float2 xy, float2 lightPosFrac, float2 lightDir, float cosCone, float falloff, float2 resolution, float seed) {\n float2 rel = xy - lightPosFrac * resolution;\n // A cone of 180° or more is an edge strip: light spreads from a line, not a point.\n if (cosCone <= -0.999) {\n float depth = max(dot(rel, lightDir), 0.0);\n return exp(-(depth / max(resolution.y, 1.0)) * falloff);\n }\n float dist = length(rel);\n float2 dirN = dist > 1e-4 ? rel / dist : lightDir;\n float cosAngle = dot(dirN, lightDir);\n // A soft lobe with half intensity at the cone angle and no boundary at all: a lamp in haze has\n // no drawn edge.\n float lobe = log(0.5) / log(clamp(cosCone, 0.05, 0.999));\n float edge = pow(max(cosAngle, 0.0), lobe);\n float atten = exp(-(dist / max(resolution.y, 1.0)) * falloff);\n if (edge * atten < 0.001) return 0.0;\n // Static shafts inside the beam: brightness varies with the angle around the lamp, so streaks\n // fan out from the source the way light through dusty air does.\n float angle = atan(dirN.x * lightDir.y - dirN.y * lightDir.x, cosAngle);\n float shafts = 0.6 + 0.32 * vgValueNoise(float2(angle * 14.0, seed)) + 0.18 * vgValueNoise(float2(angle * 33.0, seed + 5.3));\n return edge * atten * clamp(shafts, 0.0, 1.0);\n}\n`;\n\nexport const MEDIUM_COMPOSITE_SHADER = `\nuniform shader u_vapor;\nuniform shader u_condensate;\nuniform shader u_track;\nuniform shader u_shadow;\nuniform float2 u_dyeScale;\nuniform float3 u_lab0;\nuniform float3 u_lab1;\nuniform float3 u_lab2;\nuniform float3 u_labBg;\nuniform float3 u_labCondensate;\nuniform float u_baseWeight;\nuniform float u_condensateTint;\nuniform float u_condensateGain;\nuniform float u_farScale;\nuniform float2 u_farOffset;\nuniform float u_farBlurRadius;\nuniform float u_farWeight;\nuniform float u_gravityBoost;\nuniform float u_gravityBoostStart;\nuniform float3 u_channelScatter;\nuniform float2 u_light0Pos;\nuniform float2 u_light0Dir;\nuniform float u_light0CosCone;\nuniform float u_light0Falloff;\nuniform float3 u_light0Color;\nuniform float u_light0Intensity;\nuniform float2 u_light1Pos;\nuniform float2 u_light1Dir;\nuniform float u_light1CosCone;\nuniform float u_light1Falloff;\nuniform float3 u_light1Color;\nuniform float u_light1Intensity;\nuniform float2 u_light2Pos;\nuniform float2 u_light2Dir;\nuniform float u_light2CosCone;\nuniform float u_light2Falloff;\nuniform float3 u_light2Color;\nuniform float u_light2Intensity;\nuniform float u_trackLightFloor;\n\n${VG_OKLAB_TO_SRGB}\n${VG_CUBIC_WEIGHTS}\n${vgBicubicSampler('vgBicubicVapor', 'u_vapor')}\n${vgBicubicSampler('vgBicubicCondensate', 'u_condensate')}\n${VG_VALUE_NOISE}\n${VG_LIGHT}\n\nhalf4 main(float2 xy) {\n float2 baseSrc = xy * u_dyeScale;\n\n // Near plane — the calibrated look lives here. Bicubic (see the file header) on vapor and\n // condensate; track stays a plain bilinear read.\n half4 vapor = vgBicubicVapor(baseSrc);\n half4 condensate = vgBicubicCondensate(baseSrc);\n half4 track = u_track.eval(baseSrc);\n\n // Far plane — the identical field, scaled and offset (see the file header). An 8-tap ring, not\n // the near plane's ±x/±y box: axis-aligned taps read the SAME column/row on both sides of any\n // residual axis-aligned structure (a seam, a resize artifact), which is exactly how a straight\n // line gets doubled into four parallel copies instead of softened away — measured directly via\n // check-medium.mjs's isotropy gate (see its report). The ring is rotated a half-step (22.5°) off\n // the axes so no tap direction ever coincides with a grid row or column. Radius stays in the same\n // far-plane texture-space unit as before (u_farBlurRadius, grid-px) — only the tap PATTERN\n // changed, not what it measures in.\n float2 farSrc = baseSrc * u_farScale + u_farOffset;\n float2 ring0 = float2( 0.9238795, 0.3826834) * u_farBlurRadius;\n float2 ring1 = float2( 0.3826834, 0.9238795) * u_farBlurRadius;\n float2 ring2 = float2(-0.3826834, 0.9238795) * u_farBlurRadius;\n float2 ring3 = float2(-0.9238795, 0.3826834) * u_farBlurRadius;\n float2 ring4 = float2(-0.9238795, -0.3826834) * u_farBlurRadius;\n float2 ring5 = float2(-0.3826834, -0.9238795) * u_farBlurRadius;\n float2 ring6 = float2( 0.3826834, -0.9238795) * u_farBlurRadius;\n float2 ring7 = float2( 0.9238795, -0.3826834) * u_farBlurRadius;\n half4 vaporFar = (u_vapor.eval(farSrc + ring0) + u_vapor.eval(farSrc + ring1) +\n u_vapor.eval(farSrc + ring2) + u_vapor.eval(farSrc + ring3) +\n u_vapor.eval(farSrc + ring4) + u_vapor.eval(farSrc + ring5) +\n u_vapor.eval(farSrc + ring6) + u_vapor.eval(farSrc + ring7)) * half4(0.125);\n half4 condensateFar = (u_condensate.eval(farSrc + ring0) + u_condensate.eval(farSrc + ring1) +\n u_condensate.eval(farSrc + ring2) + u_condensate.eval(farSrc + ring3) +\n u_condensate.eval(farSrc + ring4) + u_condensate.eval(farSrc + ring5) +\n u_condensate.eval(farSrc + ring6) + u_condensate.eval(farSrc + ring7)) * half4(0.125);\n\n // Gravity's \"denser layer at the bottom of the chamber\" — a compositing-only boost (see\n // MEDIUM_DEFAULTS.gravityBottomBoost): it scales how density already in the conserved buffers is\n // DISPLAYED, never the buffers themselves, so it can't threaten water conservation. Shared by\n // both planes: the chamber's floor is denser everywhere along its depth, not just up close.\n //\n // Verified with a headless render, not assumed: ramping this with xy.y directly lit up the TOP\n // of the frame, not the bottom (contentTexture's own orientation contract flips y once more\n // between this pass and the final scene — see the file header) — hence the 1.0 - below.\n float bottomFrac = 1.0 - xy.y / u_resolution.y;\n float bottomT = smoothstep(u_gravityBoostStart, 1.0, bottomFrac);\n float bottomBoost = 1.0 + u_gravityBoost * bottomT;\n\n float gasR = (float(vapor.r) + float(vaporFar.r) * u_farWeight) * bottomBoost;\n float gasG = (float(vapor.g) + float(vaporFar.g) * u_farWeight) * bottomBoost;\n float gasB = (float(vapor.b) + float(vaporFar.b) * u_farWeight) * bottomBoost;\n float w0 = gasR + float(track.r);\n float w1 = gasG + float(track.g);\n float w2 = gasB + float(track.b);\n float wBg = u_baseWeight;\n\n // A faint condensate hint from the LOCAL vapor, not its own color (droplets scatter light\n // rather than tint it) — a fraction of the way from white to the vapor tone sampled right here.\n float vaporLocal = max(w0 + w1 + w2, 1e-4);\n float3 vaporHue = (u_lab0 * w0 + u_lab1 * w1 + u_lab2 * w2) / vaporLocal;\n float3 condensateLab = mix(u_labCondensate, vaporHue, u_condensateTint);\n float wCondensate =\n (float(condensate.r) + float(condensateFar.r) * u_farWeight) * u_condensateGain * bottomBoost;\n\n float total = max(w0 + w1 + w2 + wBg + wCondensate, 1e-4);\n float3 mixLab =\n (u_labBg * wBg + u_lab0 * w0 + u_lab1 * w1 + u_lab2 * w2 + condensateLab * wCondensate) / total;\n // The dark ambient base — this is where the OLD mechanism stops (see the file header on\n // VG_LIGHT): unclamped here, clamped once at the very end, after the lit layer adds in.\n float3 rgbLinearBase = vgOklabToLinear(mixLab);\n\n // Spotlights reach this point dimmed by the gas in front of them (shadow-shader.ts).\n half4 shadow = u_shadow.eval(baseSrc);\n float light0 = vgLightAmount(xy, u_light0Pos, u_light0Dir, u_light0CosCone, u_light0Falloff, u_resolution, 1.7) * u_light0Intensity;\n float light1 = vgLightAmount(xy, u_light1Pos, u_light1Dir, u_light1CosCone, u_light1Falloff, u_resolution, 4.1) * u_light1Intensity\n * float(shadow.r);\n float light2 = vgLightAmount(xy, u_light2Pos, u_light2Dir, u_light2CosCone, u_light2Falloff, u_resolution, 8.3) * u_light2Intensity\n * float(shadow.g);\n float3 illum = u_light0Color * light0 + u_light1Color * light1 + u_light2Color * light2;\n\n float totalScatter = gasR * u_channelScatter.x + gasG * u_channelScatter.y + gasB * u_channelScatter.z;\n float fogDensity = totalScatter + wCondensate;\n float mist = 0.006 + fogDensity * 0.2;\n\n // TRACKS: lit the same way as the mist, plus a floor so a track already on screen never vanishes\n // entirely just because it drifted out of a cone (a fresh ionization trail reads brighter than\n // ambient mist in a real chamber). The floor is added to ITS OWN color, not multiplied into illum:\n // illum is exactly [0,0,0] in a fully unlit spot (every light's amount is 0 there), so a floor\n // multiplied by illum would vanish exactly where it exists to help — it needs a color of its own\n // (a neutral, self-luminous glow) rather than borrowing whichever light happens to be nearby.\n float trackDensity = max(float(track.r), max(float(track.g), float(track.b)));\n float3 trackGlow = (1.0 - exp(-trackDensity * 0.4)) * (illum + float3(u_trackLightFloor)) * 0.08;\n\n float3 rgbLinear = rgbLinearBase * 0.025 + illum * mist + trackGlow;\n rgbLinear = float3(1.0) - exp(-rgbLinear * 1.0);\n float3 srgb = vgLinearToSrgb(rgbLinear);\n return half4(half3(srgb), half(1.0));\n}\n`;\n","import { VG_VALUE_NOISE } from './noise';\n\n// Stamps a track into the vapor: one pass that stamps a density burst DIRECTLY into the dye buffer\n// — the same one that carries the curl-noise field and that composite-shader.ts reads. From there\n// the track lives and fades through exactly the same code as the gas: advection carries and spins\n// it, exponential decay extinguishes it. It has no position of its own and no lifetime timer here —\n// the track's entire physics is the shape of this ONE stamped frame.\n//\n// The head (s=1, at u_head) is sharp, narrow, bright — fresh ionization. The tail (s=0, at\n// u_source) is wider, dimmer and has settled downward — those droplets had time to grow earlier.\n// The path's noise-driven spread also grows toward the tail and fades toward the head, so the\n// thread stays straight and crisp at its leading end.\n//\n// Stamped equally into r/g/b (no per-track color mask): a track carries no color of its own, it is\n// lit the same way as the mist around it (`MEDIUM_COMPOSITE_SHADER`) — species/lights own hue now,\n// not which random channel a track happened to land on.\nexport const MEDIUM_EMIT_SHADER = `\nuniform float2 u_source;\nuniform float2 u_head;\nuniform float u_headWidth;\nuniform float u_tailWidth;\nuniform float u_raggedAmp;\nuniform float u_raggedFreq;\nuniform float u_settle;\nuniform float u_tailDim;\nuniform float u_intensity;\nuniform float u_seed;\n\n${VG_VALUE_NOISE}\n\nhalf4 main(float2 xy) {\n float2 dir = u_head - u_source;\n float len = max(length(dir), 1.0);\n float2 side = float2(-dir.y, dir.x) / len;\n\n float best = 0.0;\n const int STEPS = 12;\n for (int i = 0; i < STEPS; i++) {\n float s = float(i) / float(STEPS - 1);\n float tailness = 1.0 - s;\n float ragged = u_raggedAmp * tailness * vgValueNoise(float2(s * u_raggedFreq + u_seed, u_seed * 3.7));\n float2 p = mix(u_source, u_head, s) + side * ragged + float2(0.0, u_settle * tailness * tailness);\n float w = max(mix(u_tailWidth, u_headWidth, s), 0.5);\n float amp = mix(u_tailDim, 1.0, s);\n float d = length(xy - p);\n float v = amp * exp(-(d * d) / (2.0 * w * w));\n best = max(best, v);\n }\n\n float3 dye = float3(best * u_intensity);\n return half4(half3(dye), half(1.0));\n}\n`;\n","// How much of each spotlight reaches a grid cell through the gas in front of it: denser gas throws\n// soft shadow streaks behind itself, so the beams are shaped by the gas rather than drawn. Computed\n// once per grid cell, not per screen pixel — the gas is no finer than the grid anyway.\n//\n// Coordinates are the composite's grid space (`xy * u_dyeScale` there, y up): the written row\n// comes out flipped relative to that, hence the flip on entry. Samples outside the grid count as\n// clear air, so the REPEAT border never lets the floor's gas shade the ceiling.\nexport const MEDIUM_SHADOW_SHADER = `\nuniform shader u_vapor;\nuniform shader u_condensate;\nuniform float u_condensateGain;\nuniform float2 u_light1Grid;\nuniform float2 u_light2Grid;\nuniform float u_extinction;\n\nfloat vgOpticalDepth(float2 p, float2 lightP) {\n float2 toLight = lightP - p;\n float optical = 0.0;\n for (int i = 1; i <= 24; i++) {\n float2 q = p + toLight * (float(i) / 24.0);\n float inside = step(0.0, q.x) * step(q.x, u_resolution.x) * step(0.0, q.y) * step(q.y, u_resolution.y);\n half4 v = u_vapor.eval(q);\n half4 c = u_condensate.eval(q);\n optical += inside * (float(v.r + v.g + v.b) + float(c.r) * u_condensateGain);\n }\n return optical * length(toLight) / 24.0 / max(u_resolution.y, 1.0);\n}\n\nhalf4 main(float2 xy) {\n float2 p = float2(xy.x, u_resolution.y - xy.y);\n float t1 = exp(-vgOpticalDepth(p, u_light1Grid) * u_extinction);\n float t2 = exp(-vgOpticalDepth(p, u_light2Grid) * u_extinction);\n return half4(half(t1), half(t2), half(0.0), half(1.0));\n}\n`;\n","// The crisp, screen-space track layer's CPU half: a live-track list fed by the same emissions the\n// grid stamp (`emit-shader.ts`) receives. No GL here, so the aging/drift/fade math is testable\n// without a WebGL2 context; `web/track-layer-gl.ts` turns the list into an instanced draw.\nimport {\n MEDIUM_TRACK_LAYER_BURST_ACCENT,\n MEDIUM_TRACK_LAYER_BURST_SPREAD,\n MEDIUM_TRACK_LAYER_CURL_PUSH,\n MEDIUM_TRACK_LAYER_FADE_GAMMA,\n MEDIUM_TRACK_LAYER_GRAVITY_SAG,\n MEDIUM_TRACK_LAYER_LIFE_SECONDS,\n MEDIUM_TRACK_LAYER_MAX_TRACKS,\n type VireUIKitMediumEmission,\n} from '../medium';\nimport { vgPotentialJS } from '../medium/noise';\n\n/** One track alive in the crisp layer: the original emission (never mutated) plus its age and\n * drift. */\nexport type VireUIKitLiveTrack = {\n emission: VireUIKitMediumEmission;\n /** Seconds since the track appeared. Negative while a burst ray is still waiting for its turn. */\n age: number;\n /** Accumulated rigid drift, as a fraction of `min(contentWidth, contentHeight)`. */\n driftX: number;\n driftY: number;\n};\n\n/** `(1 - age/life) ** gamma`, reaching exactly 0 at `age >= life`, so dropping a track never pops. */\nexport function trackLayerFade(age: number, life: number): number {\n if (life <= 0) return 0;\n const t = Math.min(Math.max(age / life, 0), 1);\n return (1 - t) ** MEDIUM_TRACK_LAYER_FADE_GAMMA;\n}\n\n/** Diffusion: droplets spread sideways as `sqrt(age)`, the way an ink line blurs. */\nexport function trackLayerSigmaMul(age: number, broaden: number): number {\n return Math.sqrt(1 + broaden * Math.max(age, 0));\n}\n\nfunction hash01(n: number): number {\n const s = Math.sin(n * 91.3458) * 47453.5453;\n return s - Math.floor(s);\n}\n\nfunction curlVelocityJS(px: number, py: number, phase: number): readonly [number, number] {\n const e = 0.6;\n const px1 = vgPotentialJS(px + e, py, phase);\n const px0 = vgPotentialJS(px - e, py, phase);\n const py1 = vgPotentialJS(px, py + e, phase);\n const py0 = vgPotentialJS(px, py - e, phase);\n return [(py1 - py0) / (2 * e), -(px1 - px0) / (2 * e)];\n}\n\nconst CURL_SAMPLE_SCALE = 40;\nconst PHASE_WRAP = 10_000;\n\nexport type VireUIKitTrackLayer = {\n /**\n * Ages and drifts every live track by `dt`, drops anything past its own life, appends\n * `emissions` as fresh tracks and caps the list at `MEDIUM_TRACK_LAYER_MAX_TRACKS` (oldest out).\n * Alpha rays arriving in one call are one burst: the first few appear at once, the rest are born\n * over `MEDIUM_TRACK_LAYER_BURST_SPREAD` seconds.\n */\n step(dt: number, emissions: readonly VireUIKitMediumEmission[]): readonly VireUIKitLiveTrack[];\n};\n\nexport function createTrackLayer(): VireUIKitTrackLayer {\n let tracks: VireUIKitLiveTrack[] = [];\n let phase = 0;\n\n function step(dt: number, emissions: readonly VireUIKitMediumEmission[]): readonly VireUIKitLiveTrack[] {\n phase = (phase + dt) % PHASE_WRAP;\n\n const next: VireUIKitLiveTrack[] = [];\n for (const track of tracks) {\n const life = MEDIUM_TRACK_LAYER_LIFE_SECONDS[track.emission.kind];\n const age = track.age + dt;\n if (age >= life) continue;\n if (age <= 0) {\n next.push({ ...track, age });\n continue;\n }\n\n const midX = track.emission.source[0] + Math.cos(track.emission.angle) * track.emission.lengthFrac * 0.5;\n const midY = track.emission.source[1] + Math.sin(track.emission.angle) * track.emission.lengthFrac * 0.5;\n const [curlX, curlY] = curlVelocityJS(midX * CURL_SAMPLE_SCALE, midY * CURL_SAMPLE_SCALE, phase);\n const activeDt = Math.min(dt, age);\n\n next.push({\n emission: track.emission,\n age,\n driftX: track.driftX + curlX * MEDIUM_TRACK_LAYER_CURL_PUSH * activeDt,\n driftY: track.driftY + (curlY * MEDIUM_TRACK_LAYER_CURL_PUSH - MEDIUM_TRACK_LAYER_GRAVITY_SAG) * activeDt,\n });\n }\n let burstIndex = 0;\n for (const emission of emissions) {\n let age = 0;\n if (emission.kind === 'alpha') {\n if (burstIndex >= MEDIUM_TRACK_LAYER_BURST_ACCENT) {\n age = -hash01(emission.seed * 3.17 + burstIndex) * MEDIUM_TRACK_LAYER_BURST_SPREAD;\n }\n burstIndex += 1;\n }\n next.push({ emission, age, driftX: 0, driftY: 0 });\n }\n\n tracks = next.length > MEDIUM_TRACK_LAYER_MAX_TRACKS\n ? next.slice(next.length - MEDIUM_TRACK_LAYER_MAX_TRACKS)\n : next;\n return tracks;\n }\n\n return { step };\n}\n","// The crisp track layer's GPU half: one instanced, additive quad per live track, drawn into the\n// backdrop's target right after the fullscreen composite. Raw GLSL ES 3.0 rather than the\n// transpiled shader dialect, which only draws fullscreen passes; the Android port needs its own path.\nimport { bindTextureAt, locationCache, setUniform } from 'vireglass/web';\nimport {\n MEDIUM_TRACK_LAYER_LIFE_SECONDS,\n MEDIUM_TRACK_LAYER_LOOK,\n type VireUIKitMediumLight,\n} from '../medium';\nimport { trackLayerFade, trackLayerSigmaMul, type VireUIKitLiveTrack } from './track-layer';\n\nconst TRACK_LAYER_VERTEX_SOURCE = `#version 300 es\nlayout(location = 0) in vec2 a_corner;\nlayout(location = 1) in vec4 a_geom;\nlayout(location = 2) in vec4 a_span;\nlayout(location = 3) in vec4 a_path;\nlayout(location = 4) in vec4 a_grain;\n\nuniform vec2 u_resolution;\nuniform float u_trackLightFloor;\nuniform sampler2D u_shadow;\nuniform vec2 u_light0Pos;\nuniform vec2 u_light0Dir;\nuniform float u_light0CosCone;\nuniform float u_light0Falloff;\nuniform vec3 u_light0Color;\nuniform float u_light0Intensity;\nuniform vec2 u_light1Pos;\nuniform vec2 u_light1Dir;\nuniform float u_light1CosCone;\nuniform float u_light1Falloff;\nuniform vec3 u_light1Color;\nuniform float u_light1Intensity;\nuniform vec2 u_light2Pos;\nuniform vec2 u_light2Dir;\nuniform float u_light2CosCone;\nuniform float u_light2Falloff;\nuniform vec3 u_light2Color;\nuniform float u_light2Intensity;\n\nout vec2 v_local;\nflat out vec4 v_span;\nflat out vec4 v_path;\nflat out vec4 v_grain;\nflat out float v_reach;\nout vec3 v_illum;\n\nfloat vgHash21(vec2 p) {\n float h = dot(p, vec2(127.1, 311.7));\n return fract(sin(h) * 43758.5453123);\n}\n\nfloat vgValueNoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n float a = vgHash21(i);\n float b = vgHash21(i + vec2(1.0, 0.0));\n float c = vgHash21(i + vec2(0.0, 1.0));\n float d = vgHash21(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y) * 2.0 - 1.0;\n}\n\n// Keep in step with vgLightAmount in composite-shader.ts.\nfloat vgLightAmount(vec2 xy, vec2 lightPosFrac, vec2 lightDir, float cosCone, float falloff, vec2 resolution, float seed) {\n vec2 rel = xy - lightPosFrac * resolution;\n if (cosCone <= -0.999) {\n float depth = max(dot(rel, lightDir), 0.0);\n return exp(-(depth / max(resolution.y, 1.0)) * falloff);\n }\n float dist = length(rel);\n vec2 dirN = dist > 1e-4 ? rel / dist : lightDir;\n float cosAngle = dot(dirN, lightDir);\n float lobe = log(0.5) / log(clamp(cosCone, 0.05, 0.999));\n float edge = pow(max(cosAngle, 0.0), lobe);\n float atten = exp(-(dist / max(resolution.y, 1.0)) * falloff);\n if (edge * atten < 0.001) return 0.0;\n float angle = atan(dirN.x * lightDir.y - dirN.y * lightDir.x, cosAngle);\n float shafts = 0.6 + 0.32 * vgValueNoise(vec2(angle * 14.0, seed)) + 0.18 * vgValueNoise(vec2(angle * 33.0, seed + 5.3));\n return edge * atten * clamp(shafts, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 dir = a_geom.zw;\n vec2 side = vec2(-dir.y, dir.x);\n // Beads widen sigma by up to 20%; 3.5 sigma leaves under 1% of the core outside the quad.\n float reach = max(a_span.z, a_span.w) * 1.2 * 3.5 + 1.5;\n float margin = reach + a_path.x * 1.7;\n float u = mix(a_span.y - reach, a_span.x + reach, a_corner.y);\n float s = a_corner.x * margin;\n vec2 pos = a_geom.xy + dir * u + side * s;\n v_local = vec2(u, s);\n v_span = a_span;\n v_path = a_path;\n v_grain = a_grain;\n v_reach = reach;\n // Lit per corner: light varies over hundreds of px, a track is a few px wide. The backdrop\n // target stores the scene top row at y = 0; lights use y up.\n vec2 xy = vec2(pos.x, u_resolution.y - pos.y);\n // The same gas shadow the composite applies to the spotlights (shadow-shader.ts, y up).\n vec4 shadow = texture(u_shadow, xy / u_resolution);\n float light0 = vgLightAmount(xy, u_light0Pos, u_light0Dir, u_light0CosCone, u_light0Falloff, u_resolution, 1.7) * u_light0Intensity;\n float light1 = vgLightAmount(xy, u_light1Pos, u_light1Dir, u_light1CosCone, u_light1Falloff, u_resolution, 4.1) * u_light1Intensity * shadow.r;\n float light2 = vgLightAmount(xy, u_light2Pos, u_light2Dir, u_light2CosCone, u_light2Falloff, u_resolution, 8.3) * u_light2Intensity * shadow.g;\n v_illum = u_light0Color * light0 + u_light1Color * light1 + u_light2Color * light2 + vec3(u_trackLightFloor);\n gl_Position = vec4((pos / u_resolution) * 2.0 - 1.0, 0.0, 1.0);\n}\n`;\n\n// Track-local coordinates: x along the track from its source in px, y across it in px. A track\n// is a Gaussian core whose width and brightness bead along its length, plus stray droplets\n// scattered around it; all noise is 1D along the track, so nothing lines up on a screen grid.\nconst TRACK_LAYER_FRAGMENT_SOURCE = `#version 300 es\nprecision highp float;\n\nin vec2 v_local;\nflat in vec4 v_span;\nflat in vec4 v_path;\nflat in vec4 v_grain;\nflat in float v_reach;\nin vec3 v_illum;\n\nout vec4 fragColor;\n\nfloat vgHash(float a, float b) {\n uint x = floatBitsToUint(a) * 0x9E3779B1u ^ (floatBitsToUint(b) + 0x7F4A7C15u);\n x ^= x >> 16;\n x *= 0x7FEB352Du;\n x ^= x >> 15;\n x *= 0x846CA68Bu;\n x ^= x >> 16;\n return float(x) * (1.0 / 4294967295.0);\n}\n\nfloat vgNoise1(float x, float salt) {\n float i = floor(x);\n float f = fract(x);\n float u = f * f * (3.0 - 2.0 * f);\n return mix(vgHash(i, salt), vgHash(i + 1.0, salt), u);\n}\n\nvoid main() {\n float len = v_span.x;\n float start = v_span.y;\n float sigma0 = v_span.z;\n float sigma1 = v_span.w;\n float wiggleAmp = v_path.x;\n float wiggleFreq = v_path.y;\n float amp = v_path.z;\n float seed = v_path.w;\n float beadPx = v_grain.x;\n float dropPx = v_grain.y;\n float dropChance = v_grain.z;\n float blur = v_grain.w;\n\n float u = v_local.x;\n float s = v_local.y;\n float t = clamp((u - start) / max(len - start, 1.0), 0.0, 1.0);\n\n float wiggle = 0.0;\n if (wiggleAmp > 0.0) {\n float x = u * wiggleFreq;\n wiggle = wiggleAmp * smoothstep(0.0, 0.2, t) *\n ((vgNoise1(x, seed) - 0.5) * 2.0 + (vgNoise1(x * 3.1, seed + 1.0) - 0.5) * 0.7);\n }\n float across = s - wiggle;\n if (abs(across) > v_reach) discard;\n\n float bead = vgNoise1(u / beadPx, seed + 2.0);\n // Blur (age, defocus) washes the beads out rather than stretching them into ribs.\n float sigma = mix(sigma0, sigma1, t * t) * (1.0 + (bead - 0.5) * 0.4 / blur);\n float beyond = u < start ? start - u : max(u - len, 0.0);\n float core = exp(-0.5 * (across * across + beyond * beyond) / (sigma * sigma));\n float along = 1.0 - 0.35 / blur * vgNoise1(u / (beadPx * 2.3), seed + 3.0);\n\n float drops = 0.0;\n float cell0 = floor(u / dropPx);\n for (int k = -1; k <= 1; k++) {\n float c = cell0 + float(k);\n if (vgHash(c, seed + 4.0) > dropChance) continue;\n float cu = (c + vgHash(c, seed + 5.0)) * dropPx;\n if (cu < start || cu > len) continue;\n float cs = (vgHash(c, seed + 6.0) + vgHash(c, seed + 7.0) - 1.0) * 1.7 * sigma + wiggle;\n float r = max(0.7, sigma * mix(0.35, 0.8, vgHash(c, seed + 8.0)));\n float dx = u - cu;\n float dy = s - cs;\n drops += mix(0.35, 1.0, vgHash(c, seed + 9.0)) * exp(-0.5 * (dx * dx + dy * dy) / (r * r));\n }\n\n float lum = amp * (core * along + drops * 0.7 / (blur * blur));\n if (lum < 0.002) discard;\n\n vec3 glow = lum * v_illum;\n fragColor = vec4(vec3(1.0) - exp(-glow), 1.0);\n}\n`;\n\nconst FLOATS_PER_INSTANCE = 16;\n\n/** Where near tracks start to defocus, on the emission's 0 (far) … 1 (near) depth. */\nconst DEFOCUS_FROM_DEPTH = 0.7;\n/** Sigma multiplier of the nearest track: large and soft, like an out-of-focus droplet line. */\nconst DEFOCUS_MAX = 2.4;\n\n/** Fraction-space live tracks → per-instance floats in content px, y down like the emission\n * fractions and the backdrop target (drift is y-up, hence its sign flip). Skips tracks not yet born,\n * fully faded, or everything when `amount <= 0` (the canary `check:medium` uses). Exported for\n * testing without a GPU. */\nexport function buildTrackLayerInstances(\n tracks: readonly VireUIKitLiveTrack[],\n contentWidth: number,\n contentHeight: number,\n amount: number,\n): { data: Float32Array; count: number } {\n const minDim = Math.min(contentWidth, contentHeight);\n const data = new Float32Array(tracks.length * FLOATS_PER_INSTANCE);\n let count = 0;\n if (amount <= 0) return { data, count };\n\n for (const track of tracks) {\n if (track.age < 0) continue;\n const e = track.emission;\n const fade = trackLayerFade(track.age, MEDIUM_TRACK_LAYER_LIFE_SECONDS[e.kind]);\n if (fade <= 1e-4) continue;\n const look = MEDIUM_TRACK_LAYER_LOOK[e.kind];\n\n const sigmaMul = trackLayerSigmaMul(track.age, look.broaden);\n const near = Math.max(0, (e.depth - DEFOCUS_FROM_DEPTH) / (1 - DEFOCUS_FROM_DEPTH));\n const defocus = 1 + (DEFOCUS_MAX - 1) * near * near;\n const sigma0 = Math.max(look.sigmaFrac * minDim, 0.6) * sigmaMul * defocus;\n\n const o = count * FLOATS_PER_INSTANCE;\n data[o + 0] = e.source[0] * contentWidth + track.driftX * minDim;\n data[o + 1] = e.source[1] * contentHeight - track.driftY * minDim;\n data[o + 2] = Math.cos(e.angle);\n data[o + 3] = Math.sin(e.angle);\n const len = e.lengthFrac * minDim;\n data[o + 4] = len;\n data[o + 5] = Math.min(look.startFrac * minDim, len * 0.5);\n data[o + 6] = sigma0;\n data[o + 7] = sigma0 * look.endSigmaMul;\n data[o + 8] = e.raggedFrac > 0 ? look.wiggleFrac * minDim : 0;\n data[o + 9] = e.raggedFreq / Math.max(len, 1);\n data[o + 10] = (e.intensity * look.gain * fade * amount) / (sigmaMul ** 0.7 * defocus);\n data[o + 11] = e.seed;\n data[o + 12] = look.beadFrac * minDim;\n data[o + 13] = look.dropletFrac * minDim;\n data[o + 14] = look.dropletChance;\n data[o + 15] = sigmaMul * defocus;\n count += 1;\n }\n return { data, count };\n}\n\nexport type TrackLayerProgram = {\n /** Draws the instances additively into the currently bound framebuffer. Leaves blending enabled;\n * the renderer restores its own state after the backdrop pass. */\n draw(\n contentWidth: number,\n contentHeight: number,\n data: Float32Array,\n count: number,\n trackLightFloor: number,\n lights: readonly (VireUIKitMediumLight | undefined)[],\n shadow: WebGLTexture,\n ): void;\n destroy(): void;\n};\n\nfunction compile(gl: WebGL2RenderingContext, type: number, source: string, label: string): WebGLShader {\n const shader = gl.createShader(type);\n if (!shader) throw new Error(`vireuikit: track layer ${label} shader creation failed`);\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n throw new Error(`vireuikit: track layer ${label} shader failed:\\n${gl.getShaderInfoLog(shader) ?? ''}`);\n }\n return shader;\n}\n\nexport function createTrackLayerProgram(gl: WebGL2RenderingContext): TrackLayerProgram {\n const vertex = compile(gl, gl.VERTEX_SHADER, TRACK_LAYER_VERTEX_SOURCE, 'vertex');\n const fragment = compile(gl, gl.FRAGMENT_SHADER, TRACK_LAYER_FRAGMENT_SOURCE, 'fragment');\n const program = gl.createProgram();\n if (!program) throw new Error('vireuikit: track layer program creation failed');\n gl.attachShader(program, vertex);\n gl.attachShader(program, fragment);\n gl.linkProgram(program);\n gl.deleteShader(vertex);\n gl.deleteShader(fragment);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n throw new Error(`vireuikit: track layer program failed to link:\\n${gl.getProgramInfoLog(program) ?? ''}`);\n }\n\n const loc = locationCache(gl, program);\n\n // Unit quad as a triangle strip: x across (-1…1), y along (0…1).\n const quadBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, 0, 1, 0, -1, 1, 1, 1]), gl.STATIC_DRAW);\n\n const instanceBuffer = gl.createBuffer();\n const vao = gl.createVertexArray();\n gl.bindVertexArray(vao);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);\n gl.enableVertexAttribArray(0);\n gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n\n const stride = FLOATS_PER_INSTANCE * 4;\n gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);\n for (let location = 1; location <= 4; location += 1) {\n gl.enableVertexAttribArray(location);\n gl.vertexAttribPointer(location, 4, gl.FLOAT, false, stride, (location - 1) * 16);\n gl.vertexAttribDivisor(location, 1);\n }\n gl.bindVertexArray(null);\n\n function bindLight(slot: number, light: VireUIKitMediumLight | undefined): void {\n const position = light?.position ?? [0, 0];\n const direction = light?.direction ?? [0, 1];\n const dirLen = Math.hypot(direction[0], direction[1]) || 1;\n setUniform(gl, loc(`u_light${slot}Pos`), position);\n setUniform(gl, loc(`u_light${slot}Dir`), [direction[0] / dirLen, direction[1] / dirLen]);\n setUniform(gl, loc(`u_light${slot}CosCone`), Math.cos(light?.coneAngle ?? 0));\n setUniform(gl, loc(`u_light${slot}Falloff`), light?.falloff ?? 0);\n setUniform(gl, loc(`u_light${slot}Color`), light?.color ?? [0, 0, 0]);\n setUniform(gl, loc(`u_light${slot}Intensity`), light?.intensity ?? 0);\n }\n\n function draw(\n contentWidth: number,\n contentHeight: number,\n data: Float32Array,\n count: number,\n trackLightFloor: number,\n lights: readonly (VireUIKitMediumLight | undefined)[],\n shadow: WebGLTexture,\n ): void {\n if (count <= 0) return;\n gl.useProgram(program);\n gl.bindVertexArray(vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);\n gl.bufferData(gl.ARRAY_BUFFER, data.subarray(0, count * FLOATS_PER_INSTANCE), gl.DYNAMIC_DRAW);\n\n setUniform(gl, loc('u_resolution'), [contentWidth, contentHeight]);\n setUniform(gl, loc('u_trackLightFloor'), trackLightFloor);\n for (let slot = 0; slot < 3; slot += 1) bindLight(slot, lights[slot]);\n bindTextureAt(gl, 0, shadow, program, 'u_shadow');\n\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE);\n gl.disable(gl.DEPTH_TEST);\n gl.viewport(0, 0, contentWidth, contentHeight);\n gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, count);\n gl.bindVertexArray(null);\n }\n\n function destroy(): void {\n gl.deleteBuffer(quadBuffer);\n gl.deleteBuffer(instanceBuffer);\n gl.deleteVertexArray(vao);\n gl.deleteProgram(program);\n }\n\n return { draw, destroy };\n}\n","// The medium's runtime layer: ping-pong FBOs for the simulation grid and its GPU passes. Shader\n// text and parameters live in the package's model (`../medium`) — this file is WebGL only.\n//\n// Three independent buffers, not one: vapor (a conserved color reserve), condensate (droplets —\n// they grow, settle, and exchange with vapor) and tracks (born from a stamp, fade through decay).\n// Vapor and condensate form ONE conserved sum — water; tracks aren't part of it, and their decay\n// and stamping haven't been folded into that accounting.\nimport { computeMediumSpatialPeriods, type MediumSpatialOctave } from '../medium/noise';\nimport {\n depthFarOffsetPx,\n MEDIUM_COMPOSITE_SHADER,\n MEDIUM_CONDENSATE_CORRECT_SHADER,\n MEDIUM_CONDENSATE_FORWARD_SHADER,\n MEDIUM_CONDENSATE_REACT_SHADER,\n MEDIUM_DEFAULTS,\n MEDIUM_EMIT_SHADER,\n MEDIUM_MAX_LIGHTS,\n MEDIUM_RESAMPLE_SHADER,\n MEDIUM_SEED_SHADER,\n MEDIUM_SHADOW_SHADER,\n MEDIUM_TIME_PERIOD,\n MEDIUM_TRACK_ADVECT_SHADER,\n MEDIUM_VAPOR_CORRECT_SHADER,\n MEDIUM_VAPOR_FORWARD_SHADER,\n MEDIUM_VAPOR_REACT_SHADER,\n srgbToOklab,\n type VireUIKitMediumEmission,\n type VireUIKitMediumLight,\n type VireUIKitMediumParams,\n} from '../medium';\nimport { toGLSL } from 'vireglass';\nimport {\n bindTextureAt,\n createFramebuffer,\n createProgram,\n createTexture,\n drawFullscreenTriangle,\n locationCache,\n setUniform,\n} from 'vireglass/web';\nimport { createTrackLayer, type VireUIKitLiveTrack } from './track-layer';\nimport { buildTrackLayerInstances, createTrackLayerProgram } from './track-layer-gl';\n\nexport type MediumRuntime = {\n /** Recreates the grid on the first call (seeding it); on a later call with a different size,\n * RESAMPLES the old vapor/condensate/track state into the new size instead of reseeding — see\n * `resizeGrid` below for why. A no-op when the size hasn't changed. */\n ensureGrid(gridWidth: number, gridHeight: number): void;\n /** One transport step for all three buffers (vapor and condensate via MacCormack:\n * forward/correct/react, see `advect-shader.ts`; tracks via a single backtrace plus decay),\n * ping-ponging their own FBOs. The field's phase (curl-noise's time axis) is this runtime's own\n * state, accumulated from `dt` — see `u_phase` below — not derived from a caller-supplied clock. */\n step(dt: number, params?: Partial<VireUIKitMediumParams>): void;\n /**\n * Stamps tracks into the TRACK buffer just refreshed by advection — via additive blending,\n * rather than a separate pass or its own position list. From there the same field carries them\n * and their own decay extinguishes them; there is no lifetime timer here.\n */\n emit(emissions: readonly VireUIKitMediumEmission[]): void;\n /**\n * Ages, drifts and caps the crisp screen-space track layer's own live-track list (see\n * `track-layer.ts`) and folds `emissions` into it as fresh tracks — independent of `emit()`\n * above: that one stamps the SOFT grid residue, this one feeds the SHARP geometry `composite`\n * draws over it. Called once per frame, same as `emit`.\n */\n stepTrackLayer(dt: number, emissions: readonly VireUIKitMediumEmission[]): void;\n /**\n * Composites the current density (vapor + condensate + tracks) into a buffer already bound by\n * the caller (the renderer's `contentFbo`) — this is exactly how the medium acts as the second\n * backdrop path: the result lands in the same `contentTexture` the lens samples, not a separate\n * pass drawn over the glass.\n */\n composite(contentWidth: number, contentHeight: number, params?: Partial<VireUIKitMediumParams>): void;\n /** The raw density sum across every cell — a measurement hook, not part of the frame. Balance\n * and structure gates read THIS, not a composited pixel: compositing muddies things with Oklab\n * nonlinearity, and the water (vapor+condensate) sum and contrast need to be measurable on\n * their own. */\n readTotals(): { vapor: number; condensate: number; track: number } | null;\n /** The raw VAPOR grid, at simulation-grid resolution, `data` as r/g/b triples (three species)\n * per cell, not a sum. Used by the structure gate (contrast BETWEEN species across the grid). */\n readVaporGrid(): { cols: number; rows: number; data: number[] } | null;\n /** The raw CONDENSATE grid, at simulation-grid resolution, `data` as one density value per\n * cell (condensate has no color — see `readVaporGrid`'s r/g/b for the contrast). Used by the\n * gravity gate to find the density-weighted row where the mass sits. */\n readCondensateGrid(): { cols: number; rows: number; data: number[] } | null;\n /** The raw TRACK grid, at simulation-grid resolution, `data` as one density value per cell (a\n * track carries no color — see `readCondensateGrid`). Used by the \"sharp at birth, soft later\"\n * gate to measure a stamped track's cross-section directly. */\n readTrackGrid(): { cols: number; rows: number; data: number[] } | null;\n destroy(): void;\n};\n\ntype DyeTarget = { texture: WebGLTexture; fbo: WebGLFramebuffer };\ntype DyePair = readonly [DyeTarget, DyeTarget];\n\n/** RGBA16F keeps decay from banding on its long tail; without `EXT_color_buffer_float` (float\n * render targets need it) this falls back to RGBA8 — coarser, but works everywhere. */\nfunction pickDyeFormat(gl: WebGL2RenderingContext): { internalFormat: number; type: number } {\n const hasFloat = gl.getExtension('EXT_color_buffer_float') !== null;\n return hasFloat\n ? { internalFormat: gl.RGBA16F, type: gl.HALF_FLOAT }\n : { internalFormat: gl.RGBA8, type: gl.UNSIGNED_BYTE };\n}\n\nexport function createMediumRuntime(gl: WebGL2RenderingContext, vertexSource: string): MediumRuntime {\n const format = pickDyeFormat(gl);\n\n const vaporForwardProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_VAPOR_FORWARD_SHADER));\n const vaporCorrectProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_VAPOR_CORRECT_SHADER));\n const vaporReactProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_VAPOR_REACT_SHADER));\n const condensateForwardProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_CONDENSATE_FORWARD_SHADER));\n const condensateCorrectProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_CONDENSATE_CORRECT_SHADER));\n const condensateReactProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_CONDENSATE_REACT_SHADER));\n const trackAdvectProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_TRACK_ADVECT_SHADER));\n const compositeProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_COMPOSITE_SHADER));\n const seedProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_SEED_SHADER));\n const emitProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_EMIT_SHADER));\n const resampleProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_RESAMPLE_SHADER));\n const shadowProgram = createProgram(gl, vertexSource, toGLSL(MEDIUM_SHADOW_SHADER));\n const trackLayerProgram = createTrackLayerProgram(gl);\n const trackLayer = createTrackLayer();\n let liveTracks: readonly VireUIKitLiveTrack[] = [];\n\n const vaporForwardLoc = locationCache(gl, vaporForwardProgram);\n const vaporCorrectLoc = locationCache(gl, vaporCorrectProgram);\n const vaporReactLoc = locationCache(gl, vaporReactProgram);\n const condensateForwardLoc = locationCache(gl, condensateForwardProgram);\n const condensateCorrectLoc = locationCache(gl, condensateCorrectProgram);\n const condensateReactLoc = locationCache(gl, condensateReactProgram);\n const trackAdvectLoc = locationCache(gl, trackAdvectProgram);\n const compositeLoc = locationCache(gl, compositeProgram);\n const seedLoc = locationCache(gl, seedProgram);\n const emitLoc = locationCache(gl, emitProgram);\n const resampleLoc = locationCache(gl, resampleProgram);\n const shadowLoc = locationCache(gl, shadowProgram);\n\n let gridW = 0;\n let gridH = 0;\n let vapor: DyePair | null = null;\n let condensate: DyePair | null = null;\n let track: DyePair | null = null;\n // MacCormack scratch buffers for a single frame, not ping-pong: each is fully overwritten by its\n // own pass and read again within the SAME step — no persistent state carried between frames.\n let vaporForward: DyeTarget | null = null;\n let vaporCorrected: DyeTarget | null = null;\n let condensateForward: DyeTarget | null = null;\n let condensateCorrected: DyeTarget | null = null;\n let shadow: DyeTarget | null = null;\n let front = 0;\n\n function makeTarget(w: number, h: number): DyeTarget {\n const texture = createTexture(gl, {\n width: w,\n height: h,\n internalFormat: format.internalFormat,\n type: format.type,\n // REPEAT: a domain with no border — see TextureOptions.wrap. All buffers carry the same\n // field, so the border needs to behave identically for each of them, or vapor would wrap\n // while condensate or a track at the same coordinate cuts off. The MacCormack limiter also\n // reads neighbors across this same border — it needs REPEAT exactly as much as transport does.\n wrap: gl.REPEAT,\n });\n const fbo = createFramebuffer(gl, texture);\n return { texture, fbo };\n }\n\n function clearTarget(target: DyeTarget, w: number, h: number): void {\n gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);\n gl.viewport(0, 0, w, h);\n gl.clearColor(0, 0, 0, 1);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n }\n\n function destroyPair(pair: DyePair | null): void {\n if (!pair) return;\n for (const t of pair) {\n gl.deleteTexture(t.texture);\n gl.deleteFramebuffer(t.fbo);\n }\n }\n\n function destroyTarget(target: DyeTarget | null): void {\n if (!target) return;\n gl.deleteTexture(target.texture);\n gl.deleteFramebuffer(target.fbo);\n }\n\n function destroyTargets(): void {\n destroyPair(vapor);\n destroyPair(condensate);\n destroyPair(track);\n destroyTarget(vaporForward);\n destroyTarget(vaporCorrected);\n destroyTarget(condensateForward);\n destroyTarget(condensateCorrected);\n destroyTarget(shadow);\n vapor = null;\n condensate = null;\n track = null;\n vaporForward = null;\n vaporCorrected = null;\n condensateForward = null;\n condensateCorrected = null;\n shadow = null;\n }\n\n // Three spots at fixed, spread-out grid points — the \"initial spots\", and the entire water\n // supply: it has no other source (the sum of vapor and condensate is conserved). Condensate and\n // tracks seed empty: only background condensation stamps into condensate on the first step, and\n // only `emit` stamps into tracks.\n function seed(w: number, h: number): void {\n if (!vapor || !condensate || !track) return;\n gl.bindFramebuffer(gl.FRAMEBUFFER, vapor[front].fbo);\n gl.viewport(0, 0, w, h);\n gl.disable(gl.BLEND);\n gl.useProgram(seedProgram);\n setUniform(gl, seedLoc('u_resolution'), [w, h]);\n setUniform(gl, seedLoc('u_spot0'), [w * 0.28, h * 0.64]);\n setUniform(gl, seedLoc('u_spot1'), [w * 0.7, h * 0.32]);\n setUniform(gl, seedLoc('u_spot2'), [w * 0.48, h * 0.84]);\n setUniform(gl, seedLoc('u_spotRadius'), Math.max(w, h) * 0.18);\n drawFullscreenTriangle(gl);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n clearTarget(condensate[0], w, h);\n clearTarget(condensate[1], w, h);\n clearTarget(track[0], w, h);\n clearTarget(track[1], w, h);\n }\n\n /** Bilinear-resamples one buffer into a differently-sized target — see MEDIUM_RESAMPLE_SHADER. */\n function resample(src: DyeTarget, srcW: number, srcH: number, dst: DyeTarget, dstW: number, dstH: number): void {\n gl.bindFramebuffer(gl.FRAMEBUFFER, dst.fbo);\n gl.viewport(0, 0, dstW, dstH);\n gl.disable(gl.BLEND);\n gl.useProgram(resampleProgram);\n bindTextureAt(gl, 0, src.texture, resampleProgram, 'u_src');\n setUniform(gl, resampleLoc('u_srcSize'), [srcW, srcH]);\n setUniform(gl, resampleLoc('u_resolution'), [dstW, dstH]);\n drawFullscreenTriangle(gl);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n }\n\n // A resize used to destroy every target and reseed from scratch — a phone rotation or a window\n // resize wiped the vapor back to its three initial spots and blinked condensate/tracks out, the\n // flicker the design forbids. Instead: bilinear-resample the OLD front buffers (the only ones\n // that carry real state — see the comment on the MacCormack scratch buffers above) into NEW,\n // differently-sized targets, then delete the old ones. Seeding stays a first-call-only thing.\n function resizeGrid(w: number, h: number): void {\n if (!vapor || !condensate || !track) return; // narrows for TS; callers already checked\n const oldVapor = vapor;\n const oldCondensate = condensate;\n const oldTrack = track;\n const oldW = gridW;\n const oldH = gridH;\n\n const newVaporFront = makeTarget(w, h);\n const newCondensateFront = makeTarget(w, h);\n const newTrackFront = makeTarget(w, h);\n resample(oldVapor[front], oldW, oldH, newVaporFront, w, h);\n resample(oldCondensate[front], oldW, oldH, newCondensateFront, w, h);\n resample(oldTrack[front], oldW, oldH, newTrackFront, w, h);\n\n destroyTargets();\n gridW = w;\n gridH = h;\n vapor = [newVaporFront, makeTarget(w, h)];\n condensate = [newCondensateFront, makeTarget(w, h)];\n track = [newTrackFront, makeTarget(w, h)];\n vaporForward = makeTarget(w, h);\n vaporCorrected = makeTarget(w, h);\n condensateForward = makeTarget(w, h);\n condensateCorrected = makeTarget(w, h);\n shadow = makeTarget(w, h);\n front = 0;\n\n // Total water scales with cell count, not just density: a bilinear resample roughly preserves\n // the AVERAGE value per cell, so the SUM over every cell scales with how many cells there now\n // are. Rescaling the EXISTING target (rather than re-measuring the just-resampled buffers)\n // keeps the invariant exact even if the resample itself is slightly lossy at the edges — the\n // periodic renormalization below already self-corrects any residual, and it needs a real\n // target to correct TOWARD rather than one that already baked in this resize's own error.\n // `timeSinceRenorm`/`framesSinceRenorm` are left running: the interval they measure doesn't\n // care about grid size, only about how much correction accumulated frame to frame.\n const areaRatio = (w * h) / (oldW * oldH);\n waterTargetTotal *= areaRatio;\n }\n\n function ensureGrid(gridWidth: number, gridHeight: number): void {\n const w = Math.max(2, Math.round(gridWidth));\n const h = Math.max(2, Math.round(gridHeight));\n if (vapor && condensate && track && w === gridW && h === gridH) return;\n\n if (!vapor || !condensate || !track) {\n gridW = w;\n gridH = h;\n vapor = [makeTarget(w, h), makeTarget(w, h)];\n condensate = [makeTarget(w, h), makeTarget(w, h)];\n track = [makeTarget(w, h), makeTarget(w, h)];\n vaporForward = makeTarget(w, h);\n vaporCorrected = makeTarget(w, h);\n condensateForward = makeTarget(w, h);\n condensateCorrected = makeTarget(w, h);\n shadow = makeTarget(w, h);\n front = 0;\n seed(w, h);\n // The target water total — right after seeding, before the first step: this is \"the entire\n // supply\" (see the comment on `seed`), and every later renormalization checks against it.\n // Right after seeding condensate is empty, so the target equals the vapor sum — but it's\n // re-verified every time as vapor+condensate, not just vapor, in case the initial state ever\n // changes.\n waterTargetTotal = readTarget(vapor[front]) + readTarget(condensate[front]);\n waterScale = 1;\n timeSinceRenorm = 0;\n framesSinceRenorm = 0;\n return;\n }\n\n resizeGrid(w, h);\n }\n\n /** Binds one light slot (`u_light<slot>*`) of `MEDIUM_COMPOSITE_SHADER` — `undefined` (a `lights`\n * array shorter than `MEDIUM_MAX_LIGHTS`) binds intensity 0, same as simply never setting the\n * uniform (GL's own default), so an unused slot contributes nothing either way. */\n function bindLightUniforms(slot: number, light: VireUIKitMediumLight | undefined): void {\n const position = light?.position ?? [0, 0];\n const direction = light?.direction ?? [0, 1];\n const dirLen = Math.hypot(direction[0], direction[1]) || 1;\n setUniform(gl, compositeLoc(`u_light${slot}Pos`), position);\n setUniform(gl, compositeLoc(`u_light${slot}Dir`), [direction[0] / dirLen, direction[1] / dirLen]);\n setUniform(gl, compositeLoc(`u_light${slot}CosCone`), Math.cos(light?.coneAngle ?? 0));\n setUniform(gl, compositeLoc(`u_light${slot}Falloff`), light?.falloff ?? 0);\n setUniform(gl, compositeLoc(`u_light${slot}Color`), light?.color ?? [0, 0, 0]);\n setUniform(gl, compositeLoc(`u_light${slot}Intensity`), light?.intensity ?? 0);\n }\n\n /** Shared velocity-field uniforms — one set for both phases' forward/correct passes and for\n * tracks (condensate adds its own gravity on top — see `u_settleSpeed` where it's declared).\n * The react passes don't mind them either: the extra `loc()` calls for curl uniforms that don't\n * exist there just return null, and `setUniform` silently skips them. */\n function bindVelocityUniforms(\n loc: (name: string) => WebGLUniformLocation | null,\n dt: number,\n phase: number,\n p: VireUIKitMediumParams,\n spatial: readonly MediumSpatialOctave[],\n ): void {\n setUniform(gl, loc('u_dyeSize'), [gridW, gridH]);\n setUniform(gl, loc('u_resolution'), [gridW, gridH]);\n setUniform(gl, loc('u_dt'), dt);\n setUniform(gl, loc('u_phase'), phase);\n setUniform(gl, loc('u_curlFreq'), p.curlFreq);\n setUniform(gl, loc('u_advectSpeed'), p.advectSpeed);\n setUniform(gl, loc('u_turbulence'), p.turbulence);\n // Only the vapor forward/correct programs declare u_vaporDrift; elsewhere loc() returns null\n // and setUniform silently skips it, same as the curl uniforms already do for the react passes.\n setUniform(gl, loc('u_vaporDrift'), p.gravityVaporDrift);\n setUniform(gl, loc('u_spatial0'), [spatial[0].freqX, spatial[0].freqY, spatial[0].periodX, spatial[0].periodY]);\n setUniform(gl, loc('u_spatial1'), [spatial[1].freqX, spatial[1].freqY, spatial[1].periodX, spatial[1].periodY]);\n setUniform(gl, loc('u_spatial2'), [spatial[2].freqX, spatial[2].freqY, spatial[2].periodX, spatial[2].periodY]);\n setUniform(gl, loc('u_spatial3'), [spatial[3].freqX, spatial[3].freqY, spatial[3].periodX, spatial[3].periodY]);\n }\n\n // The field's phase — curl-noise's time axis, accumulated here in float64 rather than derived\n // from an ever-growing caller-supplied clock, and wrapped at MEDIUM_TIME_PERIOD (noise.ts's\n // MEDIUM_TIME_OCTAVES makes that wrap exact, not approximate). Sending a raw, ever-growing\n // `time * curlSpeed` product to the GPU as a float32 uniform eventually loses the bits that\n // place a point within its own lattice cell — after hours of playback the field would visibly\n // jitter in place. Wrapping the ACCUMULATOR keeps its magnitude bounded, and because the rate is\n // baked in incrementally rather than multiplied in fresh every frame, changing `curlSpeed` at\n // runtime changes the slope going forward instead of rescaling everything already accumulated —\n // no jump.\n let phase = 0;\n\n function wrapPhase(value: number): number {\n const wrapped = value % MEDIUM_TIME_PERIOD;\n return wrapped < 0 ? wrapped + MEDIUM_TIME_PERIOD : wrapped;\n }\n\n // A semi-Lagrangian gather over a non-uniform velocity has no obligation to conserve the sum by\n // itself, and neither does the vapor<->condensate exchange at non-integer rounding exponents\n // (not a boundary or step-size issue — at zero advection and zero exchange there's no loss at\n // all); rather than change the scheme, once a second the WATER sum (vapor+condensate) is checked\n // against its target and the correction is spread as an EQUAL multiplier over the frames of the\n // next interval (an Nth root — otherwise an N-fold ratio would overshoot the target instead of\n // reaching it).\n const WATER_RENORM_INTERVAL_S = 1;\n let waterTargetTotal = 0;\n let waterScale = 1;\n let timeSinceRenorm = 0;\n let framesSinceRenorm = 0;\n\n function step(dt: number, params: Partial<VireUIKitMediumParams> = {}): void {\n if (\n !vapor ||\n !condensate ||\n !track ||\n !vaporForward ||\n !vaporCorrected ||\n !condensateForward ||\n !condensateCorrected ||\n !shadow\n ) {\n return;\n }\n const p = { ...MEDIUM_DEFAULTS, ...params };\n // Per step, not cached on resize: a caller may override curlFreq between frames.\n const spatial = computeMediumSpatialPeriods(gridW, gridH, p.curlFreq);\n // dt is clamped: a tab returning from the background would otherwise carry advection past the\n // grid's bounds in one jump, sweeping all the smoke to an edge in a single frame.\n const clampedDt = Math.min(Math.max(dt, 0), 1 / 15);\n // Advance and wrap the phase BEFORE using it this frame — see the comment on `phase` above.\n phase = wrapPhase(phase + clampedDt * p.curlSpeed);\n const back = front === 0 ? 1 : 0;\n const gridSize: readonly [number, number] = [gridW, gridH];\n\n // --- Transport: MacCormack splits each phase into forward/correct, both phases independently\n // (only the react pass below reads the partner, and only on already grid-aligned values). ---\n\n gl.viewport(0, 0, gridW, gridH);\n gl.disable(gl.BLEND);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, vaporForward.fbo);\n gl.useProgram(vaporForwardProgram);\n bindTextureAt(gl, 0, vapor[front].texture, vaporForwardProgram, 'u_dye');\n bindVelocityUniforms(vaporForwardLoc, clampedDt, phase, p, spatial);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, condensateForward.fbo);\n gl.useProgram(condensateForwardProgram);\n bindTextureAt(gl, 0, condensate[front].texture, condensateForwardProgram, 'u_dye');\n bindVelocityUniforms(condensateForwardLoc, clampedDt, phase, p, spatial);\n setUniform(gl, condensateForwardLoc('u_settleSpeed'), p.condensateSettleSpeed);\n setUniform(gl, condensateForwardLoc('u_settleWiggle'), p.condensateSettleWiggle);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, vaporCorrected.fbo);\n gl.useProgram(vaporCorrectProgram);\n bindTextureAt(gl, 0, vapor[front].texture, vaporCorrectProgram, 'u_dye');\n bindTextureAt(gl, 1, vaporForward.texture, vaporCorrectProgram, 'u_forward');\n // bindTextureAt only binds the sampler, not its auto uniform size (the transpiler generates a\n // <name>Size for every `uniform shader`) — the second (cross-) sampler needs its own size set\n // separately, or it silently stays (0,0) and `.eval()` divides the coordinate by zero.\n setUniform(gl, vaporCorrectLoc('u_forwardSize'), gridSize);\n bindVelocityUniforms(vaporCorrectLoc, clampedDt, phase, p, spatial);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, condensateCorrected.fbo);\n gl.useProgram(condensateCorrectProgram);\n bindTextureAt(gl, 0, condensate[front].texture, condensateCorrectProgram, 'u_dye');\n bindTextureAt(gl, 1, condensateForward.texture, condensateCorrectProgram, 'u_forward');\n setUniform(gl, condensateCorrectLoc('u_forwardSize'), gridSize);\n bindVelocityUniforms(condensateCorrectLoc, clampedDt, phase, p, spatial);\n setUniform(gl, condensateCorrectLoc('u_settleSpeed'), p.condensateSettleSpeed);\n setUniform(gl, condensateCorrectLoc('u_settleWiggle'), p.condensateSettleWiggle);\n drawFullscreenTriangle(gl);\n\n // --- Reaction: vapor<->condensate exchange and droplet growth, on the already transported\n // (MacCormack) fields, with the partner read directly at its OWN point xy — both fields are\n // already grid-aligned. ---\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, vapor[back].fbo);\n gl.useProgram(vaporReactProgram);\n bindTextureAt(gl, 0, vaporCorrected.texture, vaporReactProgram, 'u_transported');\n bindTextureAt(gl, 1, condensateCorrected.texture, vaporReactProgram, 'u_condensateTransported');\n setUniform(gl, vaporReactLoc('u_transportedSize'), gridSize);\n setUniform(gl, vaporReactLoc('u_condensateTransportedSize'), gridSize);\n setUniform(gl, vaporReactLoc('u_resolution'), gridSize);\n setUniform(gl, vaporReactLoc('u_dt'), clampedDt);\n setUniform(gl, vaporReactLoc('u_condensationRate'), p.condensationRate);\n setUniform(gl, vaporReactLoc('u_condensationFloor'), p.condensationFloor);\n setUniform(gl, vaporReactLoc('u_evaporationRate'), p.evaporationRate);\n setUniform(gl, vaporReactLoc('u_totalScale'), waterScale);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, condensate[back].fbo);\n gl.useProgram(condensateReactProgram);\n bindTextureAt(gl, 0, condensateCorrected.texture, condensateReactProgram, 'u_transported');\n bindTextureAt(gl, 1, vaporCorrected.texture, condensateReactProgram, 'u_vaporTransported');\n // Growth reads the neighbors of its OWN cell xy in the ORIGINAL (pre-transport-this-frame)\n // condensate — see the comment in advect-shader.ts on MEDIUM_CONDENSATE_REACT_SHADER — so the\n // third sampler is the old front buffer.\n bindTextureAt(gl, 2, condensate[front].texture, condensateReactProgram, 'u_dye');\n setUniform(gl, condensateReactLoc('u_transportedSize'), gridSize);\n setUniform(gl, condensateReactLoc('u_vaporTransportedSize'), gridSize);\n setUniform(gl, condensateReactLoc('u_dyeSize'), gridSize);\n setUniform(gl, condensateReactLoc('u_resolution'), gridSize);\n setUniform(gl, condensateReactLoc('u_dt'), clampedDt);\n setUniform(gl, condensateReactLoc('u_condensationRate'), p.condensationRate);\n setUniform(gl, condensateReactLoc('u_condensationFloor'), p.condensationFloor);\n setUniform(gl, condensateReactLoc('u_evaporationRate'), p.evaporationRate);\n setUniform(gl, condensateReactLoc('u_spreadRate'), p.condensateSpreadRate);\n setUniform(gl, condensateReactLoc('u_totalScale'), waterScale);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, track[back].fbo);\n gl.useProgram(trackAdvectProgram);\n bindTextureAt(gl, 0, track[front].texture, trackAdvectProgram, 'u_dye');\n bindVelocityUniforms(trackAdvectLoc, clampedDt, phase, p, spatial);\n setUniform(gl, trackAdvectLoc('u_decay'), p.decay);\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, shadow.fbo);\n gl.useProgram(shadowProgram);\n bindTextureAt(gl, 0, vapor[back].texture, shadowProgram, 'u_vapor');\n bindTextureAt(gl, 1, condensate[back].texture, shadowProgram, 'u_condensate');\n setUniform(gl, shadowLoc('u_vaporSize'), gridSize);\n setUniform(gl, shadowLoc('u_condensateSize'), gridSize);\n setUniform(gl, shadowLoc('u_resolution'), gridSize);\n setUniform(gl, shadowLoc('u_condensateGain'), p.condensateGain);\n setUniform(gl, shadowLoc('u_extinction'), p.shadowExtinction);\n for (const slot of [1, 2]) {\n const position = p.lights[slot]?.position ?? [0, 0];\n setUniform(gl, shadowLoc(`u_light${slot}Grid`), [position[0] * gridW, position[1] * gridH]);\n }\n drawFullscreenTriangle(gl);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n front = back;\n framesSinceRenorm += 1;\n\n timeSinceRenorm += clampedDt;\n if (waterTargetTotal > 0 && timeSinceRenorm >= WATER_RENORM_INTERVAL_S && framesSinceRenorm > 0) {\n const current = readTarget(vapor[front]) + readTarget(condensate[front]);\n if (current > 1e-6) {\n // The Nth root: applied N times in a row (N frames of the past interval) has to PRODUCE\n // the ratio target/current, not apply that ratio in EACH frame — otherwise one second's\n // correction would fire N times and overshoot the target instead of reaching it.\n const totalRatio = waterTargetTotal / current;\n const perFrame = Math.pow(totalRatio, 1 / framesSinceRenorm);\n waterScale = Math.min(1.05, Math.max(0.95, perFrame));\n }\n timeSinceRenorm = 0;\n framesSinceRenorm = 0;\n }\n }\n\n // Stamping goes into the TRACK buffer (the one advection just wrote) via additive blending,\n // exactly as track stamping requires: not a separate pass, not its own buffer, not water.\n function emit(emissions: readonly VireUIKitMediumEmission[]): void {\n if (!track || emissions.length === 0) return;\n const minDim = Math.min(gridW, gridH);\n gl.bindFramebuffer(gl.FRAMEBUFFER, track[front].fbo);\n gl.viewport(0, 0, gridW, gridH);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE);\n gl.useProgram(emitProgram);\n // The flip-convention comment above doesn't apply here: the shader never reads its own\n // previous frame (no `.eval()`), so there's nothing to flip — but the transpiler always\n // requires u_resolution regardless (see `targets/glsl.ts`: the entry coordinate goes through\n // it unconditionally).\n setUniform(gl, emitLoc('u_resolution'), [gridW, gridH]);\n for (const e of emissions) {\n const source: [number, number] = [e.source[0] * gridW, e.source[1] * gridH];\n const length = e.lengthFrac * minDim;\n const head: [number, number] = [\n source[0] + Math.cos(e.angle) * length,\n source[1] + Math.sin(e.angle) * length,\n ];\n setUniform(gl, emitLoc('u_source'), source);\n setUniform(gl, emitLoc('u_head'), head);\n setUniform(gl, emitLoc('u_headWidth'), e.headWidthFrac * minDim);\n setUniform(gl, emitLoc('u_tailWidth'), e.tailWidthFrac * minDim);\n setUniform(gl, emitLoc('u_raggedAmp'), e.raggedFrac * minDim);\n setUniform(gl, emitLoc('u_raggedFreq'), e.raggedFreq);\n setUniform(gl, emitLoc('u_settle'), e.settleFrac * minDim);\n setUniform(gl, emitLoc('u_tailDim'), e.tailDim);\n setUniform(gl, emitLoc('u_intensity'), e.intensity);\n setUniform(gl, emitLoc('u_seed'), e.seed);\n drawFullscreenTriangle(gl);\n }\n gl.disable(gl.BLEND);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n }\n\n function stepTrackLayer(dt: number, emissions: readonly VireUIKitMediumEmission[]): void {\n liveTracks = trackLayer.step(dt, emissions);\n }\n\n function composite(\n contentWidth: number,\n contentHeight: number,\n params: Partial<VireUIKitMediumParams> = {},\n ): void {\n if (!vapor || !condensate || !track || !shadow) return;\n const p = { ...MEDIUM_DEFAULTS, ...params };\n gl.viewport(0, 0, contentWidth, contentHeight);\n gl.disable(gl.BLEND);\n gl.useProgram(compositeProgram);\n bindTextureAt(gl, 0, vapor[front].texture, compositeProgram, 'u_vapor');\n bindTextureAt(gl, 1, condensate[front].texture, compositeProgram, 'u_condensate');\n bindTextureAt(gl, 2, track[front].texture, compositeProgram, 'u_track');\n bindTextureAt(gl, 3, shadow.texture, compositeProgram, 'u_shadow');\n setUniform(gl, compositeLoc('u_vaporSize'), [gridW, gridH]);\n setUniform(gl, compositeLoc('u_condensateSize'), [gridW, gridH]);\n setUniform(gl, compositeLoc('u_trackSize'), [gridW, gridH]);\n setUniform(gl, compositeLoc('u_shadowSize'), [gridW, gridH]);\n setUniform(gl, compositeLoc('u_resolution'), [contentWidth, contentHeight]);\n setUniform(gl, compositeLoc('u_dyeScale'), [gridW / contentWidth, gridH / contentHeight]);\n // The palette's forward direction (sRGB → Oklab) — here, once per frame (frame constants),\n // not in the shader per pixel (see the comment in composite-shader.ts).\n setUniform(gl, compositeLoc('u_lab0'), srgbToOklab(p.channelColors[0]));\n setUniform(gl, compositeLoc('u_lab1'), srgbToOklab(p.channelColors[1]));\n setUniform(gl, compositeLoc('u_lab2'), srgbToOklab(p.channelColors[2]));\n setUniform(gl, compositeLoc('u_labBg'), srgbToOklab(p.baseColor));\n setUniform(gl, compositeLoc('u_labCondensate'), srgbToOklab(p.condensateColor));\n setUniform(gl, compositeLoc('u_baseWeight'), p.baseWeight);\n setUniform(gl, compositeLoc('u_condensateTint'), p.condensateTint);\n setUniform(gl, compositeLoc('u_condensateGain'), p.condensateGain);\n setUniform(gl, compositeLoc('u_farScale'), p.depthFarScale);\n // Resolved against the GRID's own size (baseSrc is already grid-px), not content size — a\n // fixed fraction of a wrapping axis, not a fixed pixel count (see depthFarOffsetPx's own\n // comment for why a fixed grid-px value doesn't survive every grid size).\n setUniform(gl, compositeLoc('u_farOffset'), depthFarOffsetPx(p.depthFarOffsetFrac, gridW, gridH));\n setUniform(gl, compositeLoc('u_farBlurRadius'), p.depthFarBlurRadius);\n setUniform(gl, compositeLoc('u_farWeight'), p.depthFarWeight);\n setUniform(gl, compositeLoc('u_gravityBoost'), p.gravityBottomBoost);\n setUniform(gl, compositeLoc('u_gravityBoostStart'), p.gravityBottomBoostStart);\n setUniform(gl, compositeLoc('u_channelScatter'), p.channelScatter);\n for (let slot = 0; slot < MEDIUM_MAX_LIGHTS; slot += 1) bindLightUniforms(slot, p.lights[slot]);\n setUniform(gl, compositeLoc('u_trackLightFloor'), p.trackLightFloor);\n drawFullscreenTriangle(gl);\n\n // The crisp screen-space layer draws OVER the fullscreen composite just written, additively, in\n // the SAME target framebuffer — see track-layer-gl.ts's own file header for why this is a raw\n // instanced GLSL pass rather than another fullscreen shader in the DSL above.\n const { data, count } = buildTrackLayerInstances(liveTracks, contentWidth, contentHeight, p.trackLayerAmount);\n trackLayerProgram.draw(contentWidth, contentHeight, data, count, p.trackLightFloor, p.lights, shadow.texture);\n }\n\n function readTarget(target: DyeTarget): number {\n gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);\n let sum = 0;\n if (format.type === gl.HALF_FLOAT) {\n const buf = new Float32Array(gridW * gridH * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.FLOAT, buf);\n for (let i = 0; i < buf.length; i += 4) sum += buf[i] + buf[i + 1] + buf[i + 2];\n } else {\n const buf = new Uint8Array(gridW * gridH * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.UNSIGNED_BYTE, buf);\n for (let i = 0; i < buf.length; i += 4) sum += (buf[i] + buf[i + 1] + buf[i + 2]) / 255;\n }\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return sum;\n }\n\n function readTotals(): { vapor: number; condensate: number; track: number } | null {\n if (!vapor || !condensate || !track) return null;\n return {\n vapor: readTarget(vapor[front]),\n condensate: readTarget(condensate[front]),\n track: readTarget(track[front]),\n };\n }\n\n // Three numbers per cell (r/g/b of the three vapor species), not a sum: the structure gate\n // (medium-structure-gate.mjs) measures contrast BETWEEN species — a summed density would lose it.\n function readVaporGrid(): { cols: number; rows: number; data: number[] } | null {\n if (!vapor) return null;\n gl.bindFramebuffer(gl.FRAMEBUFFER, vapor[front].fbo);\n const cells = gridW * gridH;\n const data = new Array(cells * 3);\n if (format.type === gl.HALF_FLOAT) {\n const buf = new Float32Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.FLOAT, buf);\n for (let i = 0; i < cells; i += 1) {\n data[i * 3] = buf[i * 4];\n data[i * 3 + 1] = buf[i * 4 + 1];\n data[i * 3 + 2] = buf[i * 4 + 2];\n }\n } else {\n const buf = new Uint8Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.UNSIGNED_BYTE, buf);\n for (let i = 0; i < cells; i += 1) {\n data[i * 3] = buf[i * 4] / 255;\n data[i * 3 + 1] = buf[i * 4 + 1] / 255;\n data[i * 3 + 2] = buf[i * 4 + 2] / 255;\n }\n }\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return { cols: gridW, rows: gridH, data };\n }\n\n // One number per cell, not three: condensate has no color of its own (density lives in .r,\n // g/b unused — see MEDIUM_CONDENSATE_REACT_SHADER). A gravity gate reads THIS, row-weighting the\n // density to find where the mass sits, without decoding an RGB triple that's two-thirds padding.\n function readCondensateGrid(): { cols: number; rows: number; data: number[] } | null {\n if (!condensate) return null;\n gl.bindFramebuffer(gl.FRAMEBUFFER, condensate[front].fbo);\n const cells = gridW * gridH;\n const data = new Array(cells);\n if (format.type === gl.HALF_FLOAT) {\n const buf = new Float32Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.FLOAT, buf);\n for (let i = 0; i < cells; i += 1) data[i] = buf[i * 4];\n } else {\n const buf = new Uint8Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.UNSIGNED_BYTE, buf);\n for (let i = 0; i < cells; i += 1) data[i] = buf[i * 4] / 255;\n }\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return { cols: gridW, rows: gridH, data };\n }\n\n // One number per cell, same reasoning as readCondensateGrid: a track carries no color of its own\n // (emit-shader.ts stamps r=g=b — see its own comment) since MEDIUM_COMPOSITE_SHADER now lights\n // tracks rather than tinting them. Used by the \"sharp at birth, soft later\" gate to measure a\n // stamped track's cross-section directly, without compositing/lighting in the way.\n function readTrackGrid(): { cols: number; rows: number; data: number[] } | null {\n if (!track) return null;\n gl.bindFramebuffer(gl.FRAMEBUFFER, track[front].fbo);\n const cells = gridW * gridH;\n const data = new Array(cells);\n if (format.type === gl.HALF_FLOAT) {\n const buf = new Float32Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.FLOAT, buf);\n for (let i = 0; i < cells; i += 1) data[i] = buf[i * 4];\n } else {\n const buf = new Uint8Array(cells * 4);\n gl.readPixels(0, 0, gridW, gridH, gl.RGBA, gl.UNSIGNED_BYTE, buf);\n for (let i = 0; i < cells; i += 1) data[i] = buf[i * 4] / 255;\n }\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return { cols: gridW, rows: gridH, data };\n }\n\n function destroy(): void {\n destroyTargets();\n gl.deleteProgram(vaporForwardProgram);\n gl.deleteProgram(vaporCorrectProgram);\n gl.deleteProgram(vaporReactProgram);\n gl.deleteProgram(condensateForwardProgram);\n gl.deleteProgram(condensateCorrectProgram);\n gl.deleteProgram(condensateReactProgram);\n gl.deleteProgram(trackAdvectProgram);\n gl.deleteProgram(compositeProgram);\n gl.deleteProgram(seedProgram);\n gl.deleteProgram(emitProgram);\n gl.deleteProgram(shadowProgram);\n gl.deleteProgram(resampleProgram);\n trackLayerProgram.destroy();\n }\n\n return {\n ensureGrid,\n step,\n emit,\n stepTrackLayer,\n composite,\n readTotals,\n readVaporGrid,\n readCondensateGrid,\n readTrackGrid,\n destroy,\n };\n}\n","import {\n FULLSCREEN_TRIANGLE_VERTEX_SOURCE,\n type VireGlassBackdropPass,\n} from 'vireglass/web';\nimport type { VireUIKitMediumEmission, VireUIKitMediumParams } from '../medium';\nimport { createMediumRuntime, type MediumRuntime } from './medium';\n\n/** The simulation grid — its own resolution, coarser than the frame. `dt` is the caller's clock:\n * pausing on `prefers-reduced-motion` and testability both stay on its side. There is no `time`\n * field — the field's phase is the runtime's own accumulated state (see `u_phase` in\n * `web/medium.ts`), not something the caller has to track across an arbitrarily long session. */\nexport type MediumFrame = {\n gridWidth: number;\n gridHeight: number;\n dt: number;\n params?: Partial<VireUIKitMediumParams>;\n /** Tracks stamped this frame; `createMediumDynamics` schedules them. */\n emissions?: readonly VireUIKitMediumEmission[];\n};\n\nexport type MediumBackdrop = {\n /** The VireGlass renderer's backdrop for this frame: steps the medium and composites it into\n * the lens's texture. */\n pass(frame: MediumFrame): VireGlassBackdropPass;\n /** Measurement hooks for balance and structure gates; `null` until the medium has rendered once. */\n readTotals(): { vapor: number; condensate: number; track: number } | null;\n readVaporGrid(): { cols: number; rows: number; data: number[] } | null;\n readCondensateGrid(): { cols: number; rows: number; data: number[] } | null;\n readTrackGrid(): { cols: number; rows: number; data: number[] } | null;\n destroy(): void;\n};\n\nexport function createMediumBackdrop(): MediumBackdrop {\n let runtime: MediumRuntime | null = null;\n let owner: WebGL2RenderingContext | null = null;\n\n return {\n pass: (frame) => (gl, target) => {\n if (owner && owner !== gl) throw new Error('vireuikit: the medium is already bound to another WebGL context');\n owner = gl;\n runtime ??= createMediumRuntime(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE);\n runtime.ensureGrid(frame.gridWidth, frame.gridHeight);\n runtime.step(frame.dt, frame.params);\n runtime.emit(frame.emissions ?? []);\n runtime.stepTrackLayer(frame.dt, frame.emissions ?? []);\n gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);\n runtime.composite(target.width, target.height, frame.params);\n },\n readTotals: () => runtime?.readTotals() ?? null,\n readVaporGrid: () => runtime?.readVaporGrid() ?? null,\n readCondensateGrid: () => runtime?.readCondensateGrid() ?? null,\n readTrackGrid: () => runtime?.readTrackGrid() ?? null,\n destroy: () => {\n runtime?.destroy();\n runtime = null;\n owner = null;\n },\n };\n}\n"]}