pixel-face — author & refine the Frontier Faces pixel art
A persona face is pure data in examples/04-frontier-faces/personas.mjs (one entry per
persona). The same data drives the terminal renderer (face-term.mjs), the browser element
(web/cotal-face.js), and the PNG tool — so edit personas.mjs once and it's live everywhere.
Always work from examples/04-frontier-faces/.
Where things are
personas.mjs — the entries (rows, colors, glow, mouths, expr, eyes, lines). Single source of truth.
FACE-DESIGN.md — the full conventions (grid zones, color keys, eye recipe, visemes). Read it first.
tools/img2rows.mjs — rough a reference photo into a 32×32 rows+palette.
tools/render-png.mjs — render a 7-state contact sheet (PNG) for review. This is the validator.
tools/face-template.mjs — copy-me starter entry that encodes the conventions.
gallery.html (all personas, original-vs-pixel) and preview.html?p=<key> (one persona) — served
via python3 -m http.server or tools/serve-wall.mjs; for visual QA.
assets/<persona-key>.<ext> — reference photos. Gitignored / local-only — never commit them.
Persona key ≠ agent name. The roster uses agent-file basenames; the face comes from each agent's
face: frontmatter: elon→musk, steve→jobs, rayan→ray (others match). Persona keys:
neon david ray sven garry jobs musk dario mira bernie michelle.
Conventions (condensed — full detail in FACE-DESIGN.md)
- Grid 32×32,
. = transparent, face centered ~col 15. Zones: hair 1–9 · brows 10–11
(drawn by expr, not rows) · eyes 12–13 (drawn by eyes()) · nose 14–16 · mouth
18–20 (drawn by mouths) · chin 21–24 · collar 26–31.
- Color keys: skin
P light / S mid / s shadow · E pupil · W/w catchlight · R lip ·
m lip-shadow · b brow · h hair · L cyan + M magenta = the cotal glow ribbon
(glow: { L: 8, M: 6 } blurs them). Use rng(row, c1, c2, key) (exported from personas.mjs) for runs.
- Eye recipe: 2-wide iris (
E) on the iris row; row below = catchlight (W/w, OUTER corner)
- lid-shadow (
s, INNER). jobs is the "alive" benchmark — match its eye, don't use flat black blocks.
- Surprise = raised brows + open mouth, eyes stay
open (growing the iris behind glasses looks
wrong). Don't reintroduce a wide eye style.
- The face renders at a fixed 32×16 (not scaled to the pane), so faces look the same size as long
as the pane is ≥ ~32 cols.
Tools (exact)
# rough a reference photo → 32×32 rows + palette (macOS: uses sips)
node tools/img2rows.mjs --in assets/<key>.png --w 24 --h 30 [--colors 12] [--x0 4] [--y0 0]
# render a 7-state contact sheet (neutral·happy·sad·angry·surprised·talking·blink) to review
node tools/render-png.mjs --file <draft>.mjs --out /tmp/<key>.png --scale 8 # draft: export const entry = {…}
node tools/render-png.mjs --file personas.mjs --persona <key> --out /tmp/<key>.png --scale 8 # existing
A draft module must export const entry = {…} and (if it uses rng in expr/mouths)
import { rng } from '/abs/path/examples/04-frontier-faces/personas.mjs'.
After every render, Read the PNG and compare it to assets/<key>.<ext>. Iterate.
Create a new persona
- Pick a
key. If you have a photo, save it as assets/<key>.png and img2rows it for a rough base;
otherwise copy tools/face-template.mjs.
- Hand-edit a
/tmp/<key>-draft.mjs (export const entry) following the conventions above.
render-png → Read the PNG → compare to the reference → iterate until all 7 states read well.
- Splice the finished entry into
personas.mjs (keep the file's style; reuse color keys).
- Review in the gallery; confirm no other persona changed.
Improve an existing persona
- Read its entry in
personas.mjs + its reference assets/<key>.<ext> and the current render.
- Edit only the in-scope rows (e.g. hair = rows 1–9). Preserve
eyes(), glasses/beard,
expr, mouths, colors of other features, and the collar/glow — change the minimum.
render-png the draft → Read → compare → iterate (≥3 passes). Then splice the rows back into
personas.mjs and confirm the diff is limited to that entry.
Multi-agent variant loop (use for likeness — "doesn't look like them")
When matching a real person, generate options instead of guessing:
- Spawn ~3 subagents on the main working tree (NOT git worktrees —
assets/ is gitignored, so
reference photos are absent from a fresh worktree). Give each: the reference path, the current
entry, the conventions, and a distinct direction.
- Each agent writes its OWN
/tmp/<key>-<n>.mjs draft (never edits personas.mjs — avoids
conflicts), renders it, Reads the PNG, compares to the photo, iterates, and returns the final
rows.
- You pick the best, splice it in, review in the gallery. Refine one axis per pass (e.g. head
shape, then hair). This is the loop used for garry/musk.
QA bar
- Always review visually — the
render-png contact sheet and the gallery.html original-vs-pixel
view — never blind. Judge: does it read / look like the person across all 5 expressions + blink?
jobs is the reference standard. Keep faces front-facing/symmetric unless intentional.
- Don't commit
assets/ (reference photos stay local). Persona edits to personas.mjs ARE committable.
1---2name: pixel-face3description: Create or improve a 32×32 pixel-art persona face for the Frontier Faces demo (examples/04-frontier-faces/personas.mjs) — the animated agent avatars rendered by face-term.mjs / the browser <cotal-face> / the gallery. Use when the user asks to add a new persona/face/avatar, improve an existing one (hair, eyes, likeness, expression), make it "look more like" someone, or fix a face that "doesn't read". Drives the example's own tools (img2rows.mjs, render-png.mjs) and follows FACE-DESIGN.md; uses a multi-agent variant loop for likeness.4---56# pixel-face — author & refine the Frontier Faces pixel art78A persona face is pure data in **`examples/04-frontier-faces/personas.mjs`** (one entry per9persona). The same data drives the terminal renderer (`face-term.mjs`), the browser element10(`web/cotal-face.js`), and the PNG tool — so edit `personas.mjs` once and it's live everywhere.11**Always work from `examples/04-frontier-faces/`.**1213## Where things are14- `personas.mjs` — the entries (rows, colors, glow, mouths, expr, eyes, lines). Single source of truth.15- `FACE-DESIGN.md` — the full conventions (grid zones, color keys, eye recipe, visemes). **Read it first.**16- `tools/img2rows.mjs` — rough a reference photo into a 32×32 `rows`+palette.17- `tools/render-png.mjs` — render a 7-state contact sheet (PNG) for review. **This is the validator.**18- `tools/face-template.mjs` — copy-me starter entry that encodes the conventions.19- `gallery.html` (all personas, original-vs-pixel) and `preview.html?p=<key>` (one persona) — served20 via `python3 -m http.server` or `tools/serve-wall.mjs`; for visual QA.21- `assets/<persona-key>.<ext>` — reference photos. **Gitignored / local-only — never commit them.**2223**Persona key ≠ agent name.** The roster uses agent-file basenames; the face comes from each agent's24`face:` frontmatter: `elon`→`musk`, `steve`→`jobs`, `rayan`→`ray` (others match). Persona keys:25`neon david ray sven garry jobs musk dario mira bernie michelle`.2627## Conventions (condensed — full detail in FACE-DESIGN.md)28- **Grid 32×32**, `.` = transparent, face centered ~col 15. Zones: hair **1–9** · brows **10–11**29 (drawn by `expr`, not `rows`) · eyes **12–13** (drawn by `eyes()`) · nose **14–16** · mouth30 **18–20** (drawn by `mouths`) · chin **21–24** · collar **26–31**.31- **Color keys:** skin `P` light / `S` mid / `s` shadow · `E` pupil · `W`/`w` catchlight · `R` lip ·32 `m` lip-shadow · `b` brow · `h` hair · **`L` cyan + `M` magenta** = the cotal glow ribbon33 (`glow: { L: 8, M: 6 }` blurs them). Use `rng(row, c1, c2, key)` (exported from personas.mjs) for runs.34- **Eye recipe:** 2-wide iris (`E`) on the iris row; row below = catchlight (`W`/`w`, OUTER corner)35 + lid-shadow (`s`, INNER). `jobs` is the "alive" benchmark — match its eye, don't use flat black blocks.36- **Surprise = raised brows + open mouth, eyes stay `open`** (growing the iris behind glasses looks37 wrong). Don't reintroduce a `wide` eye style.38- The face renders at a **fixed 32×16** (not scaled to the pane), so faces look the same size as long39 as the pane is ≥ ~32 cols.4041## Tools (exact)42```sh43# rough a reference photo → 32×32 rows + palette (macOS: uses sips)44node tools/img2rows.mjs --in assets/<key>.png --w 24 --h 30 [--colors 12] [--x0 4] [--y0 0]4546# render a 7-state contact sheet (neutral·happy·sad·angry·surprised·talking·blink) to review47node tools/render-png.mjs --file <draft>.mjs --out /tmp/<key>.png --scale 8 # draft: export const entry = {…}48node tools/render-png.mjs --file personas.mjs --persona <key> --out /tmp/<key>.png --scale 8 # existing49```50A draft module must `export const entry = {…}` and (if it uses `rng` in expr/mouths)51`import { rng } from '/abs/path/examples/04-frontier-faces/personas.mjs'`.52**After every render, Read the PNG and compare it to `assets/<key>.<ext>`.** Iterate.5354## Create a new persona551. Pick a `key`. If you have a photo, save it as `assets/<key>.png` and `img2rows` it for a rough base;56 otherwise copy `tools/face-template.mjs`.572. Hand-edit a `/tmp/<key>-draft.mjs` (`export const entry`) following the conventions above.583. `render-png` → Read the PNG → compare to the reference → iterate until all 7 states read well.594. Splice the finished entry into `personas.mjs` (keep the file's style; reuse color keys).605. Review in the gallery; confirm no other persona changed.6162## Improve an existing persona631. Read its entry in `personas.mjs` + its reference `assets/<key>.<ext>` and the current render.642. Edit **only the in-scope rows** (e.g. hair = rows 1–9). **Preserve** `eyes()`, glasses/beard,65 `expr`, `mouths`, `colors` of other features, and the collar/glow — change the minimum.663. `render-png` the draft → Read → compare → iterate (≥3 passes). Then splice the rows back into67 `personas.mjs` and confirm the diff is limited to that entry.6869## Multi-agent variant loop (use for likeness — "doesn't look like them")70When matching a real person, generate options instead of guessing:711. Spawn ~3 subagents **on the main working tree** (NOT git worktrees — `assets/` is gitignored, so72 reference photos are absent from a fresh worktree). Give each: the reference path, the current73 entry, the conventions, and a distinct direction.742. Each agent writes its OWN `/tmp/<key>-<n>.mjs` draft (never edits `personas.mjs` — avoids75 conflicts), renders it, Reads the PNG, compares to the photo, iterates, and **returns the final76 `rows`**.773. You pick the best, splice it in, review in the gallery. Refine **one axis per pass** (e.g. head78 shape, then hair). This is the loop used for garry/musk.7980## QA bar81- Always review **visually** — the `render-png` contact sheet and the `gallery.html` original-vs-pixel82 view — never blind. Judge: does it **read** / look like the person across all 5 expressions + blink?83- `jobs` is the reference standard. Keep faces front-facing/symmetric unless intentional.84- Don't commit `assets/` (reference photos stay local). Persona edits to `personas.mjs` ARE committable.