Character Studio help

The same SVGMotion editor — canvas, timeline, inspector, Connect graph, AI dock, exports — wearing a character hat. Three extra things on top: rig roles, a micro-action verb kit, and a Stage floor where many entities act together under an AI director. This page mirrors the main editor guide — everything in it still applies here. Two guided tours run live inside the studio — the quick tour (rig → micro → macro → world, ~2 min) and the full tour (everything incl. keyframing, links and the director) — plus a How it's made walkthrough on every gallery item.

Quickstart

npm run dev                         # → http://localhost:4173/characterstudio
ollama serve && ollama pull qwen3:8b  # optional — same providers as the editor

Character Studio shares the editor's ☰ Menu → Settings… → AI provider — Ollama, OpenRouter, or OpenAI, BYOK, keys stay in this browser. Settings → Advanced can also enable a local browser AI engine (WebLLM, runs on this device) that handles tool work — commands, edits, and world-director beats — before any provider is needed. With neither, everything works offline; the world director falls back to a deterministic built-in brain so the choreography pipeline never dead-ends.

Fastest first session Open the studio → New character… → Blank biped rig (arrives fully rigged) → *Micro actions* panel → Wave + → it plays. Switch to Stage → ⇪ Send to stage → + Cast tray → click a prop tile → type blobby pick up bone in the command bar. Or just run the tour: Get started → ▶ Tour / ☰ Menu → ▶ Studio tour walks the same loop live, and Help → ▶ Full tour covers everything end-to-end.
No sections match — try a different word.

Core concepts

TermMeaning
Rig roleA semantic alias for a layer — head, body, legL/R, armL/R, tail, earL/R, wingL/R, snout, mouth, prop. Verbs find parts through roles, not layer names.
ArchetypeThe role spec a creature is checked against: blob, biped, quadruped. Aliases bridge them — arm means a biped's arm or a quadruped's front leg; mouth resolves to a pet's snout.
Micro-actionA small ordinary action that poses only the parts it needs (wave = one arm). Same machine as editor actions — keyframes, easing, transitions.
Micro-verbA parameterized function that builds a transient micro from args — wave({side:'r', duration:400}). The panel, the command bar and the AI all call these.
MacroA named chain of micros — greet = nod → wave → bow — wired as ordinary complete transitions, or queued verb calls with $target/$self bindings.
EntityAnything on the world floor — a rigged character (●) or a prop (▣, any unrigged .svg). Each keeps its own actions and click triggers.
LinkAn entity→entity rule: when source finishes action X / is near / touches target → target plays action Y.
DirectorThe world-level AI that sees the full cast state and returns {thought, actions[]} beats, dispatched through the same invoke() as everything else.
UserYou — a ◈ presence on the floor, not an actor. Characters can face(user) / walkTo(user); drops and clicks emit events the director reacts to.

The three modes

ModeFor
RigBuild the character — draw/import parts, assign roles, add micros, keyframe on the same timeline. This is the editor canvas.
ConnectThe same state-machine graph — macro chains show up as readable rows of nodes. Rig/world panels park here.
StageThe floor — sidebar hides, cast tray spawns entities, drag them, link them via the console, type commands, let the director run beats.

The mode rail sits where the editor's view switcher lives; every panel, shortcut (B/I/M/G/F), the AI dock, autosave, undo and export behave exactly as in the main editor. Canvas gestures too — click a part to select it, drag to move it, or use the arrow keys to nudge the selected layer or shape (⇧ = 10×); with nothing selected ←/→ step the playhead.

The sidebar is contextual: instead of every studio section open at once, it keeps the relevant one expanded — select a rigged part → Parts, select something unrigged or nothing → Rig, pose a non-idle action → Micro actions. In Stage mode the sidebar and inspector hide entirely — stage chrome (cast tray, autopilot, console drawer) takes over. You can always ▾-open another section by hand; the next selection change re-tidies.

Rigging a character

New character… (top bar) opens the picker:

OptionWhat you get
Blank biped / quadruped / blob rigPlaceholder skeletons with every role pre-registered — all presets work immediately; draw better art over the placeholders.
Blobby / Stick / RobotHand-authored template characters.
Describe it — AI generate…The AI dock with a rig-aware prompt — ask for "a dog" or "an alien" and it emits the right archetype.
Open .svg project…Any SVGMotion .svg (or generic SVG) becomes the working character.

Assigning roles

The Rig sidebar lists the archetype's roles as a checklist. Pick a role row → click a layer on the canvas → assigned. Shape-level roles (eyes, mouth, ears) tag an element inside a layer. Presets a creature can't perform — Wag tail on a blob — are greyed out automatically.

Example — rig your own drawing Draw a creature with one <g> per movable piece (draw with ⧉ Into-layer off) → pick archetype biped → assign body, head, armL, armR, legL, legR → the checklist goes green and every biped preset unlocks.

Parts library

