Atlas Region Reservation
The boot-time texture atlas is a 4096x4096 canvas packed with every sprite the game will draw — bullets, particles, enemies, ships, the XP orb, crates, death shards, etc. Not every sprite is available at boot. Some assets (real PNGs for enemy archetypes, ship art) load asynchronously after first paint. The reservation pattern lets the atlas allocate space for those assets up front, so a stable UV region exists from frame one, and lets the loader fill in the real pixels later without re-packing.
The two-call shape
reserveAtlasRegion(name, w, h) runs during buildAtlas(). It calls _allocRegion to bump the packing cursor past a w x h rectangle (plus 2 px bleed padding) and registers a named region with u0/v0/u1/v1 UV coords. No pixels are drawn. The slot exists in the atlas’s layout — sprites that sample this region will read whatever happens to be there (transparent by default after clearRect) until it’s patched.
patchAtlasRegion(name, img) writes an HTMLImageElement or HTMLCanvasElement into a previously reserved slot. It looks up the region’s UV coords, converts back to pixel space (u0 * ATLAS_SIZE, v0 * ATLAS_SIZE, etc.), clears that rectangle, and drawImages the source into it. v5 sprite exports are square, so drawImage scales proportionally to the reserved dimensions.
Caller responsibility — GPU re-upload
patchAtlasRegion only writes to the CPU-side _atlasCanvas. The GPU texture is a separate copy that was uploaded once after buildAtlas. Patching the canvas does nothing visually until the caller re-uploads the atlas to the WebGL sprite batch. The patch function comments this explicitly: “Call after the sprite has loaded. Caller must re-upload atlas to GPU after.” Bridges that patch in PNGs are responsible for triggering the upload (typically via the sprite batch’s uploadAtlasTexture path).
Where this pattern is used
Two boot-time uses live in atlas-builder.ts itself:
- Enemy archetypes (section 4 of
buildAtlas). A nested loop over 9 archetypes x 5 rarities pre-allocates 45 empty 128x128 slots namedenemy_<arch>_<rar>via_registerRegion. The bridge that owns enemy PNG loading patches each slot on first encounter with a red-tinted outlined sprite. No polygon fallbacks — until the PNG loads, the region samples transparent pixels. - Ship sprite (
ship_sprite, 512x512). Baked at boot with a placeholder blue triangle so the player ship renders something on frame one. The real ship PNG patches the same slot when it finishes loading.
External callers use reserveAtlasRegion directly (rather than _registerRegion inside buildAtlas) for sprites discovered after boot — content that wasn’t known at build time but needs an atlas slot. Aspect ratio metadata is tracked separately via setAtlasAspect(name, w/h) so non-square PNGs can be drawn without distortion.
Why reserve rather than re-pack
The packing cursor (_cursorX, _cursorY, _rowHeight) walks left-to-right then row-wraps. Once buildAtlas finishes, the cursor’s state is undefined for further allocations unless reserveAtlasRegion is called inside that same build sequence — it keeps using the same cursor. There is no eviction, no compaction, no second pass. Reserving up front means every UV coord baked into a sprite definition stays valid for the lifetime of the atlas.
Atlas overflow (cursor past 4096 on either axis) emits console.warn('Atlas overflow! Region will be clipped.') and the new region’s UVs point off-canvas. The pattern depends on the boot-time region budget being correct.
Related
wiki/gameplay/concepts/sprite-baking-pipeline.md— how sprites are baked into the atlas at bootwiki/gameplay/systems/rendering.md— the WebGL sprite batch that samples atlas regions