Sprite Lab · design notes & docs

◓ Sprite

Procedural sprite generation for the people of hoop — the NPCs the econ society already breeds, made visible. We devour the world of what a sprite is, then pare back to the one application: seed-deterministic, atproto-persistable little figures that walk an O'Neill cylinder.

Surface mega.mino.mobi/sprite Owning branch claude/npc-sprite-generation-aeh9e1 Status Stage 0 → 1 — the lab is live at /sprite Consumer hoop (the game wing)

§0 The thesis

hoop already has NPCs. The econ kernel breeds whole societies from a seed — people who "wear many hats" (Jim = mend@chopshop + grow@home + worship), each bound to a stable chamber-address slot (chunk, ordinal) in the deterministic ship engine. What it does not have is a way to look at one of them. Today an NPC is an abstract glyph. This wing closes that gap: it turns a social genome into a picture.

The house method is to devour the world, then pare back — the same arc as fable's wings, the read/ apparatus, and the borges engine. So this document first takes the broadest possible view of what a sprite is, what it is made of, and what limits it; then it collapses that whole space onto the single, sharply-constrained application: an NPC in hoop. The three questions the brief asked — what is a sprite, what are its components, what are its constraints — are answered in Parts I–III at full breadth, and re-answered for our case in Part IV.

§I What is a sprite? — devour

A sprite is a two-dimensional image — historically a bitmap — that is composited into a larger scene as an independent, movable object, most often in a 2D game. It floats on top of the background without overwriting it. That "floating" is the etymology: the term was coined around 1979 by Dave Ackley at Texas Instruments for the TMS9918 video display processor, because the figures hover over the backdrop "like a ghost or mythological sprite."

The hardware → software arc (why the word means two things)

Originally a sprite was a hardware trick, not an image format. Early consoles and home computers had no full-screen framebuffer to spare. Instead, dedicated video hardware composited a handful of small bitmaps per scanline, on the fly, as the CRT beam swept — no CPU, no backing buffer. The TMS9918 (1979) gave the TI-99/4 up to 32 sprites of 8×8 or 16×16 px; the same idea shows up in the Atari VCS (1977), Commodore 64 (1982), NES/Famicom (1983), Genesis, MSX, Amiga.

As machines got framebuffers and fill rate to burn, the optimization stopped mattering and the word migrated to the image itself: today a "sprite" is just a 2D image (and its animation frames) drawn into a scene, whether by GPU, by canvas drawImage, or by a game engine's sprite batcher. Both senses are alive — and, crucially for us, the old hardware sense is a ready-made generative grammar (see §III): its limits are exactly the kind of tight rules that make procedural output cohere.

A taxonomy (the whole world, before we cut)

KindWhat it isRelevant to us?
StaticOne frame, no animation (an icon, a tree).baseline
AnimatedAn ordered set of frames + timing (walk cycle, idle bob).yes — motion
DirectionalDistinct frames per facing (4-way / 8-way), often mirrored.yes — they walk
Layered / "paper-doll"Composited parts (body + head + equipment) with z-order & attach points.core — this is how we vary by role
BillboardA 2D sprite always faced at the camera inside a 3D scene.maybe — econ/foam 3D
Vector / SDFResolution-independent (signed-distance, glyph-like).fits hoop's glyph-world
Procedural / parametricGenerated from a seed or genome rather than drawn by hand.the whole point

§II Components — the anatomy of a sprite

Pull any sprite apart and you find the same organs. This is the full parts list; Part IV keeps only the ones an NPC needs.

ComponentWhat it is
Raster gridThe pixel matrix — width × height. Defines the silhouette and detail budget.
PaletteThe set of colours, often indexed (each pixel is an index, not an RGB). Indexing makes recolouring free — swap the palette, keep the pixels. Usually organized into ramps (a dark→light run per material: cloth, metal, skin).
Transparency keyOne palette index (or alpha channel) reserved as "see through," so the sprite is a shape, not a rectangle.
Pivot / anchor / originThe point the sprite is positioned and rotated about — feet for a walker, centre for a projectile. Wrong pivots = jittery animation and bad placement.
FramesThe individual still images of an animation, each with a duration.
Animation clipsNamed, ordered runs of frames (idle, walk, talk) with loop rules.
Spritesheet / texture atlasMany sprites/frames packed into one image so the engine loads & binds one file. Comes with a metadata sidecar (JSON/XML): per-frame rectangles, pivots, durations.
Hitbox / collision shapeThe interaction bounds — deliberately not the visual bounds (usually tighter). A separate datum from the picture.
Parts & z-orderFor layered sprites: the sub-images and their stacking order (body behind shirt behind held-item).
Attachment socketsNamed points where other sprites mount (hand → tool, head → hat) — this is how one body wears many roles.
Facing / orientationWhich way it looks; often one authored side + a horizontal mirror for the other.
Identity / metadataThe non-visual record: name, seed, kind, the parameters it was generated from. For us this is the durable part.

