---
date: 2026-07-06
tags: [catblaster, catblaster-adjacent, tooling, avatar, meatsuit, pending-ratification]
aliases: ["Meatsuit Generator", "Avatar Builder", "Meatsuit Forge"]
source: original
status: growing
related: ["[[catblaster-claude-engagement-articles]]", "[[project-catblaster-level-forge]]", "[[project-book-of-ships]]", "[[project-catblaster-sprite-standards]]"]
area: "02-areas/catblaster"
authors: ["Alex Fulton"]
rights: alex-owned
---

> [!summary]
> The **Meatsuit Generator** is a layered pixel-art avatar builder — a sibling of the Sprite Forge — that assembles CATBLASTER-canon character busts ("meatsuits") from swappable parts (Torso / Head / Nose / Mouth / Eyes / Hair), one asset library per house. Phase 1 (local builder) is **built and headless-verified**; the public product at `meatsuitgenerator.com` (signups + per-user save) is **Phases 2–3, pending ratification**.

Meatsuits are **Alex Fulton's IP** and **CATBLASTER canon**. Per the [[catblaster-claude-engagement-articles|Articles of Engagement]], this tool is a **machine that arranges Alex's art** — it composites PNGs he authored. It does not generate appearances, lore, or defaults.

## What it is

A stdlib Python web app (`_scripts/meatsuit_server.py`, `localhost:8790`) built on the exact stack as [[project-book-of-ships|book_server.py]] and forge_server.py — `BaseHTTPRequestHandler` + `ThreadingHTTPServer`, PIL, no third-party framework, macOS system-Python compatible.

