atlas-loader
PURPOSE
Fetches a per-category VFX atlas manifest + its per-component PNG layers from /src/starship-survivors/data/vfx/atlas/, then uploads the decoded pixel data into a GPU texture-array via a batch interface. Bridge between baked atlas assets on disk and the WebGL renderer.
OWNS
AtlasManifestFileinterface — JSON shape:category,format,atlasDir, and alayers[]array. Each layer:componentId,layerIndex,frameCount,fps,tileSize,gridCols,gridRows,loopMode('oneshot' | 'loop' | 'parameterized'),pivot {x, y},bakedAt.LoadedAtlasinterface —{ manifest, layerImages: Map<string, HTMLImageElement> }keyed bycomponentId.loadAtlas(category)— async fetch + decode pipeline.uploadAtlasToTexture(batch, atlas)— CPU-side decode-to-pixels + per-layer GPU upload.
READS FROM
- HTTP fetch:
/src/starship-survivors/data/vfx/atlas/${category}.manifest.json(manifest JSON). - HTTP image load:
/src/starship-survivors/data/vfx/atlas/${category}/${componentId}.png(one PNG per layer, loaded in parallel viaPromise.all). AtlasCategorytype from./atlas-format.
PUSHES TO
batch.createEmptyAtlas(tileSize, gridCols, gridRows, layerCount)— allocates a 2D-array texture sized off layer 0.batch.uploadLayer(layerIndex, tileSize, gridCols, gridRows, pixels: Uint8Array)— uploads one layer’s RGBA bytes per layer.- Returns
LoadedAtlas | nullto caller;nullon fetch failure or non-OK response.
DOES NOT
- Does NOT define
AtlasCategory(imported fromatlas-format). - Does NOT create the WebGL context, the renderer, or the
batchobject — caller supplies it. - Does NOT cache atlases across calls — each
loadAtlasre-fetches. - Does NOT validate manifest shape at runtime — trusts the JSON.
- Does NOT handle partial-failure of layer PNG loads — one rejected image rejects the entire
Promise.all. - Does NOT crop, repack, or resize images — assumes the PNG already matches
tileSize * gridColsxtileSize * gridRows. - Does NOT free the temporary
<canvas>it allocates per layer (relies on GC). - Does NOT animate, sample, or interpret
frameCount/fps/loopMode/pivot— those are passthrough metadata for downstream sampler code.
Signals
loadAtlasreturnsnullon fetch error or HTTP non-OK; throws (viaPromise.allrejection) if a layer PNG fails to decode.uploadAtlasToTextureearly-returns whenmanifest.layers.length === 0.uploadAtlasToTextureskips any layer whosecomponentIdis missing fromlayerImages(continue).
Entry points
loadAtlas(category: AtlasCategory): Promise<LoadedAtlas | null>— public, async, called at workbench / scene init.uploadAtlasToTexture(batch, atlas): void— public, sync, called afterloadAtlasresolves and a WebGL batch exists.
Pattern notes
- Two-phase init: fetch JSON + decode images (
loadAtlas) → CPU canvas readback + GPU upload (uploadAtlasToTexture). Splits async I/O from sync GL work so callers can sequence GL state changes. - Texture-array sizing trusts layer 0:
createEmptyAtlasuseslayers[0].tileSize / gridCols / gridRowsfor the whole array. Mixed-size layers within a category are not supported. - CPU readback via 2D canvas: each PNG is
drawImage’d into a fresh<canvas>, thengetImageData(...).datais wrapped asUint8Array(data.buffer)for upload. This is the standard browser path to get raw RGBA bytes from anHTMLImageElement. - Soft-fail on missing manifest: returning
null(rather than throwing) lets callers gate on atlas availability without try/catch — consistent with the workbench being a dev tool that may not have baked assets. - Duck-typed
batch: the parameter is a structural interface (createEmptyAtlas,uploadLayer), not a concrete class import, so this module stays renderer-agnostic.
EXTRACT-CANDIDATE
AtlasManifestFilelayer schema (componentId,layerIndex,frameCount,fps,tileSize,gridCols,gridRows,loopMode,pivot,bakedAt) is the same shape the bake pipeline writes and the sampler reads. Worth promoting to a shared type inatlas-format.tsif it isn’t already, so loader / baker / sampler can’t drift.- The “image → canvas → getImageData → Uint8Array” RGBA-extraction helper is generic; if any other module needs raw bytes from an
HTMLImageElement, extractimageToRgbaBytes(img, w, h)into a shared util.