§III Constraints — the limits (and why we want them)

Historical hardware limits — a found grammar

The classic machines were brutally constrained, and those constraints are famous precisely because they shaped a coherent look:

These aren't just trivia. A limited palette, a small grid, a transparency key, and mirror-symmetry are exactly the rules that make random output read as a believable character instead of noise. The retro constraints are our procedural cohesion engine — we adopt them on purpose, not for nostalgia.

Modern limits

Our constraints — the pare-back (these are non-negotiable)

ConstraintWhy
Determinismhoop invariant #1: (seed, slot, genome) → identical output on every machine and across ATProto repos. No Date.now(), no unseeded Math.random() in the generator, ever. A sprite must be reproducible from its seed alone.
Persistable as a genome, not a blobAn NPC sprite should travel as a compact parameter record (a few bytes of genome) that regenerates the pixels — not a stored PNG. This is what makes it atproto-cheap and identity-stable. Baking to a spritesheet is an output cache, never the source of truth.
Canvas-2D, no build stephoop is pure-static vanilla JS drawn on a 2D canvas at glyph scale. The renderer must run client-side with zero deps and degrade to the current glyph.
Slot-boundA sprite's seed derives from its chamber address gid, so the same NPC is the same face wherever and whenever you meet it.
Crowd-cheapMany NPCs visible at once. Generation must be fast and cacheable; baked frames per distinct genome, reused across identical NPCs.

§IV Pare back — the application: hoop's people

Now collapse the whole space onto one job. A hoop NPC is not decoration; it is a reading of the social genome. The econ kernel already gives every person a structure — we just need the sprite to express it.

Where NPCs come from (already built, in hoop/econ/)

What the sprite must express

The mapping is the design. The sprite's parts and palette are functions of the genome:

Social datum (input)Sprite channel (output)
Primary role / dominant hat (the verb)Held item & head-gear at the attachment sockets (mender → tool, grower → basket, cleric → cowl)
Domain / material they work (matter)Palette ramp bias (metal worker → cool metals; gardener → greens)
Society archetypeSilhouette family & uniformity (company town → uniform palettes; commons → varied)
Affiliation count (how many hats)Accessory density / trim detail
Chamber-address gidThe base seed — fixes body shape, skin ramp, face. Same person, same face, forever.
The win condition: you glance at a street in hoop and can read the economy off the crowd — clusters of like-dressed workers near the chopshop, mixed dress in the commons — without a single label. The sprite layer makes the econ genome legible at a glance.

§V The engine — genome → sprite

A pure, deterministic pipeline mirroring ship.js / the econ genome / borges' generator. Five stages, each a pure function of the one seed:

  1. Seed. Hash the NPC's gid (chamber address) to a 32-bit seed with the house PRNG (xmur3 → mulberry32, as in borges/js/prng.js). All later randomness draws from this one stream — determinism by construction.
  2. Genome. Roll a small parameter bundle: body proportions, the per-material colour ramps (skin / cloth / metal), symmetry axis, and the part selections (head, torso, legs, held-item, head-gear). Where it exists, override rolled genes with the econ society's data (role → item, domain → ramp). This is the "pull" — a genome fully determines a figure.
  3. Compose (paper-doll). Stack the indexed-colour parts on the raster grid in z-order, mounting accessories at sockets. Use vertical mirror symmetry for the body so a single seeded half-grid yields a coherent whole — the cheapest path to "this looks like a creature, not noise" (the classic random-sprite technique).
  4. Palette. Generate ramps in HSL (a hue per material, a dark→light run), then map indices → RGB. Recolour = swap ramps; pixels untouched.
  5. Animate (later stage). Derive — don't author — a small set of clips: an idle bob and a 2–4 frame walk keyed to the NPC's actual motion vector, with an 8-way (mirrored to 4-authored) facing set.

