Faces
The face belongs to the character, not to a style. All three faces are drawn in the vector, pixel and kaomoji series alike, on the same states, poses and blinks. The default is cute.
cute is the default; pass face="eyes" or face="line" for the others. eyes and line have their own geometry and eye tables. Switching face on a mounted character does not jump: the cute face and the others cross over on the cuteBlend spring.
cute: the default face
The cute face is painted on the body in --aora-face (white on every hue; see Palette). Every state has its own eyes, mouth and blush, and the eyes cycle on an interval. The tables and demos are on Expressions; the mood and activity effects of each state are on States.
On the cute face the belt states do not shrink the body to a bead: the whole character, face included, stays, and what the belt says on the other faces is said by an effect beside it.
The line face
line adds a nose and a mouth to eyes. The sections below are about it: how it keeps one ink and one weight, what the mouth and nose are, and how they follow the state.
One ink, one weight
The eyes face works because it is a single visual idea: a filled body and two cut-outs. Adding a nose and a mouth risks turning that into a drawing with several kinds of line on it. The line face avoids that by keeping the rule:
- Every feature is a stroke in the eye colour — the background on
solid, the ink onoutlineandglass. - Every stroke has the same weight,
LINE_WEIGHTtimes the eye radius, so it scales with the body exactly as the eyes do. - The eyes shrink towards dot proportions (
LINE_FACE_TUNE) so three features share the plate instead of crowding it, and the thin expressions —closed,happy,sleepy— are floored to the line weight so they read as the same stroke as the mouth. - There are no brows, no blush and no second colour.
Every eye topology of the eyes face is available on the line face; it only changes how they are sized and floored. wink is the one topology whose two eyes differ — a happy arc on one side, a neutral eye on the other.
The mouth is six numbers
The lip line is a parabola through the two corners, bowed by bend with its apex pushed sideways by skew, plus one ripple of amplitude wave. The opening is a lens split unevenly — the lower lip drops further than the upper lip rises, the way a real mouth opens. When open is 0 the two lips coincide and only the stroke is visible, so a closed mouth opening into an "o" is one continuous shape rather than a swap.
| id | width | bend | open | wave | skew | dy |
|---|---|---|---|---|---|---|
flat | 0.75 | 0 | 0 | 0 | 0 | 0 |
soft | 0.75 | 0.14 | 0 | 0 | 0 | 0 |
smile | 0.9 | 0.33 | 0 | 0 | 0 | 0 |
grin | 1.05 | 0.39 | 0.21 | 0 | 0 | 0 |
smirk | 0.75 | 0.22 | 0 | 0 | 0.7 | 0 |
o | 0.36 | 0 | 0.72 | 0 | 0 | 0 |
open | 0.75 | 0.21 | 0.93 | 0 | 0 | 0 |
dot | 0.15 | 0 | 0.3 | 0 | 0 | 0 |
wavy | 0.83 | 0 | 0 | 0.17 | 0 | 0 |
frown | 0.75 | -0.27 | 0 | 0 | 0 | 0 |
pout | 0.45 | -0.15 | 0 | 0 | 0 | 0.09 |
hum | 0.51 | 0.21 | 0 | 0.08 | 0 | 0 |
tight | 0.48 | 0 | 0 | 0 | 0 | 0.06 |
Like the eyes, the mouth is parametric for a reason: morphing. Every field interpolates independently and every intermediate value is a valid mouth, so a state change eases from frown to grin on a spring instead of cutting between two outlines.
The nose is chosen once
A nose is not an expression. It is a trait, like a shape, so it is chosen once per character and stays put while the eyes and mouth change.
Six polylines. Each is a single open stroke down the bridge with a small turn for the tip, stored as sixteen points in eye-radius units. Passing nose={null} draws none — a plain line face with eyes and mouth only.
When no nose is given, one is derived from the character's key the same way its shape and ink are: autoNoseForKey('agent-42') is stable and spreads keys evenly across the six. AgentAvatar uses this, so every agent gets a consistent nose with nothing stored.
Every state has a mouth
STATE_MOUTH names one mouth per state, in the same way STATE_EYE_TOPOLOGIES names the eyes. The eyes carry most of the expression already, so the mouth's job is to agree with them — a frown under sad, an "o" under surprised, a wavy line under confused — rather than say something on its own.
Resting
Attending
Feeling
Signalling
Two mouths exist for states the other eleven could not say honestly. hum is closed and content with a ripple running through it: a smile would say "pleased", but the point of humming is that the mouth is shut and something is coming out anyway. It is kept narrow so it does not read as a grin at sixteen pixels. tight is pressed shut — short, flat and a shade lower than flat — and suspicious wears it rather than smirk: suspicion is a mouth held shut while the eyes do the doubting, and a smirk reads as amusement next to a narrowed eye. refusing keeps its frown; that one is a refusal, not merely a reservation.
Six states talk: composing, dictating, messaging, sending, laughing and humming. Their mouth pulses open and shut on two incommensurate sine waves, so it reads as speech rather than as a metronome. The pulse is switched off under prefers-reduced-motion.
One plate
The nose and mouth are projected with the same pose matrix as the eyes and take a share of the same gaze offset — 60% for the nose, 50% for the mouth. Turn the head and the whole face compresses as a unit; glance sideways and the features lean together. Without this the eyes would swing and the mouth would stay put, which reads as a mask sliding on a ball.
Both features are clamped inside the silhouette at their own height, and held above the chin on squat bodies, so a tofu or an onigiri gets a face that fits rather than one that hangs off the bottom edge. The face and pose internals cover the projection.
Switching live
face can change on a mounted character. The eye size, the stroke floor and the feature opacity all ease on one lineBlend spring, so the mouth fades in as the eyes shrink rather than everything snapping. Changing nose crossfades the old nose out and the new one in.
Where it applies
Every component that draws a character accepts face and nose: CharacterMark, StagedMark, AgentAvatar (also through the agent object and through a cast preset), AvatarStack and the static renderers. A CharacterPicker with onSelectFace shows a face toggle, and a nose row when the line face is active.
On a shared stage, face style and nose are part of the appearance key, so a blob with the line face and the hook nose and the same blob with the bow nose are two sources, while two cute blobs with different noses are one — only the line face draws a nose, so a nose on any other face is moot and does not split the stage.
The line face across surfaces
Every component that puts a character on a surface — HudPlate and AgentHud, the editor and its trigger, the template dialog and share card, OccupancyList, the restricted scene and boot loop, CastStage and its members, the walkthrough parts and both pickers — takes face, nose and lineWeight, and passes them down unchanged. A character that is a line-face bun with a hook nose in the roster is the same line-face bun in the editor's preview, on the share card and in the boot sequence.
lineWeight is the third trait, and like the nose it is chosen once per character. It is a multiplier on LINE_WEIGHT, clamped to LINE_WEIGHT_RANGE (0.8–1.25), and one number reaches the nose, the mouth and the HUD dials: the ring outside the body is stroked at the same weight as the features inside it, so the face and its dial are one drawing rather than a face with a chart beside it. The range is deliberately narrow. The weight is the one number that decides whether a character reads as drawn with a pen or with a marker; letting a host set it freely would let one character in a list arrive at a different weight from its neighbours and break the set.
A CharacterPicker takes lineWeight too, so the previews in the grid are drawn with the same pen as the character being edited.
Next
- Expressions — the cute face's eyes, mouths, blush and effects
- Face and pose — how the head turns