The Parts sidebar panel restyles a character by swapping rig slots — eyes, mouths, ears, snouts, heads, torsos, limbs, tails — plus attachable accessories (hats, glasses, scarves, headphones…). It only shows slots your current rig actually resolves, and swaps keep the rig keys, transforms and pivots, so every micro-action and macro keeps working after a restyle. The library has ~90 authored parts plus everything below.

GestureWhat it does
★ OriginalPinned first in every slot — restores the exact part the character shipped with (geometry, position, pose metadata). Swap Nova's arms, click ★ Original, Nova's arms are back. Works from the popover too.
★ Gallery chipParts harvested live from the /characters gallery — Nova's eyes, Biscuit's tail, Orbit's body — applied with the donor's own colors. Under All they appear after the authored set.
Slot chipsEyes / Ears / Mouth / Snout / Body / Head / Torso / Arms / Legs / Tail — whichever exist on this archetype — plus Accessories. Selecting a part on the canvas auto-filters the panel to its slot.
Style chipsAll / Soft·round / Boxy·mech / Critter·pet / ★ Gallery — personality-grouped so nothing clashes.
Part gridClick a thumb to swap. Pairs (eyes, ears, arms, legs) apply to both sides; left limbs auto-mirror. One ⌘Z undoes a swap.
Right-click a partOn the canvas, right-click any rigged part → the same picker opens as a popover for that slot.
AccessoriesAttach to the head (hats, glasses, antennae, halo) — click to add, click again to remove. They're ordinary shapes: pose them, micro them, or delete them.
✦ describe a part…With AI configured, type e.g. "cyberpunk goggles" → Make generates a style-locked fragment into the active slot.

Parts arrive style-locked: snippets paint with palette tokens ($ink, $body, $skin, $soft, $accent) sampled from your character's colors — a swapped eye inherits the face ink, a swapped arm inherits the body tone.

Example — restyle Blobby Eyes chip → Star eyes (blink still works — keys are preserved) → Accessories → Crown → done. ⌘Z steps each change back.

Micro-actions

The Micro actions panel offers presets — Nod, Tilt head, Wave, Blink, Smile, Bark/speak, Wag tail, Prick ears, Sit, Bounce, Hop, Shiver. Click + to add one; it lands in the Actions list like any action and plays so you can refine it on the canvas/timeline. Right-click a preset to add it with tuned params ({"degrees":20,"duration":500,"side":"l"}).

+ Custom micro… scopes a sparse action to a role (or the whole body); + From selected layer scopes one to the selection. Then pose and keyframe exactly like the main editor — unposed layers simply hold.

Micro-verb API

Every preset is a call into js/char/microapi.js — functions that build a transient sparse action from clamped args. The command bar and the AI call the same verbs directly:

GroupVerbs
PrimitivessetPartRotation(partId, degrees, duration?, hold?), setPartPosition, setPartScale, setPartOpacity, resetPart, setShape, physMove (bake-then-play physics on one part)
CompoundstiltHead, nod, shakeHead, raiseArm, wave, lookAt, squash, stretch, hop, bounceBody, wagTail, blink, mouthOpen, perkEars, lieDown, sit, relax, emote, breathe

partId is a rig role; numbers clamp to safe ranges; an unresolvable verb is a no-op, never a crash. hold:true keeps the pose until the next call — that's how sleep-style behaviors are built.

Macros

A macro is a named chain of micros — "greet" = nod → wave → bow. In the Macro composer: tap action chips in play order, name it, pick an entry trigger (click/hover/press), hit ⌁ Wire. The composer writes ordinary idle → steps → idle transitions — fully visible and editable in Connect.

Parameterized macros: steps may be verb calls — {call:'walkTo', params:{entity:'$target'}} — where $target, $self, $user, $x, $y bind at trigger time. So eat can be walkTo($target) → consume($target), triggered by the AI as triggerMacro('eat', {targetEntityId:'prop_apple'}). Verb-step macros run through the entity's queue (each step waits for the previous) and only play in Stage mode.

Connect

The shared state-machine graph, unchanged: action nodes, transition edges, triggers (click/hover/press/layer-targeted), guards, inputs. Macro chains appear as readable rows of nodes. No world entities here — it's deliberately the same manual canvas as the editor's Connect mode.

Stage floor

Switch to Stage to stage many entities on one floor — the sidebar and inspector hide so the stage gets the whole canvas:

Entity methods — what AI and you can call

walkTo(x,y | entity)   goto(entity)    face(entity)    tellAt(entity, action)
pickUp(prop)           drop()          consume(prop)   interact(entity, act, targetAct)
nudge(prop)            say(text)       seq(steps[])     launch/throw(x|entity|vx,vy)
micro(verb, args)      triggerMacro(name, bindings)    macro name  → runs the macro

pickUp makes the prop ride with its carrier; consume plays the prop's own eaten/vanish action (or shrinks it away). Movement is a real walk, not a glide: the entity advances stride-by-stride in sync with its own gait cycle — eased sub-move + a footfall pause per stride, the loco-tagged action re-firing each step — and it faces travel direction (left-authored art sets rig.facing:-1 so it never moonwalks). Positions track live as it walks, so near/moves links fire mid-journey. Positions clamp to the floor. seq queues ordered steps — walkTo blocks until arrival. launch/throw arcs a prop (or any entity) on a real ballistic path — the same solver as the editor's projectile recipe, run in stage space.

