# Alive Kit: rig

One face truth for every look. This area packages the working voice-site viseme bus,
phoneme clock, neural face and orb expression clock, plus the app’s character life,
expression maps and Beam drawing. `manifest.json` records every original source.
Nothing in the rig mints a session, plays sound, captures a microphone, or chooses a
voice. The platform area owns those operations and their permissions and ceilings.

Load `/alive-kit/rig/index.js` as an ES module. It registers `AliveKit._areas.rig`.

```js
import {createRig} from '/alive-kit/rig/index.js';

const rig = createRig();
const detach = rig.attach(lookInstance);
const unbind = rig.bind(platformSession); // the platform area’s existing session

// The platform session already drives rig.adapter. Direct calls are also available:
await rig.speak(remoteAudioStream);       // analysis-only; leaves playback to platform
rig.state('listening');
rig.feel('shy', 0.7);                     // eases in, then fades back to neutral

// A supplied phoneme chunk must be anchored to its actual playback:
await rig.speak({phonemes: 'hɛloʊ', durationMs: 1100, ctx, startAt: ctx.currentTime});

unbind(); detach(); rig.destroy();        // never stops somebody else’s audio track
```

The module’s `rig` default export supplies shared `attach`, `bind`, `adapter`, `bus`,
`speak`, `state`, `feel`, `gaze`, `snapshot`, neural controls and `destroy` for one host.
`createRig()` creates an isolated rig for a host
whose owner manages its lifecycle. Use one rig per host, across look swaps.

## Look contract

`attach()` accepts a mount (`{adapter, type}`, including the looks area’s `adapter()` getter) or an adapter itself, returns a detach
function, and immediately replays current face truth. A look can implement either:

* `applyRigFrame(frame)` to consume the complete renderer-neutral face; or
* the existing Shape-A `setAgentState`, `setAudioLevel`, `setMood` and optional
  `attachVisemeBus`, `setViseme(id, weight)`, `setBlink`, `setGaze(x,y)`, `setBlush`,
  `setExpression(entry, strength)`, `setRigFrame(frame)`, `setTranscript` seams.

Legacy scalar mouths receive the app runtime’s existing five-shape projection.
Set `lookInstance.visemeTier` to an existing bus tier if its scalar sink expects
that tier’s native ids. Traced mounts retain their own `blink()` and expression
hooks; the rig calls those on the mount when the Shape-A adapter lacks them.

For full phoneme shapes, consume `applyRigFrame` or `attachVisemeBus`. Shape-A-only
mounts retain their existing energy-driven mouths and their own idle painting.
`bus.read('v12'|'v3'|'v4'|'v5')` projects the same OVR-15 frame into the existing
ASCII/hybrid, SVG, sketch and rigged mouth vocabularies. An unavailable renderer
cannot stop the other looks. Detaching never destroys a renderer.

Frames carry `state`, `mouthOpen`, `viseme`, `visemeWeight`, VRM `visemes`, `blink`,
`blinkL/R`, `gazeX/Y`, `browL/R`, `breath`, `pose` (existing ASCII expression channels),
`orb` (existing orb dynamics), `mood`, eased `strength` and `blush`. `arkit` is present
only while opted-in neural inference is supplying fresh coefficients.

States: `idle`, `listening`, `thinking`, `speaking`, `interrupted`. Captions alone
never fabricate speech. Session levels are bounded and expire after 350 ms. Leaving
speech releases its analysis tap. Interruption clears speech and feeling in one frame.

## Feeling law

Blush and the copied Beam’s feeling glow start at **exactly zero**. Calling `feel`
is the conversation’s explicit affect input; volume and speaking state never add
blush. Supported feelings are exported as `FEELINGS`. Strength is finite and clamped
to 0–1. Feelings ease in, hold six seconds and fade, or fade early on `feel('neutral')`.
Reduced motion keeps blinks and real mouths while removing idle gaze and breathing.
The app original stays untouched; the kit copy’s `frameOf` accepts `blushIntensity`
and `feelingIntensity` with zero defaults.

The copied Happy Face’s stage tools remain available through its existing
`contentWindow` option. Pass the content-window factory owned by the scenes area
when mounting it or opening its room. The rig imports no separate scenes engine;
its face and timing modules work independently of app-specific paths.

## Neural face

