PURPOSE
Places flat-shape silhouette stamps from a stamp family across an infinite tiled world for parallax backdrops. Deterministic per-tile placement keeps the same content in the same world position across scroll. Stamps paint into a per-layer offscreen buffer with destination-over compositing so overlapping stamps retain the first-painted alpha, then the merged buffer is blitted onto the main canvas at one configured opacity — the whole layer reads as a single cohesive silhouette at uniform alpha regardless of overlap density.
OWNS
SilhouetteStampLayerConfiginterface (id, slot, parallax, depth, family, tileSize, density, tint, blurPx, opacity, sizeScale, rotate, composite, seed).createSilhouetteStampLayer(config)factory that returns aParallaxLayer.- Local
resolveTint(tintOrSlot)helper that accepts either aPaletteSlotname or a#-prefixed hex literal. - Per-layer offscreen
HTMLCanvasElementbuffer and itsCanvasRenderingContext2D, lazily allocated on first draw and resized to the viewport. - Per-layer seed multipliers
SX0,SY0,SZ0derived asseed * 1009,seed * 2003,seed * 3001. - The layer’s
draw(frame)anddispose()implementations.
READS FROM
./layer-types—ParallaxLayer,ParallaxFrame,hash3i,worldToScreen,forVisibleTiles../silhouette-stamps—getFamilyInfo,getStampCanvas,pickVariantIndex,StampFamilyId.../palette/palette-system—resolvePaletteSlotfor slot-name tints (resolved per frame against the active palette).../palette/palette-types—PaletteSlottype.- The
ParallaxFramepassed todraw:ctx,camX,camY,camZoom,viewW,viewH. - Global
document(guarded by atypeof document === 'undefined'early return for non-DOM environments).
PUSHES TO
- The per-layer offscreen buffer context (
bctx):clearRect,globalAlpha = 1,globalCompositeOperation = 'destination-over',save/translate/rotate/scale/restore, anddrawImage(stampCanvas, ...)for each placed stamp. - The main frame context (
ctx): onesave,globalAlpha = opacity,globalCompositeOperation = composite(defaultsource-over),drawImage(buffer, 0, 0),restore. - Returns a
ParallaxLayer(itsid,slot,parallax,depth,draw,dispose) to whatever caller composes the parallax stack.
DOES NOT
- Does not bake or cache stamp variants — that is owned by
silhouette-stampsviagetStampCanvas(the cache keys include tint and blur). - Does not resolve palette swaps over time — it calls
resolvePaletteSlotonce per draw for the layer’s tint. - Does not animate stamps; no per-frame motion, drift, twinkle, or rotation change. Positions and orientations are pure functions of
(tileX, tileY, i, seed). - Does not stack alpha across overlapping stamps —
destination-overin the buffer plus a single final blit prevents darkening. - Does not perform world simulation, spawn entities, or read game state outside the
ParallaxFrameit is handed. - Does not own the parallax stack, tile visibility math, or camera transform — those live in
layer-types. - Does not depend on WebGL; pure Canvas 2D.
Signals
None. The module exports no events, emitters, or callbacks. Communication is one-way: the renderer calls draw(frame) and dispose().
Entry points
createSilhouetteStampLayer(config: SilhouetteStampLayerConfig): ParallaxLayer— sole export besides the config type. Returns an object withid,slot,parallax,depth,draw(f),dispose().ParallaxLayer.draw(f: ParallaxFrame)— invoked once per frame by the parallax renderer.ParallaxLayer.dispose()— nulls out the buffer and its context.
Pattern notes
- Coprime seed mixing:
SX0 = seed*1009,SY0 = seed*2003,SZ0 = seed*3001. Mirrors the same pattern asstarfield-layer.ts. Sibling layers that share a stamp family (e.g. back/mid/near buildings) pick different variants, rotations, scales, and flips at coincident tile coordinates, so the human eye does not pattern-match identical silhouettes across parallax slots. - Per-tile stamp count:
Math.floor(family.baseDensity * layer.density + jitter)wherejitterishash3i(tx+SX0, ty+SY0, SZ0). - Per-stamp placement rolls all derive from
hash3iwith different third-argument offsets(i*3 + k + SZ0):ux,uy(position-within-tile),variant,sizeRoll,scaleRoll,rot,flipX,flipY. - World-to-screen via
worldToScreen(worldX, worldY, parallax, camX, camY, camZoom, viewW, viewH); offscreen culling rejects stamps whose AABB lies fully outside the viewport. worldSize = info.minSize + sizeRoll * (info.maxSize - info.minSize); finaldrawSize = worldSize * scale * camZoomwherescaleis drawn fromsizeScalerange[a, b].- Rotation branch: full
0..2πrotation plus independent mirror flips on both axes (flipX,flipYfrom a< 0.5threshold). Rotation × flipX × flipY gives 4× finer orientation space than rotation alone. Whenrotate=false, stamps draw axis-aligned at the screen-space top-left of their AABB. - Tint resolution: a tint string starting with
#(charcode0x23) is treated as a hex literal; otherwise it is passed toresolvePaletteSlotas aPaletteSlotname and resolves to the active palette every draw, so palette transitions recolor the layer. - Blur is pre-baked into the stamp cache keyed on
blurPx, soblur=4andblur=1share no cache. Typical values per the source comment:back=4,mid=2,near=1,fg=0.5. - Uniform alpha cohesion: the buffer is cleared, painted with
destination-over, then blitted once withglobalAlpha = opacityandglobalCompositeOperation = composite. Theopacityfield is the only place layer alpha is applied — overlapping silhouettes never compound darker than a non-overlapping stamp. - Lazy + viewport-sized buffer: allocated on first
drawviadocument.createElement('canvas'); resized toMath.max(1, Math.ceil(viewW))×Math.max(1, Math.ceil(viewH))whenever the viewport changes. Kept per-layer (not shared) so two silhouette layers in the same slot do not clobber each other. dispose()simply nullsbufferandbufferCtx; the layer is intended to be cheap to drop and recreate at biome boundaries.- Default config values:
opacity = 1,sizeScale = [1, 1],rotate = true,composite = 'source-over',seed = 0.