PURPOSE
Provides a scrolling tiled fractional-Brownian-motion noise field rendered as a low-opacity parallax layer. Used for deep fog, volumetric haze, and dust diffusion. A single tileable 512² fbm canvas is baked at first use (one-time ~30 ms), tinted, and drawn in a 2×2 tiled pattern with parallax offset and time-scrolling. Cost is four drawImage calls per frame (~0.08 ms on mobile).
OWNS
_noiseCache— module-levelMap<string, HTMLCanvasElement>keyed by${tint}:${octaves}, holding baked fbm canvases.makeTileableFbmCanvas— internal generator that produces a wrap-seamless value-noise canvas at a given size, seed, octave count, and tint.getFbmCanvas— cache-lookup wrapper that bakes a 512² canvas with seed42 + octaveson miss.resolveTint— helper that distinguishes hex literals (starts with#) from palette-slot names and resolves the latter via the palette system.createAtmosphereFbmLayer— factory returning aParallaxLayerwhosedrawclosure renders the cached canvas tiled across the viewport.disposeFbmCache— exported cache-clearing utility.AtmosphereFbmLayerConfig— exported config shape.
READS FROM
./layer-types—ParallaxLayer,ParallaxFrametypes.../palette/palette-system—resolvePaletteSlotfor tint resolution at bake time.../palette/palette-types—PaletteSlottype.ParallaxFramefields consumed indraw:ctx,t,camX,camY,camZoom,viewW,viewH(camZoomis currently unused, kept for future scale coupling).document— guarded bytypeof document === 'undefined'so SSR/test environments returnnullinstead of crashing.
PUSHES TO
- The 2D canvas context supplied via
ParallaxFrame.ctx— callssave, setsglobalCompositeOperation(default'lighter') andglobalAlpha(configopacity), issues 4 basedrawImagecalls plus up to 4 extra extension tiles for very wide or tall viewports, thenrestore. _noiseCacheon first lookup of a(tint, octaves)pair.
DOES NOT
- Does not own its own camera, world clock, or palette state — all of those are passed in via
ParallaxFrameor resolved at bake time. - Does not regenerate the fbm canvas per frame; the canvas is baked once and cached for the process lifetime (or until
disposeFbmCacheis called). - Does not use
camZoom; it is referenced viavoid camZoomonly to silence the unused-parameter hint. - Does not loop arbitrarily many tiles — base coverage is exactly 4 tiles, with at most 2 additional tiles each for wide and tall viewports.
- Does not handle palette swaps — once a canvas is baked with a resolved tint, the resolved tint is captured in the cache key but the canvas pixels are not re-tinted if the palette changes.
- Does not blend with anything other than the configured composite operation.
- Does not validate config values or guard against
octaves === 0or non-finitescale.
Signals
None. This is a pure renderer with no event emission, no telemetry hooks, and no callbacks. Its only side effect is drawing into the supplied canvas context and populating the local noise cache.
Entry points
createAtmosphereFbmLayer(config: AtmosphereFbmLayerConfig): ParallaxLayer— primary public factory. Callers (parallax composition / biome layer definitions) instantiate one layer per atmosphere effect withid,slot,parallax,depth,tint,opacity,scrollSpeed, and optionalscale(default2.5),composite(default'lighter'), andoctaves(default4).disposeFbmCache(): void— clears the noise cache; intended for teardown / hot-reload paths.AtmosphereFbmLayerConfig— exported interface enumerating the supported configuration knobs.
The layer instance is then consumed by the parallax renderer, which invokes its draw(frame) method each frame.
Pattern notes
- Tileable value noise is achieved by wrapping the integer lattice coordinates modulo the frequency before hashing, so corner samples on opposite edges hash to the same values and the canvas seams disappear.
- Fbm sum uses fixed amplitude/frequency progression:
ampstarts at0.5and halves each octave, frequency starts at1and doubles, base sample frequency isf * 4over the normalized[0, 1]UV. - Output pixels carry the tint RGB verbatim in the R/G/B channels and encode the noise value in the alpha channel (
Math.round(v * 255)), letting the composite operation andglobalAlphamodulate the visible intensity at draw time. - Cache key combines tint and octave count, so the same tint at two octave settings produces two separate canvases. Seed is derived as
42 + octavesto keep two different octave bakes from producing identical hash sequences. - Scroll offset combines camera-driven parallax (
camX * parallax,camY * parallax) with continuous time scroll (t * scrollSpeed), then double-modulo (((x % w) + w) % w) ensures correct negative-modulo behaviour so tiling stays seamless regardless of sign. - The four base
drawImagecalls cover any viewport up tow × h(wherew = cv.width * scale,h = cv.height * scale). With defaultscale = 2.5and a 512² source, a single tile is 1280 px; the 2×2 grid therefore covers up to 2560×2560 px viewports without seams. Two conditional extension passes add a column or row for viewports that exceed the base coverage. - Tint resolution uses
charCodeAt(0) === 0x23to detect the#prefix — slightly faster thanstartsWith('#'). - Module-level cache lives for the lifetime of the JS context; tests or hot-reload flows should call
disposeFbmCacheto avoid stale tinted canvases bleeding across runs.