PURPOSE
Procedural starfield layer for the parallax sandwich. Computes 0..N stars per visible tile from (tx, ty) hashes and draws each as a tiny filled circle. No pre-baking, no images. Twinkle modulation via a slow per-star sine. Deterministic across parallax scroll. Intended for the deepest parallax slots (parallax 0.02–0.20).
OWNS
StarfieldLayerConfiginterface — id, slot, parallax, depth, tileSize, density, color, accentColor, sizeMul, twinkle, bigRatio, seed.createStarfieldLayer(config)factory — returns aParallaxLayerwith adraw(f)callback.- Local
resolveColor(colorOrSlot)helper — passes through literal CSS colors (starting with#,r, orR) and routes everything else throughresolvePaletteSlot. - Per-layer seed mixers
sx0 = seed * 1009,sy0 = seed * 2003,sz0 = seed * 3001— coprime multipliers so sibling starfields with different seeds produce independent patterns rather than the same pattern at different offsets.
READS FROM
./layer-types—ParallaxLayer,ParallaxFrametypes;hash3i,worldToScreen,forVisibleTilesutilities.../palette/palette-system—resolvePaletteSlotfor slot-name color resolution.../palette/palette-types—PaletteSlottype.ParallaxFramefields per draw:ctx,camX,camY,camZoom,viewW,viewH,t.
PUSHES TO
CanvasRenderingContext2D—save/restore,globalCompositeOperation = 'lighter',fillStyle,globalAlpha,beginPath/arc/fill.- No external state mutation. Returns a
ParallaxLayerobject whosedrawcallback issues canvas commands only.
DOES NOT
- Does not allocate per frame (no arrays, no object literals beyond the destructured frame fields and the
worldToScreenreturn). - Does not pre-bake textures, images, or offscreen canvases.
- Does not maintain star-position arrays across frames — every star is rederived from hashes each frame.
- Does not implement
prepareordisposeon the returned layer. - Does not handle input, collision, audio, or gameplay state.
- Does not draw outside the visible viewport — culls any star whose screen position falls more than 4 px outside
viewW/viewH.
Signals
- None emitted. Pure rendering layer; no events, callbacks, or external notifications.
Entry points
createStarfieldLayer(config: StarfieldLayerConfig): ParallaxLayer— the sole export. Called by biome recipes when assembling the parallax stack.ParallaxLayer.draw(f: ParallaxFrame)— invoked once per frame by the parallax renderer during the matching slot phase.
Pattern notes
- Color inputs accept either a
PaletteSlotname or a literal CSS color (#…,rgb(…),rgba(…)). Slot resolution runs once per frame, not once per star. - Tile iteration goes through the shared
forVisibleTileshelper, which pads tile bounds by 1 tile on each side to hide pop-in. - Star count per tile is jittered by ±20% around
densityusing a per-tile hash, so neighbor tiles don’t all carry exactly the same count. - Star positions inside a tile are derived from two independent
hash3icalls per star (offsets+1and+2), then transformed byworldToScreenusing the layer’sparallaxfactor. - Big-star branch:
sizeRoll < bigRatio(default 0.08) flags ~8% of stars as bigger (radius1.1 + sizeRoll * 0.7); the rest get radius0.5 + sizeRoll * 0.5. Both multiplied bysizeMul. - Alpha is rolled per star with a separate
hash3icall, base0.35 + brightRoll * 0.6. Withtwinkleon, alpha is multiplied by0.75 + 0.25 * sin(t * 1.3 + phase)wherephaseis yet another per-star hash mapped into[0, 2π). - Accent color (when supplied) is used when
brightRoll < 0.12, i.e. ~12% of stars. Same resolution rules as the primary color. globalCompositeOperation = 'lighter'is set insidectx.save()/ctx.restore(), so additive blending applies only to this layer’s draws and doesn’t leak into adjacent layers.- Performance budget noted in the source header: ~50 stars per tile × 9 tiles = ~450 arcs per frame ≈ 0.3 ms on mobile.