PURPOSE
Pre-renders all sprite assets onto a single 4096×4096 texture atlas at game startup. Programmatically bakes enemy shapes, bullet archetypes, particle stamps, the XP orb, crate emoji, ring/star/triangle utility sprites, glow gradients, death-shard polygons, and the ship-sprite placeholder onto one Canvas 2D surface, then hands the canvas to the WebGL sprite batch for GPU upload. No external image files — every sprite is drawn from code or baked from emoji glyphs.
OWNS
ATLAS_SIZEconstant — 4096 px (bumped from 2048 in v5.147.1 for 256×256 boss sprites; 4096 is the WebGL2 / GLES2 guaranteed minimum).- Packing cursor state —
_cursorX,_cursorY,_rowHeight(module-level shelf packer with 2 px padding). _regionsmap —name → AtlasRegionUV coords, the canonical lookup table for every named sprite in the atlas._aspectRatiosmap — per-region width/height ratio, default 1.0 (square)._atlasCanvas— module-level reference to the built canvas, kept for runtime patching after initial bake.- The
BULLET_COLORStable (13 weapon colors: rifle, shotgun, missile, revolver, cannon, mortar, disc, flame, lightning, railgun, sweep, shield, line). - The
ENEMY_ARCHETYPES×ENEMY_RARITIESreserved-region matrix (9 × 5 = 45 empty 128 px slots for runtime PNG patching). - Six baked death-shard polygon variants with deterministic per-variant LCG seeding (no
Math.random()at module init). - Internal sprite renderers
_drawCircleSpriteand_drawNeonBullet, and the temp-canvas helper_makeTmpCanvas.
READS FROM
AtlasRegiontype imported from./sprite-batch.- DOM
document.createElement('canvas')andCanvasRenderingContext2D(Canvas 2D + system emoji font viasans-serif).
PUSHES TO
- Returns the built
HTMLCanvasElementfrombuildAtlas()— the renderer hands this canvas to the WebGL sprite batch as a texture. _regionsmap is read by every sprite-drawing call across the engine viagetAtlasRegion/tryGetAtlasRegion._atlasCanvasis exposed viagetAtlasCanvas()so the bridge can patch PNG sprites into reserved regions after first load.
DOES NOT
- Does not upload the texture to the GPU — that’s the sprite batch’s job after
buildAtlas()returns the canvas. - Does not load external image files or PNGs — every initial sprite is drawn programmatically. PNG patching happens later via
patchAtlasRegion, driven by the bridge. - Does not draw polygon enemy fallbacks — enemy archetype regions are reserved empty and stay empty until the runtime patches them with red-tinted outlined PNGs on first encounter.
- Does not handle dynamic atlas growth — overflow logs a
console.warnand the offending region gets clipped. Sizing is fixed at 4096. - Does not animate anything — atlas is baked once and only mutated via explicit
patchAtlasRegioncalls. - Does not use
Math.random()for death-shard baking — uses a deterministic per-variant LCG so the bake is reproducible across reloads. - Does not use
ctx.strokeTextto outline color-glyph emoji — Chromium silently ignoreslineWidthon COLR/SBIX glyphs, so it builds a silhouette mask and stamps it at every offset inside a disc instead.
Signals
None. This module has no events, observers, or pub/sub. Coordination with the renderer and bridge is via direct function calls and the shared _regions / _atlasCanvas module state.
Entry points
buildAtlas(): HTMLCanvasElement— bakes the full atlas; called once by the renderer at startup.getAtlasRegion(name): AtlasRegion— throws if missing.tryGetAtlasRegion(name): AtlasRegion | null— returns null if missing.getAtlasRegionNames(): string[]— debug-only listing of registered names.getAtlasAspect(name): number/setAtlasAspect(name, ratio): void— per-region aspect ratio.reserveAtlasRegion(name, w, h): { x, y, region }— allocates and registers an empty region for later patching.patchAtlasRegion(name, img): void— draws a loaded image into a previously reserved region; caller is responsible for re-uploading the atlas to the GPU.getAtlasCanvas(): HTMLCanvasElement | null— returns the live atlas canvas for patching.bakeStickerEmoji(emoji, sz, fontPx, outerRimPx, innerRimPx, outerRimColor?, innerRimColor?): HTMLCanvasElement— standalone exported helper for chunky sticker-style emoji rendering; used by the crate region internally and by other modules (e.g. crates code) for ad-hoc emoji sprites.
Pattern notes
- Shelf packing. Sprites are placed left-to-right into rows. When the cursor would overflow the right edge,
_allocRegionadvances to a new row sized by the tallest sprite in the previous row, plus 2 px padding to prevent texture bleeding under bilinear filtering. - UV coordinates only.
_registerRegionstores only normalizedu0/v0/u1/v1floats in the range 0–1; pixel coordinates are not retained. Patching reconstructs pixel bounds by multiplying back byATLAS_SIZE. - Bake order matches code order. Regions are allocated in declaration order: white circle, glow stamp, bullet archetypes, reserved enemy slots, particle square, death shards, star, ring, missile triangle, bullet glow, XP orb, crate, ship-sprite placeholder. Adding new sprites at the end is safe; reordering changes UV layout and forces a re-bake on next boot.
- Empty placeholders for runtime patching. Enemy archetype × rarity regions are reserved but not drawn, then patched by the bridge once PNG sprites load. There is no polygon fallback — pre-patch enemies render as transparent quads.
- Sticker emoji technique.
bakeStickerEmojiderives a binary silhouette mask from the colored emoji’s alpha viasource-in, stamps the mask at every offset within an outer-rim radius (filled black) and inner-rim radius (filled custom color, e.g. crate uses bright green#3ce06b), then composites the colored emoji on top — outer rim, inner rim, emoji fill. This is the only reliable way to outline color-glyph emoji in Chromium. - Death-shard determinism. Per-variant LCG seed
(v + 1) * 9301with the standardseed = (seed * 9301 + 49297) % 233280recurrence; shards are baked white and tinted red at draw time via sprite-batch vertex color, so the bake doesn’t fork per color shade. - Crash-on-bad-data.
getAtlasRegionthrows on missing name; onlytryGetAtlasRegionandpatchAtlasRegionsilently no-op for unknown names. Atlas overflow logsconsole.warnbut does not crash. - Module-level mutable state. Both packer cursor and
_atlasCanvasare file-scopedlets.buildAtlasresets the cursor on each call, but calling it twice would double-register every region; treat it as one-shot per session.