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-level Map<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 seed 42 + octaves on miss.
  • resolveTint — helper that distinguishes hex literals (starts with #) from palette-slot names and resolves the latter via the palette system.
  • createAtmosphereFbmLayer — factory returning a ParallaxLayer whose draw closure renders the cached canvas tiled across the viewport.
  • disposeFbmCache — exported cache-clearing utility.
  • AtmosphereFbmLayerConfig — exported config shape.

READS FROM

  • ./layer-typesParallaxLayer, ParallaxFrame types.
  • ../palette/palette-systemresolvePaletteSlot for tint resolution at bake time.
  • ../palette/palette-typesPaletteSlot type.
  • ParallaxFrame fields consumed in draw: ctx, t, camX, camY, camZoom, viewW, viewH (camZoom is currently unused, kept for future scale coupling).
  • document — guarded by typeof document === 'undefined' so SSR/test environments return null instead of crashing.

PUSHES TO

  • The 2D canvas context supplied via ParallaxFrame.ctx — calls save, sets globalCompositeOperation (default 'lighter') and globalAlpha (config opacity), issues 4 base drawImage calls plus up to 4 extra extension tiles for very wide or tall viewports, then restore.
  • _noiseCache on 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 ParallaxFrame or 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 disposeFbmCache is called).
  • Does not use camZoom; it is referenced via void camZoom only 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 === 0 or non-finite scale.

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 with id, slot, parallax, depth, tint, opacity, scrollSpeed, and optional scale (default 2.5), composite (default 'lighter'), and octaves (default 4).
  • 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: amp starts at 0.5 and halves each octave, frequency starts at 1 and doubles, base sample frequency is f * 4 over 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 and globalAlpha modulate 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 + octaves to 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 drawImage calls cover any viewport up to w × h (where w = cv.width * scale, h = cv.height * scale). With default scale = 2.5 and 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) === 0x23 to detect the # prefix — slightly faster than startsWith('#').
  • Module-level cache lives for the lifetime of the JS context; tests or hot-reload flows should call disposeFbmCache to avoid stale tinted canvases bleeding across runs.