Contents

M10 3D characters: a card swap

ADR-0004 put the stage in 3D and kept characters as 2D cutout rigs on camera-facing cards, so that 3D characters could come later as a swap: "replace a card with a 3D character; nothing else in staging changes". This is that swap. A character whose rig template is biped_3d.v1 is drawn as a stylized 3D figure on its mark. Every other character keeps its card, so a show can mix them.

Read with docs/adr/0004-3d-stage-2d-characters.md, docs/design/m1-engine.md (the card, the pose solver), docs/design/m1-compiler.md ("Metrics") and docs/design/m6-props.md (held props).

Otto as a 3D figure beside the 2D Chief: seated at the helm, in close-up, walking in the engine room, standing at the workbench

What the swap is

  • One field. rig.template: "biped_3d.v1" in a CharacterInstance. Nothing else in the character changes: the same body, head, costume, palette and persona are read. No new schema; the template id was already free-form.
  • Nothing else changes. The compiler, the directors, the framing solver and film grammar never ask how a character is drawn. The Timeline is the same; the engine draws the actor with a figure instead of a card.
  • The same skeleton. engine/addons/reelwright/library/biped_3d.v1.rig.json names biped_toon.v1 as its skeleton. The figure has that template's bones, gestures, expressions and mouth shapes. The core follows the same field to time its gestures (skeletonOf in core/src/compile/gestures.ts), so a cue lasts as long either way. The toon part library lists both templates (its rigTemplates), so a 3D character validates.
  • (implementation) One seam. RwCardFactory.create makes an RwCharacterFigure for the 3D template and an RwCharacterCard otherwise. Both answer the same calls: apply (state, camera, lighting), motion_probe, grip_world, grip_depth_m and get_screen_bounds.

How it is posed

  • The card's pose, in 3D. The figure keeps the card's RwPoseSolver and the design's RwJointLayout. The solver already works in a pseudo-3D character space (cm; +x the character's left, +y up, +z forward, the floor point the origin) before the card flattens it for a view. The figure skips the flattening: every bone is a node set to the pose's frame of that bone.
  • So every channel carries over. Position and facing; posture (stand, sit, crouch, lean, lie) and its blends; the gait cycle (planted feet, swinging arms, hip bob); gestures from the library, with the solver's two-bone IK for aimed ones and reaches for props; looks (eyes, then head, then body); breathing and idle fidget; the float's lift, weightless arms and dangling legs; a landing's squash about the feet.
  • World placement. The body node puts character space in the world: the feet on the floor point plus the float's lift, turned to the facing, stretched about the feet by the pose's stretch (volume kept), cm to meters.

How it is built

From primitives, once per character, each in its bone's rest frame:

  • Torso. Lathed through the joint layout's cross-sections (torso_levels, the same widths and depths the card's torso is drawn from), a piece on each trunk bone (hips, spine, chest) so the spine bends. The pelvis rounds under; the shoulders round into the neck, with a cap at each arm socket.
  • Limbs. Capsules along the upper arm, forearm, thigh and shin, the joint layout's arm and leg radii, thinner toward the hands and feet by the style's limbTaper. Hands are mittens (ellipsoids along the hand bone); shoes are ellipsoids from heel to toe, soles on the floor at rest.
  • Head. An ellipsoid of the design's head width, height and depth. Hair is a dome over the crown and back in the hair color (none when bald). Ears, a nose, two eyes (white, pupil in the iris color), brows in the brow color, a mouth. A walrus mustache or a full beard as an ellipsoid in the facial-hair color.
  • Costume colors. The topmost garment on the trunk dresses the torso; sleeves color the upper arm (short, rolled) or the whole arm (three-quarter, long); the topmost garment with legs colors the thigh (short) or the whole leg (cropped, long). An open outer garment (a jacket or coat over something else) shows what is under it down the chest. Footwear colors the shoes, gloves the hands. Headwear draws a dome with a brim (cap, beanie, hat, helmet) or a strap with two lenses (goggles). A belt is a band round the waist.

