PURPOSE
Draws the gameplay underlay — a thin world-space overlay rendered at 100% parallax that sits in the gap between the parallax backdrop and the gameplay layer. Acts as the “digital playing board” under events, terrain, and drop channels. Dispatches one of three variants based on the active biome: a flat-top tessellating hex grid (default), a horizontal water-surface wave pattern (biome delphi), or an orthogonal square topographic grid with random chunks cut out (biome old_earth).
OWNS
- The three underlay variant renderers:
drawHexGrid,drawWaveGrid,drawLatLongGrid. - The pre-baked tileable canvases for the hex and lat/long variants (
_hexBake,_latLongBake) and their stroke-color memoization keys (_hexBakeColor,_latLongBakeColor). - The shared base-alpha breathing modulation (
BASE_ALPHA,WAVE_AMPLITUDE,WAVE_SPEED,frameAlpha) used by every variant so all biomes’ boards breathe on the same cadence. - The world-space context setup (
enterWorldSpace) that translates and scales the canvas once per draw rather than per primitive. - All variant tuning constants: hex radius and tile period, wave row spacing / wavelengths / amplitudes / phase speed, lat/long cell size / drop rate / repeat period, and supersample factors for the bakes.
- The deprecated
drawGameplayGridalias kept alive during the rename todrawGameplayUnderlay.
READS FROM
resolvePaletteSlot('bg_star')from./palette/palette-system— provides the stroke color for all three variants, so the underlay tint tracks the active biome’s mood.getActiveBiomeId()from./parallax/parallax-system— drives the variant switch in the dispatcher.hash2ifrom./parallax/layer-types— deterministic 2D hash used to decide which lat/long cells are dropped from the topographic grid (restricted to a 16×16 cell repeat for clean tiling).- Caller-supplied camera state and frame time:
camX,camY,camZoom,viewW,viewH,tSeconds.
PUSHES TO
- The supplied
CanvasRenderingContext2D— sets transform,globalAlpha,strokeStyle,lineWidth, and issuesdrawImage(hex / lat-long) orstroke(wave) calls. - The two module-level baked canvases — created on first use and invalidated when the palette stroke color changes.
DOES NOT
- Does not draw events, terrain, drop channels, or any gameplay entity — only the underlay pattern beneath them.
- Does not manage its own render ordering — relies on the caller invoking it between
parallax_fgand the events pass; anything drawn after this call naturally occludes the underlay. - Does not handle screen-space drawing, HUD, or post-processing.
- Does not own palette resolution, biome selection, or camera math beyond converting view extents into world-space tile ranges.
- Does not animate the hex or lat/long variants beyond the shared alpha breathing — only the wave variant has live time-driven motion in its geometry.
- Does not early-out on zero-area views; it only guards against
camZoom <= 0.
Signals
- Reads the active-biome signal each frame via
getActiveBiomeId(); switching biome immediately changes which variant runs on the next draw. - Bake invalidation is triggered implicitly: when
resolvePaletteSlot('bg_star')returns a different stroke than the cached_hexBakeColor/_latLongBakeColor, the corresponding tile is re-baked. - No event bus subscriptions; the module is pure render-on-call.
Entry points
drawGameplayUnderlay(ctx, camX, camY, camZoom, viewW, viewH, tSeconds)— exported dispatcher. Called bybridge.tsimmediately after the parallax foreground pass and before the events pass. Returns early whencamZoom <= 0.drawGameplayGrid— deprecated alias re-exportingdrawGameplayUnderlayso legacy callers continue to work during the rename.
Pattern notes
- Hex and lat/long variants pre-bake one tileable canvas at a
2×supersample, then draw it as a tileddrawImageper frame — avoids per-framebeginPath/strokework. The hex bake covers one tile period plus a one-cell margin so strokes that cross the tile edge wrap cleanly when the canvas is drawn next to itself. - Wave variant stays live because the surface ripples are time-driven; it composes two sinusoids of different wavelengths and phase speeds per row, and adapts its sample step to
camZoomso finer steps are used when zoomed in. - All variants enter world space via the same
enterWorldSpacehelper (translate to view center, scale by camera zoom, translate by camera position) — scaling once is cheaper than computing screen coordinates per primitive, and the underlay’s parallax of 1.0 means this transform matches the gameplay layer exactly. - Tile-range math computes
startX/startYby flooring the world-space left/top to the tile period, then iterates until the right/bottom edge — guarantees the visible region is fully covered without overdraw beyond one tile period. - Wave variant pads its X iteration by
±WAVE_LAMBDA_Xand its row range by ±1 so the sinusoidal lines don’t visibly clip at the view edges. - Lat/long variant deliberately restricts the hash domain to a 16×16 cell repeat so the bake is tileable; the original implementation used an unbounded
hash2i(col, row)with no period. At a 28% drop rate while the camera is moving, the repeat is not perceptible. - Wave variant scales its alpha by
1.6×relative to the sharedframeAlphaso the live sinusoid reads as strongly as the static baked grids despite being a single stroke path. - Wave variant sets
lineWidth = 1 / camZoomso the world-space stroke renders at roughly one screen pixel regardless of zoom; the baked variants rely on supersampling for crispness instead. - Module-level mutable state (
_hexBake,_hexBakeColor,_latLongBake,_latLongBakeColor) is the only retained state — there is no init/teardown lifecycle and no per-frame allocation in the hot path.