Output forms

Determinism contract & tests

Follow the hoop pattern: a pure, zero-dep kernel with a node selftest (sprite.selftest.mjs) asserting same seed → byte-identical pixels, palette ramps monotonic in lightness, and symmetry exact. The kernel must run in plain node (attach to globalThis like borges) so it tests headless; the page only draws what the kernel returns.

§VI Proof of concept — determinism, live

The original Stage-0 sketch, kept here as the doc's worked example: a seeded, vertically-mirrored, ramp-palette pixel figure whose only job is to prove the load-bearing claim — the same seed always yields the same NPC. This grew into the full Sprite Lab (roles, parts, palette archetypes, export); the kernel below is its seed. Click any figure to copy its seed.

Seeded NPC generator — mulberry32, mirrored, 3-ramp palette

Same seed string → identical figure, here and in node. Roll changes the base seed; each figure in the row is seed#i. This is the symmetry + ramp + seed core only — no parts library, no econ binding, no animation yet (those are Stages 1–3).

§VII Roadmap — devour, then pare

now next later

StageWhat ships
0 · PlanThis document + the seeded proof-of-concept at /sprite. Deploy surface taken over.now
1 · PartsShipping in the lab: pure kernel — roles, material palettes & ramps, mirror symmetry, palette archetypes (company/commons), held-item socket, export. Still pending: a richer parts library + sprite.selftest.mjs to pin the determinism contract.now
2 · BindShipping in the lab: the body now reads off the civic genome — archetype (balanced/dormitory/company/commons) biases the role mix, role sets accent colour + held item + glyph, domain roles dress in their material's hue. Plus anatomy: rounded head, legs + pants, a pareidolia face. Pending: consume hoop's live rolled genome rather than a faithful embedded copy.now
3 · Facing + animateShipped: 8-way facing (front/¾/profile/back via face + hairline + item side; west = mirror of east) and a derived walk cycle per direction — legs scissor (profile) or alternate-lift (front/back), torso bobs, item counter-swings; live in the compass grid, with an 8×N-frame walk-atlas export. Pending: wire the gait speed to the NPC's real motion in world.js.now
4 · PortDrop the renderer into hoop world.js — the @ and ambient NPCs become sprites; degrade to glyph.later
4½ · PortableShipped: the kernel is now a pure ESM module (core.js) with a node selftest; an HTTP API (/sprite/api/sprite.svg|.json|walk.json) renders it canvas-free (SVG = a scalable portable asset); and you can save/load sets to your PDS as com.minomobi.sprite.set (genomes, not pixels) via the shared auth worker. Deploy note: PDS writes need the auth worker redeployed with the new scope (see below).now
5 · HoopPort the renderer into hoop's world.js — NPCs become sprites, gait keyed to real motion; bind seeds to chamber-address gid.later

Stages 1–2 are the kernel and the payoff; everything after is polish. The kernel lives here at mega/sprite/ as a sandbox (the way hoop/paint/ and hoop/econ/ iterate the look before world.js consumes it), then ports into hoop once it's right.

Open questions — decisions to make

  1. Render target: true pixel-art raster (image-rendering: pixelated) vs. an SDF/vector figure that suits hoop's glyph-world aesthetic better. The PoC is raster; hoop's look might want vector.
  2. Where the kernel finally lives: resolved — it's now a pure ESM module at mega/sprite/core.js (browser, API worker, and node selftest all import it). To reach hoop, vendor a copy into hoop/js/ (hoop is pure-static and can't import a sibling at runtime — the hoop/vendor/wayfind.js rule).
  3. Persistence shape: a dedicated com.minomobi.hoop.sprite lexicon, or just a spriteGenome field on the existing person/place record, or nothing on the wire at all (regenerate purely from gid). Cheapest is the last — decide whether players ever customise a sprite (which would force a stored override).
  4. Authored anchors: fully procedural parts, or a small hand-drawn parts library the generator recombines (the Gen-Jam approach). Hand parts read better; pure-procedural is more "infinite."

References — what we devoured