What the face does

  • Eyes. Open and close with the expression's eye openness, a squint and the blink. The pupils follow the gaze (the solver's eye direction) and the expression's pupil size.

  • Brows. Rise, tilt and go asymmetric with the expression.

  • Mouth. Opens with the mouth shape of each viseme and the expression's mouth open, wider or narrower with the shape's width and the mouth's curve. (implementation) It draws every Rhubarb shape as the card does, from the same shape parameters: rounding narrows the opening toward a circle (an O, a pucker); the upper teeth are a band along the top of the opening, flush with its front, when the shape shows them (a band on a barely parted mouth for F and V); the tongue lies along the bottom (AA, L). The mouth is set into the face along its normal, low on the head where the face curves under, so a wide opening sits in the face evenly instead of sinking under the skin at the top.

    Otto's 3D figure saying each Rhubarb shape, A to H and rest

  • Not in v1. No smile curve on the lips, no cheek raise or blush, no hand shapes (a fist and an open hand are the same mitten), no costume details (pockets, stripes, emblems, patterns, wear), no smears on fast limbs, no boil, no hair styles beyond the dome. Coats are drawn as jackets.

Light, ink and shadow

  • Light. Surfaces take the set's cel material (RwStyleMaterials.set_surface): the style's shading model and bands, shadow tone, highlight and rim, under the scene's lighting preset, relit when the preset changes. No painted background texture on a body, and no more of a lamp's or a screen's light than it gives 0.6 m off, however close the figure stands to it (as for props).
  • Ink. An inverted hull at the style's character line weight (line.weightPx1080) in the line color (or the fill darkened, as the style says), on every part but the eyes, brows and mouth. Unlike set ink it never thins with depth. Where parts overlap their outlines cross, like a cutout's part lines.
  • Shadow. The style's ground shadow: the figure casts a projected one from its surfaces, or stands on the card's blob (RwCharacterCard.make_blob and place_blob, shared), which shrinks and fades as the figure floats.

On the model sheet

(implementation) A figure's model sheet (render_preview model_sheet, reelwright preview model_sheet) draws the figure, not its card: the turnaround, the expressions and mouths and the line-up each in their own 3D viewport, on the transparent sheet. It is seen from 20 m on a lens that gives the card's scale, its feet (or, for a bust, its eyes) where the card's would be, under a studio light: a near-white key from the upper left front, a soft cool fill and a light grey ambient. The cel shader takes the light's color from its own uniforms but lights only where a key light shines, so each viewport has one, white, at unit energy and casting no shadows. Before, a 3D character's sheet showed its card.

Otto's figure turned round on his model sheet

The contract with the compiler

  • Framing, look targets and prop hands are computed by the core from the metrics (core/src/compile/metrics.ts). The figure draws on the same skeleton, which the engine's tests hold to those formulas, so:
    • the hips are exactly the posture's hip height, and the top of the head is within 5% of the metric top in every posture;
    • the drawn eyes are within 3 cm of the solver's eye point, where looks and gaze aim from;
    • standing and sitting (the postures dialogue is framed in) the drawn eye line is within 5% of the metric eye (1.5% for the demo cast). In a crouch or a lean the head pitches with the body, and the face, ahead of the head's center plane the metric eye is measured on, drops below the metric line: up to 7% of the height for the Chief's hunched crouch, so a close shot there frames a little above the eyes.
  • A held prop's grip goes where the drawn hand is: grip_world is the solver's grip point in the palm, in the world.

Proof

(implementation) engine/tests/character/rw_character_figure_test.gd: the factory picks the figure for the template and the card otherwise; the template shares the toon skeleton with no warning; hips, top and eyes against the metrics as above in four postures for both of the demo cast; it stands on its floor point facing its way; a walk swings the feet out of phase; a grip is in the drawn hand; a float lifts it and a landing squashes it about its feet; the same state renders the same bytes, its lowest drawn row on the floor point. And core/test/compile.test.ts: a cast of figures times every gesture from the toon library. (implementation) engine/tests/character/rw_character_render_test.gd, "a 3D character's model sheet draws its figure": the turnaround differs from the card's, is drawn, and is lit as the card is (the card's commonest color covers a third as much of it, at least; without the key light it covered none). (implementation) engine/tests/character/rw_character_figure_mouth_test.gd: each mouth shape close up, shut, toothed, tongued and rounded as above (the image is its close-ups). Watched on lavapipe (2026-10-01): the demo show with Otto on biped_3d.v1, the Lighthouse and a heuristic cut of The Salvage Claim (the image above). The demo show's characters stay on cards, and its golden stills do not move.