The builder loop:
- **Browse & swap** — the left column lists parts **grouped by house** (house header → its layer slots); click any part to fill that slot, click it again to clear. Parts can be **mixed across houses**; the bust live-composites on an HTML canvas (pixel-perfect, upscaled). No house selector — you pick straight from the grouped column.
- **Load assets — batch drop + verify** (Alex's ask) — drop one PNG or a whole set at once. Each filename is auto-parsed for house + slot + variant (`Stal Head 1.png` → stal/head/head-1); a **verification overlay** shows every part with a confidence badge (high/medium/low) and editable house + slot + name. Anything not confidently detected is flagged **not ready** and can't import until you set it — nothing lands mis-filed. Confirmed parts commit to `assets/<house>/<category>/<slug>.png` (never overwrites — auto-suffixes) and appear immediately. Endpoints: `/parse` (pre-flight guess) + `/import` (commit batch).
- **Save a meatsuit** — writes the recipe JSON **plus** a flat 64×64 PNG and an 8× (512px) profile PNG to `00-inbox/meatsuit-output/<id>/`.

## The meatsuit recipe (data contract)

Save persists the *recipe*, not just a picture — so one meatsuit serves all three outputs Alex specified (downloadable PNG · game/sprite feed · profile image) and can re-render at any size:

```json
{
  "meatsuit": "stal-captain-jax",
  "name": "Captain Jax",
  "house": "stal",
  "layers": {
    "torso": { "house": "stal", "file": "torso-1.png" },
    "head":  { "house": "stal", "file": "head-1.png" },
    "nose":  { "house": "stal", "file": "nose-1.png" },
    "mouth": { "house": "stal", "file": "mouth-1.png" },
    "eyes":  { "house": "stal", "file": "eyes-2.png" },
    "hair":  { "house": "stal", "file": "hair-1.png" }
  },
  "z_order": ["head","torso","nose","mouth","eyes","hair"],
  "canvas": [64, 64],
  "created": "2026-07-06T19:27:27+00:00"
}
```

Each layer carries its own `house`, so parts can be mixed across houses. The top-level `house` is the meatsuit's *primary* house — the torso's house (or the most-used house if no torso).

## Asset library layout

```
02-areas/catblaster/meatsuit/assets/<house>/<category>/<part>.png
```

- **Houses:** the canonical nine (Kirkland · Stal · Pearson · Roxdun · Hodel · Zikaron · Xingwu · Bukhara · Vahana).
- **UI slot order:** torso · head · nose · mouth · eyes · hair.
- **Compositing z-order (bottom→top), independent of slot order:** head · torso · nose · mouth · eyes · hair — head sits *behind* the torso so the neck/shoulders overlap the head's base.
- **Seeded:** the **Stal** starter set (from `00-inbox/AF Sprites/Stal Character Bust Options`, copied — originals untouched): 1 each of torso/head/nose/mouth/hair, 3 eyes. All 64×64 RGBA.

## Skin / hair / eye palettes (grayscale → ramp remap)

Added 2026-07-07 (Alex's ask; eye color added same day). Every element can be recolored for **skin**, **hair**, and **eye color** without re-drawing it. The method is a **grayscale-map → color-ramp remap**: a part is desaturated to its own luminance, that luminance is normalized (percentile-clipped so faded art still uses the full range), and remapped through an ordered ramp (**dark → light**, shadow → highlight). Shading is preserved for free across every tone. Pure PIL, numpy-free. Per the [[catblaster-claude-engagement-articles|Articles]] this is mechanism only — Alex owns the ramps and the role assignments.

- **Palettes** — editable `assets/palettes.json` (`skin` + `hair` + `eyes` lists, each `{id, name, ramp:[hex…]}`). Seeded naturalistic (7 skin porcelain→ebony, 7 hair, 7 eyes incl. a sci-fi violet); the app re-reads the file every render, so curating/extending tones needs no code change. Three ramp-chip pickers sit under the canvas.
- **Per-element tint roles** — each part is `skin` / `hair` / `eyes` / `fixed`. `fixed` parts composite in their **authored colors** (untouched). Category **defaults** (head/nose/mouth = skin, hair = hair, eyes = eyes, torso = fixed) are **code-authoritative**; only **per-file overrides** persist to `assets/tint-roles.json`. A badge on each part swatch (S / H / E / –) **cycles the role on click** and persists it — how a torso that's bare skin vs. armor, or brows that should follow skin vs. eye color, gets handled per-part.
- **Eyes use an explicit iris mask** — the eyes element is ~90% red brow markings and only ~6–8px is the actual iris, so a luminance band can't isolate it (brow and iris overlap in brightness, differ in hue). A companion mask **`assets/<house>/eyes/<stem>.iris.png`** marks exactly which pixels are iris; eye color recolors **only** those, so brows + sclera keep their authored colors (brows stay Stal red, per decision). Masks are excluded from the library scan. `python3 meatsuit_server.py --make-iris-masks` writes **starter** masks (cool/non-skin pixels) to hand-refine; `--force` overwrites. No mask yet → falls back to the luminance band-limit (`EYE_KEEP_LOW`=0.16 / `EYE_KEEP_HIGH`=0.80). Verified: 5 eye colors change only the iris, brows stay red.
- **Detail retention** — the ramp remap otherwise flattens the original's hand-painted chroma nuance (warm cheeks, cool shadows). `apply_ramp(detail=)` re-injects a fraction of the original's per-pixel chroma offset on skin/hair; a live **Detail slider** (0–100%, default 45%) sits under the pickers; recipe stores `detail`. 0 = flat ramp (old behavior), ~1 over-saturates.
- **Auto-derive** — works on the *existing* full-color PNGs (no grayscale re-authoring needed); luminance is derived on the fly.
- **Preview = output** — the live preview is composited **server-side** (`POST /render` → PNG) using the *same* `composite()` code as save, so the on-screen bust is byte-identical to the saved `meatsuit.png`. Endpoints: `/render` (live preview) + `/tint-role` (persist a role).
- **Recipe** gains `skin_palette` + `hair_palette` + `eyes_palette` (palette ids) so any meatsuit re-renders to the exact same colors.

Verified headless 2026-07-07: distinct palettes yield distinct composites (skin, hair, and eye proof sheets all rendered correctly on the Stal bust), eye band-limit keeps sclera white + pupil dark while the iris changes, roles persist/revert, transparent pixels untouched, save bakes + records all three palettes.

## Roadmap

1. **Phase 1 — Local builder** ✅ built + headless-verified 2026-07-06 (all endpoints, composite renders a correct Stal bust, upload + save confirmed on disk). **Skin/hair palette remap added + verified 2026-07-07.**
2. **Phase 2 — Deploy `meatsuitgenerator.com`** — Cloudflare Pages, same front-end. *Pending: Alex authorizes Cloudflare (interactive OAuth) + confirms domain ownership.*
3. **Phase 3 — Public signups + per-user save** — Workers API + D1 (accounts + recipes) + R2 (assets + renders). Local Python backend swaps for Workers behind the same UI.

> [!warning] Pending ratification
> The public product (Phases 2–3: `meatsuitgenerator.com`, public account signups, hosted per-user avatar storage) is an **outward-facing launch** and parks in [[ratification-queue]] until Alex signs off on the specifics (auth provider, storage, privacy posture). Phase 1 (local tool) needs no ratification. Remove `pending-ratification` from tags on ratify.

## Open questions

- **Z-order** — now head→torso→nose→mouth→eyes→hair (head behind torso, fixed 2026-07-06). Confirm hair vs. head for over-forehead styles is right.
- **Cross-house parts** — now **supported** (2026-07-06): the column groups by house but any slot can take a part from any house, and each layer records its own house. Flag if you'd rather lock a meatsuit to a single house.
- **Guide/wiki emitter** — like the Forge's dossier card, should saved meatsuits feed a roster page? (Deferred.)
