# FULLNORMIES.
40×40 Faces → Full Body Sprites
HomeSprite EngineGalleryAPI Docs

API Docs

FullNormies generates full-body pixel art sprites for any Normie NFT on demand, server-side, with no dependencies. Each body is derived from that Normie's on-chain portrait width and traits (type, age, gender, facial hair) plus a deterministic seed — so wide faces get broad shoulders, Agents run tall, Young Normies get longer legs, and bearded characters get a thicker neck. Solid blocky silhouettes only; no clothing patterns carved into the body.

Base URL
https://fullnormies.vercel.app/api/v1

All endpoints are CORS open (Access-Control-Allow-Origin: *) so you can call them directly from a browser, game client, or any server. Sprites are cached for 1 hour at the CDN and include ETags for cheap revalidation.

Endpoints
MethodPathReturns
GET/v1/normies/{id}/full.pngSprite PNG (40×80 px, transparent)
GET/v1/normies/{id}/full-meta.jsonMetadata JSON (size, anchor, poses)
GET/v1/normies/{id}/sheet.png7-frame atlas PNG (280×80 px)
GET/v1/normies/{id}/sheet.jsonAtlas layout (frame indices, anchor)
GET/v1/normies/{id}/face.png40×40 face PNG (from normies.art)
HEAD/v1/normies/{id}/full-meta.json200 = exists, 404 = not found
POST/v1/normies/full-metaBatch metadata (up to 50 IDs)
full.png — query params
ParamValuesDefaultNotes
posestand · walk · sit · sleepstandPose family
frame0 · 1 · 2 · 30Walk frame only. 0 = heel-strike R, 1 = passing R, 2 = heel-strike L, 3 = passing L

All sprites face right. Flip horizontally in canvas (ctx.scale(-1,1)) or CSS (transform: scaleX(-1)) for left-facing art.

Placing sprites on a floor

Every sprite’s anchor comes from full-meta.json or sheet.json: { x: 20, y: <per normie> } — bottom-center of the feet in the stand pose (native pixels).x is always half the 40px canvas width; y depends on torso height and leg-length seed (typically mid-50s to low-60s). Always use the meta value so characters line up on a floor regardless of build.

// Fetch meta once per normie (or use known values)
const meta = await fetch(`/api/v1/normies/${id}/full-meta.json`).then(r => r.json())
// → { pixelWidth: 40, pixelHeight: 80, anchor: { x: 20, y: <number from API> } }

// Draw at floor position (floorX, floorY) using the anchor
const img = new Image()
img.src = `/api/v1/normies/${id}/full.png?pose=stand`
img.onload = () => {
  const scale = 4  // upscale 4× for crisp pixel art
  ctx.imageSmoothingEnabled = false
  ctx.drawImage(
    img,
    floorX - meta.anchor.x * scale,
    floorY - meta.anchor.y * scale,
    meta.pixelWidth  * scale,
    meta.pixelHeight * scale,
  )
}
Walk animation

Cycle frame=0 through frame=3 at ~150 ms per frame for a smooth walk loop. The 4 frames are: right heel-strike → right passing → left heel-strike → left passing.

const FRAME_MS = 150
let frame = 0
const img = new Image()

function tick() {
  img.src = `/api/v1/normies/${id}/full.png?pose=walk&frame=${frame}`
  frame = (frame + 1) % 4
}

tick()
setInterval(tick, FRAME_MS)

img.onload = () => {
  ctx.imageSmoothingEnabled = false
  ctx.clearRect(x, y, 40 * scale, 80 * scale)
  ctx.drawImage(img, x, y, 40 * scale, 80 * scale)
}
Spritesheet (atlas)

Load one PNG + one JSON to get all poses in a single HTTP request. The atlas is 280×80 px — 7 frames × 40 px wide.

// Layout (from /sheet.json). anchor.y is per normie — use the fetched value.
{
  "frameWidth":  40,
  "frameHeight": 80,
  "totalFrames": 7,
  "frames": {
    "walk":  [0, 1, 2, 3],
    "stand": [4],
    "sit":   [5],
    "sleep": [6]
  },
  "anchor": { "x": 20, "y": 59 }
}

// Drawing frame N from the atlas:
const frameX = frameIndex * sheet.frameWidth
ctx.drawImage(atlasImg, frameX, 0, 40, 80, destX, destY, 40 * scale, 80 * scale)
Batch prefetch

Prefetch metadata for a full dorm roster in one call. Useful for sizing canvases before images arrive and for checking which normies have pixel data available.

const res = await fetch('/api/v1/normies/full-meta', {
  method:  'POST',
  headers: { 'Content-Type': 'application/json' },
  body:    JSON.stringify({ ids: [1, 42, 627, 9999] }),  // max 50
})
const metas = await res.json()
// → [{ id: 1, exists: true, pixelWidth: 40, ... }, ...]
How the engine works
01
On-chain data
Pixel and trait data is fetched from api.normies.art for each token ID (0–9999). Pixels: a 1600-char string encoding the 40×40 face grid. Traits: JSON attributes (type, age, hair, expression, etc.).
02
Portrait + trait body model
The engine scans face pixels for width, then combines Type, Age, Gender, Expression, Hair, and Facial Feature traits with a token hash. Seven build tiers, eight silhouette styles, nine arm-length tiers, and seven torso heights produce distinct bodies that still read as the same Normie. full-meta.json returns a bodyProfile object plus ready-to-fetch sprite URLs for game clients.
03
Pixel-by-pixel drawing
The engine draws every pixel into a 40×80 buffer — no SVG, no AI. Head pixels come from on-chain data; the body uses a three-zone torso (chest → waist → hips), segmented arms (upper/forearm/hand), knees, and block feet. Six silhouette styles and five build tiers keep variation high while staying blocky and game-ready.
04
Zero-dep PNG encode
The RGBA buffer is encoded to PNG using Node.js's built-in zlib (deflate) with a hand-written PNG chunk serializer. No canvas package, no sharp, no jimp — runs on any edge/serverless runtime.
05
CDN + ETag caching
Sprites are served with Cache-Control: public, max-age=3600 and an ETag. The CDN caches each URL variant (id + pose + frame) independently. Subsequent requests are instant from edge.
06
Palette
Two colors only — #e3e5e4 (light) and #48494b (dark) — matching the Normies monochrome aesthetic exactly. Background is fully transparent (RGBA PNG). Scale to any size with nearest-neighbor for zero quality loss.
OpenAPI spec

Full schema, request/response examples, and caching docs.

openapi.json ↗