Sprite Lab · design notes & docs
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.
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.
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."
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.
| Kind | What it is | Relevant to us? |
|---|---|---|
| Static | One frame, no animation (an icon, a tree). | baseline |
| Animated | An ordered set of frames + timing (walk cycle, idle bob). | yes — motion |
| Directional | Distinct 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 |
| Billboard | A 2D sprite always faced at the camera inside a 3D scene. | maybe — econ/foam 3D |
| Vector / SDF | Resolution-independent (signed-distance, glyph-like). | fits hoop's glyph-world |
| Procedural / parametric | Generated from a seed or genome rather than drawn by hand. | the whole point |
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.
| Component | What it is |
|---|---|
| Raster grid | The pixel matrix — width × height. Defines the silhouette and detail budget. |
| Palette | The 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 key | One palette index (or alpha channel) reserved as "see through," so the sprite is a shape, not a rectangle. |
| Pivot / anchor / origin | The point the sprite is positioned and rotated about — feet for a walker, centre for a projectile. Wrong pivots = jittery animation and bad placement. |
| Frames | The individual still images of an animation, each with a duration. |
| Animation clips | Named, ordered runs of frames (idle, walk, talk) with loop rules. |
| Spritesheet / texture atlas | Many 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 shape | The interaction bounds — deliberately not the visual bounds (usually tighter). A separate datum from the picture. |
| Parts & z-order | For layered sprites: the sub-images and their stacking order (body behind shirt behind held-item). |
| Attachment sockets | Named points where other sprites mount (hand → tool, head → hat) — this is how one body wears many roles. |
| Facing / orientation | Which way it looks; often one authored side + a horizontal mirror for the other. |
| Identity / metadata | The non-visual record: name, seed, kind, the parameters it was generated from. For us this is the durable part. |
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.
| Constraint | Why |
|---|---|
| Determinism | hoop 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 blob | An 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 step | hoop 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-bound | A sprite's seed derives from its chamber address gid, so the same NPC is the same face wherever and whenever you meet it. |
| Crowd-cheap | Many NPCs visible at once. Generation must be fast and cacheable; baked frames per distinct genome, reused across identical NPCs. |
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.
hoop/econ/)verb@place affiliations
(mend@chopshop, grow@home, worship). Their hats say where they walk;
the "desire-line roads" are literally the Laplace transform of that NPC motion.postal.js to a stable
(chunk, ordinal) chamber address.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 archetype | Silhouette family & uniformity (company town → uniform palettes; commons → varied) |
| Affiliation count (how many hats) | Accessory density / trim detail |
Chamber-address gid | The 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.
A pure, deterministic pipeline mirroring ship.js / the econ genome / borges' generator.
Five stages, each a pure function of the one seed:
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.@'s
position (Stage-0 path; replaces the glyph).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.
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.
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).
now next later
| Stage | What ships | |
|---|---|---|
| 0 · Plan | This document + the seeded proof-of-concept at /sprite. Deploy surface taken over. | now |
| 1 · Parts | Shipping 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 · Bind | Shipping 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 + animate | Shipped: 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 · Port | Drop the renderer into hoop world.js — the @ and ambient NPCs become sprites; degrade to glyph. | later |
| 4½ · Portable | Shipped: 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 · Hoop | Port 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.
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).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).