ASCII
ascii Light — runs anywhereA face made of typed characters — nine rows of monospace text that blink, glance, talk and think.
Renders into a DOM <pre> element. No canvas, no WebGL, no images — the face is real selectable text.
What it can express
- A nine-row face, 15 columns wide — hair, brows, eyes, nose, mouth, chin, a blank, a caption row and a thought row — assembled as one string and centred to a fixed 15-column grid
- Six mouth glyphs that actually fire — three closed ('‿' smiling, '⌒' downturned, '‗' neutral) and three open ('o' for the oh vowel, '═' for ee, and 'O' when the mouth is wide open)
- Blink and horizontal gaze — blinking swaps the eyes to '─'; gaze shifts the pupil left or right in three discrete positions — there is no vertical gaze in the glyph
- Brows that lift — on or off — brows are binary: '/' and '\' when lifted, '_' and '_' when not. There is no partial raise and no per-side asymmetry
- Listening cheeks — the cheek brackets thicken from ' (' to '((' while the agent is listening — a whole-face state read at a glance
- Hair that reacts to loud speech — the hair row grows an extra '/\' when speaking with the mouth more than half open
- A thought bubble and an interrupt mark — '. o O ( … )' while thinking, '!' when interrupted
- A spoken-word ribbon — the last 26 characters of transcript ride under the face while speaking, showing the most recent 15
- The five agent states — idle, listening, thinking, speaking, interrupted — the same five every engine here speaks
- Selectable, copyable text — because the face is a <pre>, a visitor can select it, copy it and paste it anywhere — no other engine here can be pasted into a chat message
Moods: 25 protocol moods, folded onto two scalars — a smile bend and a brow lift — then resolved by the glyph table into roughly six distinguishable faces. A real mood layer, but a coarse one.
What it costs a device
| measured on this box | 375×667 | 1440×900 |
|---|---|---|
| cold mount → first frame | 362 ms | 410 ms |
| rAF fps — idle | 60 | 60 |
| rAF fps — speaking | 60 | 60 |
| CPU cores busy — idle | 0.10 | 0.12 |
| CPU cores busy — speaking | 0.13 | 0.12 |
| worst frame gap (p95) | 16.8 ms | 16.8 ms |
| over the wire | 299 KB | 299 KB |
| JS heap | 10 MB | 10 MB |
The empty control page on this box costs 0.08 / 0.10 cores. ASCII costs 0.10 / 0.12 — inside the noise of an empty page, at a locked 60 fps. It is the cheapest thing the estate can put on a screen.
Why light: It costs what an empty page costs. Nothing rebuilds geometry — the whole face is one diffed string assignment per frame into a <pre>, and the browser skips the write entirely when the text has not changed.
Dials it really exposes today
Try it
/alive-site/universal-stage.html
Freestyle Stage — the agent draws its own ASCIIopen/alive-site/freestyle-stage.html
Honest limits
- Two of the eight mouth glyphs never appear. The f/v and m/b/p shapes read viseme keys the frame driver never produces, so those branches are dead on this path — six of eight reachable.
- Brows are on or off, and both move together. No partial lift, no raised single brow, no wink.
- The smile disappears while it talks. Mood only reaches the mouth in the closed-mouth branch, so through a whole spoken sentence the mouth is a viseme glyph and the only mood channel left is the brow lift.
- On the older bridge path (talk.html ?face=basic-ascii) the mood layer is dead — smile and surprise are passed as literal zeros, so all 25 moods collapse to one neutral mouth. Only the mount used by the Universal Stage carries moods.
- No seal/export door. Sketch, SVG and Kinetic can be sealed into a portable .alive.json from the Universal Stage; ASCII cannot. The ASCII wear gate exists only in staged-patches and is not live.
- Keeps animating when scrolled off-screen. Only the SVG engine pauses on offscreen/hidden; the shared frame driver re-arms its rAF loop unconditionally, so a card scrolled out of view still costs what the table says.
- You cannot load this engine alone. The mount contract statically imports every 2D engine, so ASCII, Sketch, SVG and Kinetic each pull the same ~299 KB module graph — measured byte-identical across all four.
Numbers are software-renderer numbers. Measured 2026-08-04 on the estate box
(18 vCPU, load 2.8–5.3 during the run) under chromium_headless_shell-1232 + SwiftShader, 3 runs per cell, median reported.
SwiftShader rasterizes on the CPU, so a real phone GPU changes the picture — most of all for 3D.
Read the ordering between engines, not the absolute figures.
Evidence for every claim: CARD-FACTS.md. Harness: work/measure-engines.mjs.