Sprite Baking Pipeline
At level boot, atlas-builder.ts pre-bakes enemy, boss, and debris sprites
into a single runtime texture atlas. The atlas canvas is 4096×4096 (bumped
from 2048 in v5.147.1 to fit 17 boss sprites at 256×256), built once via
Canvas 2D and uploaded to the WebGL sprite batch as a GPU texture. Every
quad rendered afterward samples from that one atlas — no per-frame Canvas
2D paths, no per-frame strokes, no per-frame gradients.
Regions are packed by a simple cursor allocator: each _allocRegion(w, h)
call walks left-to-right with 2px bleed padding, wrapping to a new row when
the cursor overflows. Each named region stores its UV coords (u0, v0, u1, v1) in the _regions map and is looked up by sprite identifier via
getAtlasRegion(name). Aspect ratios are tracked separately because PNG
patches can override the default 1.0 square.
The atlas contains utility stamps (white circle, soft glow, particle
square, particle star, ring, missile triangle, bullet glow), one neon
bullet sprite per weapon color, six pre-baked death-shard polygons drawn
with a deterministic LCG so each variant is reproducible across reloads,
the layered XP orb (outer green glow → black outline → green disk →
white-hot core), the crate sticker (📦 emoji with thick black + green
rim), the ship sprite placeholder (512×512 blue triangle, later patched
with the real PNG), and reserved 128×128 placeholder regions for every
enemy archetype × rarity (9 archetypes × 5 rarities = 45 enemy slots).
The enemy placeholders are left empty at build time and patched at
runtime by patchAtlasRegion(name, img) once the red-tinted outlined
PNG sprite loads. After patching, the caller re-uploads the atlas to
the GPU.
bridge-sprite-baking.ts exports the two helpers that produce the
patched images.
bakeOutlinedSprite(img, sz, strokeColor, glowColor, strokePx = 2)
returns a freshly allocated canvas with the source sprite wrapped in a
crisp stroke and an optional soft glow halo. Internally it uses an angular
dilation trick: 32 copies of the source image are drawn on a circle of
radius strokePx, then re-tinted to the stroke color via source-in
composition. Sampling at 32 points eliminates the octagonal artifact you
get from cardinal-only stamping. The composite order back-to-front is:
two glow layers at strokePx+5 (alpha 0.22) and strokePx+3 (alpha
0.38), one dense stroke layer at strokePx (alpha 1.0), then the
original sprite on top. A 14px padding is reserved around the sprite so
the stroke and glow have room to spread without clipping. Passing
glowColor = null skips the halo (common enemies), and using the same
color for stroke and glow gives a single unified rarity tint.
getFogStamp() returns a 256×256 cached canvas of soft white-noise
cloud blobs on a black background. It bakes 12 overlapping radial
gradients with Math.random()-jittered positions and radii (40–100px)
on the first call, then memoizes the canvas in _fogStampCache so every
later call returns the same texture without re-baking. The fog stamp is
drawn as a parallax overlay at 1–3 drawImage calls per frame — cheap
atmospheric fog without per-frame noise generation.
A third helper, bakeStickerEmoji(emoji, sz, fontPx, outerRimPx, innerRimPx, outerRimColor, innerRimColor), lives in atlas-builder.ts
and powers the crate sprite. ctx.strokeText() does not outline
COLR/SBIX color-emoji glyphs in Chromium — lineWidth is silently
ignored on color glyphs. Instead, the helper bakes the emoji once,
derives a binary silhouette mask from its alpha channel, then stamps
the mask at every integer offset inside a disk of outerRimPx (filled
black) and innerRimPx (filled white or accent color) before drawing
the colored emoji on top. Composite order back-to-front: black rim,
white/accent rim, emoji fill — the chunky sticker look used for the
loot crate.
The net effect is that all expensive Canvas 2D operations (strokes, gradients, emoji silhouettes, noise generation) happen exactly once at level boot. Everything else is a quad draw against the atlas UVs through the WebGL sprite batch.
Related
- Bullet archetype renderer — consumes the
per-color
bullet_*regions baked here. - XP orb mechanics — renders the pre-baked
xp_orbregion. - Rarity tints — supplies the stroke and glow colors
passed to
bakeOutlinedSprite. - Debris spawn — uses the six baked
death_shard_*polygons.
Files
src/starship-survivors/engine/rendering/atlas-builder.ts—buildAtlas(), packing helpers, all bake-at-boot sprites,reserveAtlasRegion()/patchAtlasRegion(),bakeStickerEmoji().src/starship-survivors/engine/bridge-sprite-baking.ts—bakeOutlinedSprite()(32-sample angular dilation),getFogStamp()(cached 256×256 noise).