PURPOSE
Shared type and utility module for the parallax “sandwich” render stack. Defines the slot taxonomy, the per-frame draw context, the layer interface, the biome recipe shape, and the deterministic math helpers every concrete parallax layer reuses. It is the contract that the parallax system, biome recipes, and individual layer implementations (starfield, atmosphere FBM, ground texture, silhouette stamps) all conform to.
OWNS
ParallaxSlot— string union of the four render phases:'back','mid','near','fg'.ParallaxFrame— per-draw context withctx, game-timet, camera world positioncamX/camY,camZoom, and CSS-pixel viewport sizeviewW/viewH.ParallaxLayer— layer contract:id,slot,parallaxscroll factor,depthtiebreaker, optionalprepare()(cache once on biome entry), requireddraw(f)(called once per frame inside the matching slot), optionaldispose()(cleanup on biome switch out).BiomeParallaxRecipe—biomeId,displayName, orderedlayersarray that defines a biome’s look.hash2i(x, y)— deterministic integer hash returning a float in[0, 1). Used for stamp placement and noise sampling.hash3i(x, y, z)— three-input variant for per-instance seeds derived from(tileX, tileY, index).forVisibleTiles(parallax, tileSize, camX, camY, camZoom, viewW, viewH, cb)— iterates integer(tx, ty)tile coords overlapping the visible viewport at the given parallax factor, with one-tile padding on each side to hide edge pop-in.worldToScreen(worldX, worldY, parallax, camX, camY, camZoom, viewW, viewH)— returns{ sx, sy }screen-space coords using the shared parallax + camera math.
READS FROM
CanvasRenderingContext2D(DOM) — referenced only via theParallaxFrame.ctxtype.Math.imul,Math.floor,Math.ceil— used inside the hash and tile-iteration helpers.
The module imports nothing else. It is leaf-level.
PUSHES TO
Nothing at runtime — the module exports types and pure utility functions. Downstream consumers (the parallax system, biome recipes, the four concrete layer files in the same directory) import from it; this file does not push state anywhere.
DOES NOT
- Allocate per frame. The utilities are pure functions over numbers; they return either nothing (
forVisibleTilesinvokes a callback) or a small literal object (worldToScreen). - Maintain hidden state, caches, or singletons. No module-scope mutable bindings.
- Schedule, batch, or sort layers. Ordering across slots and within a slot (via
depth) is the parallax system’s responsibility. - Touch WebGL. The render context is Canvas 2D; the WebGL nebula sits below the
'back'slot and is owned elsewhere. - Read device pixels.
ParallaxFrame.viewW/viewHare explicitly CSS pixels. - Provide non-deterministic randomness. All randomness must flow from
hash2i/hash3iso parallax scroll never pops content.
Signals
parallaxfield onParallaxLayer:0.0= locked in place (infinitely far),1.0= world plane (moves with the world exactly),>1.0= foreground (moves faster than the camera, e.g. dust or fog between camera and ship).slotfield draws in fixed order —'back'(after WebGL nebula, before terrain — deep background),'mid'(before terrain — distant silhouettes and atmosphere),'near'(right before enemies and ship — close silhouettes),'fg'(after gameplay, before HUD — dust and haze).depthfield is the within-slot tiebreaker: smaller draws first (further back).
Entry points
ParallaxLayer.prepare()— called once when the layer enters the active biome. Layers cache stamp textures, bake noise fields, and allocate the single reusable dust buffer here.ParallaxLayer.draw(f)— called once per frame inside the matching slot phase.ParallaxLayer.dispose()— called when the biome switches out, for optional cleanup.hash2i,hash3i,forVisibleTiles,worldToScreen— imported directly by concrete layer implementations.
Pattern notes
- The four-slot sandwich is fixed and forms a hard contract with the parallax system and the WebGL nebula below it. Adding a phase means changing every consumer.
- Determinism from
(tile coords, seed)is load-bearing — both hash helpers use the same Murmur-style mixing (0x1b873593,0xcc9e2d51,0x85ebca6b,0xc2b2ae35) so seeds line up across helpers and layer types. forVisibleTilesapplies parallax to the camera (ex = camX * parallax) before computing tile bounds, so a single iteration pattern works for every parallax factor.worldToScreenuses the same(world - cam * parallax) * zoom + viewCenterformulaforVisibleTilesis consistent with — layer code does not reimplement camera math.- The header comment is the canonical description of the sandwich; concrete layers should not redefine slot semantics.
- The “never allocate memory per frame” rule is stated in the header and is the reason
prepare()exists. Baking textures and noise fields up front, and reusing a single dust buffer, are the patterns this contract enables. - The tile loop pads bounds by one tile on each side (
- 1on min,+ 1on max) so content sliding into view is already present.