Everything funnels through one dispatch:

invoke(entity, { type: 'micro'|'macro'|'verb'|'say'|'seq', action, params })

Chips, the command bar, links and the director all use it — malformed actions drop with a visible ✗, never a crash.

In the console drawer's Links tab pick source → when → target → action. Conditions:

WhenFires
finishes (after)When the source's chosen action completes
is near (near)When the entities come within range
touches (touch)When their boxes overlap
movesWhile the source keeps changing position (label-drag, launch, another verb) — the target re-fires its action on a ~1s cooldown. Pair it with walkTo on the destination and you get a follow rule: drag the ball, the dog chases it. Skipped while the follower carries the source.

"When Nova finishes wave → Orbit plays greet." "When the dog is near the bone → bone plays pickup." Links fire once when the condition begins and re-arm when it ends — that's how the gallery's Buddy duo world runs its call-and-response greeting with zero code.

AI director & the autopilot

With a provider configured or the local engine enabled, ▶ Autopilot (stage bar) hands the director the full state — every entity's kind, position, tags, callable methods and micro-verb signatures, plus links and recent events — and it returns a structured beat:

{ "thought": "The user placed an apple next to Blobby…",
  "actions": [
    { "target": "char_blobby", "type": "micro",
      "action": "setPartRotation", "params": { "partId": "head", "degrees": 12, "duration": 200 } },
    { "target": "char_blobby", "type": "macro",
      "action": "eat", "params": { "targetEntityId": "prop_apple" } } ] }

thought shows as a 💭 bubble over the entity it's about to move (the full log lives in the console's Director tab); actions run through invoke(). Beats are periodic and reactive — drops, clicks, arrivals and landings wake the director in under a second; user-caused events react fastest (~0.5s). While the model thinks, a light ambient micro keeps the cast alive so nobody freezes. Quiet world → ambient micros (blink, breathe, wagTail) or an empty list.

Offline: a deterministic built-in director and a parser handle "X does A", "X go to Y", "X pick up / eat Y", "X throw Y", "X say …" — so the pipeline still runs end-to-end. The gallery's scripted beats are canned presets in the same {thought, actions[]} shape — the AI director buttons and the "tell the cast" box ask the real model (local browser AI first, provider fallback). In the editor the same local-first path powers the ⚡ command box — it also edits: "make it red", "add a spin action", "move head up".

URLOpens
/characterstudio?scene=<id>A character scene from /chars/items/<id>.svg in the Rig canvas
/characterstudio?world=<id>A world bundle from /chars/worlds/<id>.json, staged on the floor
&guide=1Starts the item's guided build — same machinery as the editor's tours
/characterstudio#tour · ?tour=1Runs the studio quick tour (basics)
/characterstudio#tour-full · ?tour=fullRuns the full studio tour — every feature end-to-end
/characters/gallery?item=<id>The gallery with that item's detail open

World bundles are JSON: {name, floor, entities:[{name, file, x, y, w, tags}], links:[…], presets:{note, beats:[{thought, label, actions}]}} — entities reference ordinary gallery/studio .svg files by path. Deep links merge into the current stage rather than wiping it.

Files & persistence

ThingWhere it lives
Character sceneA normal SVGMotion .svg — export/save/share exactly like the editor; gallery chars are the same format.
WorldAutosaves to the mf.charworld.v1 pref; bundles for sharing live as JSON under chars/worlds/.
Autosavechar-autosave — separate from the main editor's autosave.

Physics-lite: proximity uses bounding boxes polled per frame — enough for near/touch/pick-up. Real motion physics reuse the editor's solvers (launch, physMove).

Worked example — Biscuit fetches a bone

Recipe /characterstudio?scene=biscuit loads the pup → Stage → ⇪ Send to stage → + Import chars/items/prop-bone.svg → tag it toy edible → drag it across the floor → add a link: when Biscuit is near Bone → Bone plays pickup → command bar: biscuit go to bone → he walks over, the link fires, the bone vanishes. Or run the whole thing ready-made: /characterstudio?world=park-play.

Troubleshooting

SymptomFix
Preset greyed outMissing rig role — the Rig checklist shows which; assign it or switch archetype chips.
Verb does nothingUnresolvable role → no-op by design. Check partId spelling and the role list.
Macro won't play in Rig modeVerb-step macros (walkTo, consume…) need Stage mode — they queue on an entity.
Director idleNo AI? The built-in offline director still answers the command bar; autopilot needs AI — the local engine (☰ → Settings → Advanced) or a provider (☰ → Settings → AI provider).
?world= didn't loadStage must be entered (Stage mode) before import; a bad bundle name 404s → check chars/manifest.json ids.
Entity won't movewalkTo needs the entity mounted on the floor — user marker and props without scenes can be targets but not walkers.