`await rig.enableNeural({search: '?face=neural-lite'})` explicitly opts in to the
existing manifest-checked wav2arkit driver. It resolves weights/runtime from the
already served same-origin `/alive-site/` tree; weights are not copied or installed.
It returns true only when ready. `rig.neuralStatus()` exposes `off`, `ready`, or
`weights-not-installed` and missing filenames. Missing weights keep band-energy
mouths working. Full, lite, auto, direct ARKit/VRM mapping and utterance APIs remain
available in `alive-neural-face.js`. Interrupted inference cannot restart a stopped rig.

## Demo and checks

Open `/alive-kit/rig/demo.html`. The existing character greets in a caption, blinks,
glances, and wears its original letter, TV robot and Pip forms. One hover/tap menu
contains the language, sound, camera preview, hide and end controls. Camera starts
only from its explicit control; frames stay local. The demo discovers the sibling
platform manifest, or binds an existing `NetShowPresent.session`; it creates no
second voice transport. A standalone rig demo has no signed mint and stays silent.

Run `node --test public/alive-kit/rig/tests/rig.test.mjs`. The Chromium checks are
`tests/reference-browser.mjs` (GET-only live references) and `tests/demo-browser.mjs`
(a local server, real WebAudio stream fixture, recordings at 375 and 1440). On NetShow
hosts run them through `~/bin/test-slot.sh`. Browser scripts use this lane’s installed
Playwright and Chromium paths; change those paths when running on another host.

## Whole body (line 17222)

`body.js` is the one body clock. Every rig frame carries `frame.body`: joint angles in degrees for `hips` (plus
`shift`, `bob`), `spine`, `chest`, `upperChest`, `neck`, `head`, `shoulderL/R` (`raise`, `fwd`), `armL/R` (`lift`, `fwd`),
`elbowL/R` (`bend`) and `handL/R` (`flex`, `tilt`, `open`), in the character's own frame (pitch forward, yaw and roll to
her left). It is the voice VRM stage's SpeechBody bus (`alive-vrm-motion.mjs`) with R2's core liberation: syllabic peaks
fire beat gestures that travel into the chest, upper chest and hips; caption words that are shouted, exclaimed or leaned
on (`adapter.setTranscript`) make the next beat emphatic, and a question opens both hands; each phrase swells the torso
with the hips counter-rotating; gaps move the weight to the other hip; loud peaks lift both collarbones. Breathing never
stops, listening leans in and tilts the head, thinking brings a hand up, idle fidgets now and then. A seeded generator
varies every gap and amplitude, so it never loops. Reduced motion keeps 40 % of the range and full breathing.

* Shape-A looks receive `setBody(body)`; `applyRigFrame` looks read `frame.body`.
* VRM: `import {createVrmBody} from './body-vrm.js'`, then `createVrmBody(vrm).apply(frame.body)` before `vrm.update(dt)`.
* `createRig({body: false})` turns it off for a host that drives its own body; `rig.bodyLog()` lists the fired events.
* The app's 3D rung (`public/js/alive/host-morph-3d.js`) and `<alive-avatar>` (`looks/character-engine.js`) consume it.

Proof page: `/alive-kit/rig/body-demo.html` (a VRM character, the app's 3D host and the 2D Fen on one rig). Checks:
`node --test tests/body.test.mjs` and `node tests/body-browser.mjs` (Chromium, `?voice=fixture` plays the local
recorded voice samples through WebAudio and never mints; without it the page takes the house mint from
`/alive-kit/voice-src` like `demo.js`, so the first tap speaks on the NetShow proxy voice and the body follows it).


## Base character editor (line 17821)

`bases.html` extends the existing `bases.js` renderer and `base-appearance.js` state. The viewer pins beside the controls (or above them on a phone), supports orbit, wheel/pinch zoom, one/both bodies, front/back, automatic rotation and reset. The selected body's shape and hair controls stay independent. Additive bind-space morphs deform skin and its garment shells together; the original rig, face expressions, licensed animation clips and one platform voice session remain the source.

Photo/mascot matching is a consent-first browser fallback using the canonical forge's local pixel/proportion approach. Pixels and the object URL stay in tab memory. No network upload, model, persistent storage or likeness ledger record is created. Forgetting revokes the URL, clears the pixels and restores the pre-match appearance. The server-side PhotoAvatarPipeline remains the path for a separately consented persistent Blender export. Local estimates are editable and do not claim exact facial likeness or inferred muscle definition.

The archived editor is `bases-v1-20261002/bases.html`, linked as “Saved version, 2 Oct”; `SNAPSHOT.json` records the source hashes and relocated URLs. Local proof: `tests/bases-browser.mjs`; pure appearance bounds: `tests/bases-adjustments.test.mjs`.
