PURPOSE
Render pipeline orchestrator for the game’s main canvas. Owns the 2D canvas context, manages a named layer system with z-index ordering, sets up viewport clipping and DPR transforms each frame, initializes the WebGL2 sprite batch + atlas (with Canvas 2D fallback), and exposes camera-aware world/screen coordinate conversion and visibility culling. Ported from the legacy 08a-render-core.js.
OWNS
Rendererclass — holds the canvas element, itsCanvasRenderingContext2D, aMap<string, RenderLayer>of named layers, and alayerOrderarray sorted ascending byzIndex.webglReadyboolean flag — true once the sprite batch atlas has been uploaded.- Default render layers created in
initializeLayers:background(z=0),world(z=10),particles(z=20),effects(z=30),hud(z=100). RenderLayerinterface —{ name, zIndex, entities: any[] }.- Per-frame canvas state: DPR transform via
ctx.setTransform(dpr, 0, 0, dpr, 0, 0), opaque black clear, and a viewport clip rect of(0, 0, W, H). safeRadialGradienthelper — clamps radii against zero / NaN / negative / inverted ordering before callingctx.createRadialGradient.PALconstant — the game-wide named color palette (player, enemy, shield, xp, station, heat, boss, beacon, text, neutral families).
READS FROM
../core—W,H(CSS-pixel viewport size),camera(withx,y,zoom),dpr(device pixel ratio),game.../core/config—PERF_FLAGS.noBackground(skips the background draw pass when set, e.g. via?noBackground).../core/render-diag—diagBeginPass/diagEndPassfor per-pass diagnostic instrumentation (wraps the background pass)../background—Background.draw(ctx)for the procedural backdrop pass../post-processing—PostProcessingimport (post-FX module)../sprite-batch—initSpriteBatch,getSpriteBatch,getGlowBatchfor the WebGL2 batched sprite path../atlas-builder—buildAtlasto produce the texture atlas canvas uploaded to the GPU.
PUSHES TO
- The HTML
<canvas>element supplied at construction — both via the 2D context for layered drawing and via the WebGL sprite/glow batches (which calluploadAtlasandresize(W, H, dpr)). - Console — a single
console.warnwhen WebGL2 is unavailable and the renderer falls back to Canvas 2D. - Caller-supplied draw callbacks —
render()invokes the passeddrawBackground,drawWorld,drawParticles,drawEffects, optionaldrawLighting, anddrawHUDfunctions in fixed order.
DOES NOT
- Does not draw entities itself —
endFrameiterateslayerOrderbut performs no per-entity drawing; layers are populated and drawn by external systems via therender()callbacks. - Does not flush the sprite batch — uploads the atlas and resizes the batches, but flushing is owned elsewhere.
- Does not own camera state or update camera position — only reads
camera.x,camera.y,camera.zoomfrom../core. - Does not apply post-processing —
PostProcessingis imported but not invoked in this file. - Does not resize the WebGL batch viewports on
resize()— only the backing-store dimensions on the canvas are updated, guarded against no-op reassignment. - Does not handle input, animation, physics, or game state.
Signals
webglReady(public field) — observable flag indicating the WebGL2 sprite batch is ready and the atlas is uploaded; false when WebGL2 is unavailable.console.warn('WebGL2 sprite batch unavailable — using Canvas 2D fallback')— emitted once during_initSpriteBatchifinitSpriteBatch()returns falsy.diagBeginPass('background')/diagEndPass('background')— render-diagnostic markers around the background draw insidebeginFrame.
Entry points
new Renderer(canvasElement)— constructs the renderer, grabs the 2D context (throws'Failed to get 2D context'on failure), initializes default layers, and initializes the sprite batch.render(drawBackground, drawWorld, drawParticles, drawEffects, drawHUD, drawLighting?)— main per-frame entry. CallsbeginFrame, then invokes the four world-draw callbacks in order, thenendFrame, then the optionaldrawLighting, thendrawHUDlast (HUD is drawn on top of any darkness overlay and is un-post-processed).beginFrame()— applies the DPR transform, clears to#000000, sets viewport clip, and runs the background pass (gated byPERF_FLAGS.noBackground).endFrame()— restores the viewport clip and iterateslayerOrder(composite pass; per-entity drawing happens elsewhere).createLayer(name, zIndex)/getLayer(name)/addToLayer(layerName, entity)/clearLayers()— layer management API.worldToScreen(wx, wy)/screenToWorld(sx, sy)— camera-aware coordinate conversion usingcamera.x,camera.y,camera.zoom, andW/H.isVisible(wx, wy, radius=0, margin=100)— viewport visibility test in screen space with default 100-pixel margin.getVisibleBounds()— returns axis-aligned world-space rect of the current viewport with a fixed 500-world-unit margin scaled by1/camera.zoom, suitable for spatial-query culling.setViewportClip()/restoreViewport()— pairedctx.save+clipandctx.restorefor clipping draws to the canvas viewport.getCanvas()/getContext()/getDimensions()— accessors.resize(width, height)— reassignscanvas.width/canvas.heightonly when dimensions change (no-op guard).safeRadialGradient(ctx, x0, y0, r0, x1, y1, r1)— exported standalone helper that clamps radii and coordinates before delegating toctx.createRadialGradient.PAL— exported palette constant.
Pattern notes
- Game code draws in CSS-pixel coordinates; the DPR transform applied at the start of each
beginFramescales those calls to the canvas backing store’s native device resolution.setTransform(notscale) is used so prior transforms are reset rather than compounded. - Layer composition is currently nominal —
layerOrderis sorted on insertion, butendFramedoes not draw layer entities. Systems pre-draw into the canvas during therender()callbacks, and the layer map is a registry surface (addToLayer,getLayer) for systems that want a shared entity bucket. render()defines a fixed pass order: background → world → particles → effects → (viewport restore) → optional lighting → HUD. Lighting/darkness sits between the world and the HUD so the HUD is never dimmed. The HUD is intentionally drawn outside any post-processing chain.- WebGL2 sprite batch is opt-out by failure:
initSpriteBatch()returning falsy triggers a warn and a silent Canvas 2D fallback — there is no hard error andwebglReadystays false. The atlas canvas is shared between the alpha sprite batch and the additive glow batch, and both are sized to(W, H, dpr)at construction. resize()deliberately guards the backing-store reassignment because writingcanvas.width/canvas.heightresets the entire drawing buffer (transforms, styles, clip, content), causing performance collapse and visual glitches if done every frame.safeRadialGradientexists becausecreateRadialGradientthrows on zero / NaN / negative / inverted radii; it clampsr1to0.001, forcesr0 < r1, and zeroes non-finite coordinates to keep the renderer crash-free in the presence of bad input.PALis a flat string-keyed object — colors are looked up by name (e.g.PAL.player,PAL.enemyEngine) so palette tweaks propagate everywhere without touching draw sites.