# MUTATIO — Motion Lab and browser animation rig

## Files

- `source.glb`: unchanged NFT model downloaded from OpenSea's model viewer.
- `mutatio-rigged.glb`: 3.26 MB GLB; 32 joints, 7 skinned meshes, 7,688 vertices, 12,620 triangles, four original embedded textures, Walk/Fly clips.
- `controller.js` + `pose.js` + `lab-state.js`: programmatic Three.js controls, seven pose states and validated pose serialization.
- `index.html` + `lab.css` + `viewer.js`: Motion Lab with joint explorer, specimen viewport, inspector and animation transport.
- `rig-manifest.json`: source/output checksums, coordinate transform, joint hierarchy, pivots and component assignments.
- `../../artifacts/mutatio/validation.json`: executed Node validation, separate from browser reports in that directory.

## Open the local viewer

App source and GLB assets live in `apps/motion-lab`. Shared build/test tooling lives in `tools/mutatio`; validation reports live in `artifacts/mutatio`.

From this repository's root:

```sh
npm run motion-lab:dev
```

Open **http://127.0.0.1:8790**. It uses the repository's pinned Three.js 0.180.0. No CDN, service login, build step or Cloudflare deployment is needed. It starts in Rest; choose Idle, Walk, Fly, Groom, Wave or Look around to animate. Drag to orbit, scroll to zoom. Each joint can be posed in degrees in the viewer. The JavaScript API uses radians.

The GLB is self-contained. To use the viewer outside this repository, supply Three.js and its addons at the import-map paths in `index.html`, or adapt those paths to your existing installation. Opening the HTML with `file://` is not supported; serve it over HTTP.

## Appearance

Both the lab and landing self-host Departure Mono 1.500 from the [official release](https://github.com/rektdeckard/departure-mono/releases/tag/v1.500). The unmodified WOFF2 and SIL OFL license are included; no external font request is required.

The header actions share the same height, including the GLB download. **Dark mode** toggles the interface theme and remembers your choice on this browser. Before choosing, the lab follows your system theme. The 3D specimen retains its original lighting in both themes.

## Copy instructions for an agent

Use **Copy Agent Instructions** beside the GLB download to copy the canonical model link and a self-contained integration guide. It covers baked Walk/Fly clips, optional procedural modules, joint controls and validation. If the browser blocks clipboard access, a dialog exposes the complete text for manual copying. Copying works independently of the 3D viewer.

## Use the Motion Lab

- **Joint explorer:** all 32 joints, including the root. Search by name, body group or side. Click a marker or model surface to select the associated joint.
- **Joint inspector:** three sliders plus exact numeric degree inputs, per-axis limits, parent, bound-vertex count and measured world position. Typing an angle applies it immediately. Editing pauses playback and holds the selected joint; other manual overrides are retained.
- **Mirroring:** mirror a wing or leg joint to its opposite side. X keeps its sign; Y/Z reverse. Mirroring applies to manual edits and zero/release actions; the range test targets only the selected joint.
- **Zero vs. release:** Zero holds a joint at its rest rotation. Release lets that joint follow the active preset again. Reset all returns the whole rig to rest. Selecting a different joint preserves the current pose.
- **Range test:** sweeps the selected axis through 75% of its limit in each direction over four seconds, reports sampled minimum/maximum angles and restores its starting override. Playback remains paused. It does not test collisions or anatomical validity.
- **Motion library:** rest plus six animated studies: idle, tripod-style walk, fly, paired foreleg groom, wave and look around. Every preset loops. Selecting a preset clears manual overrides. The new presets are procedural; the GLB still embeds its original two derived Walk/Fly clips.
- **Transport:** play/pause, rewind, exact-time scrub, playback speed from 0.1× to 2× and wing amplitude. Lab playback speed advances time continuously; changing it does not jump the pose phase. Scrubbing pauses playback.
- **Inspection views:** 3D, front, side, top and fit; joint markers, skeleton, wireframe, segment tint and ground grid. Segment tint includes descendants; emissive texture details remain visible.
- **Pose memory:** Store pose / Restore pose keeps a snapshot for this page session. Reloading the page clears this temporary memory.
- **Pose files:** Save pose downloads a JSON file. Load pose restores one exported by this lab, including the body offset from flight. Files must contain all 32 joints and finite, bounded values; malformed files are rejected before changing the rig. No file is sent to a remote service.

```js
const snapshot = rig.capturePose();
rig.restorePose(snapshot); // restores all joints and body offset; pauses
```

The initial scene is static. Animation starts only on an explicit action. Playback pauses off screen or when the page is hidden, and a change to reduced-motion preference pauses playback.

Fresh lab checks are recorded in `../../artifacts/mutatio/lab-validation.json`. Browser coverage includes numeric controls for all 32 joints, all six animated presets and scrubbing, mirrored angles, selected-axis sweep restoration, pose memory, and an actual downloaded pose file loaded back into the page. Node tests cover preset loop continuity, finite deformation, limits, round-trip transforms and atomic invalid-file rejection.

## Programmatic animation

Copy `mutatio-rigged.glb`, `controller.js`, `pose.js` and `lab-state.js` into your web project. Resolve the `three` import to your installed Three.js module, then:

```js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { MutatioController } from './controller.js';

const gltf = await new GLTFLoader().loadAsync('/models/mutatio-rigged.glb');
scene.add(gltf.scene);

const rig = new MutatioController(gltf.scene);
rig.setMode('walk'); // rest, idle, walk, fly, groom, wave, look
rig.speed = 1;
rig.wingAmplitude = 0.65; // radians; affects fly mode

// In your existing rendering loop, with deltaSeconds measured in seconds:
rig.update(deltaSeconds);
renderer.render(scene, camera);

// Fly, pause, or seek deterministically:
rig.setMode('fly');
rig.paused = true;
rig.poseAt(0.25);

// Independent control, relative to rest orientation:
rig.setMode('rest');
rig.setJoint('Wing_front_L', { z: 0.4 });
rig.setJoint('Leg_front_R_tibia', { z: -0.25 });
rig.setJoint('Head', { y: 0.2 });
rig.clearJoint('Head');

// Move/turn the complete character independently of articulation:
gltf.scene.position.set(1, 0, 0);
gltf.scene.rotation.y = Math.PI / 2;
```

`setJoint` applies a persistent override until `clearJoint` or `setMode`. Each call replaces all three rest-relative Euler angles for that joint; omitted axes become zero. It clamps angles to conservative limits stored in the GLB. `rig.bones[name]` exposes the Three.js Bone for custom animation; do not call `rig.update()` if you want to drive those bones entirely yourself.

`poseAt(t)` samples absolute seconds with the current speed multiplier. Changing `speed` can change the phase immediately. There is no automatic transition blending. Hide/pause your animation loop when the document is not visible, as the supplied viewer does.

## Use the baked clips instead

The GLB also works without the controller in glTF-capable engines. In Three.js:

```js
import { AnimationMixer, AnimationClip } from 'three';
const mixer = new AnimationMixer(gltf.scene);
const walk = mixer.clipAction(AnimationClip.findByName(gltf.animations, 'Walk'));
walk.play();
// Each frame:
mixer.update(deltaSeconds);
```

Do not update the procedural controller and AnimationMixer on the same instance at the same time. Both write the same joints. `Walk` is 0.8 seconds; `Fly` is 2 seconds; both loop. Clip playback was checked with the installed Three.js AnimationMixer.

## Joint names and coordinates

- `RigRoot` → `Body` → `Head`, `Abdomen`, wings and six leg chains.
- `Wing_{front|rear}_{L|R}`: four independent wings.
- `Leg_{front|middle|rear}_{L|R}_{hip|femur|tibia|tarsus}`: 24 leg joints.
- Y up, Z forward, L on +X. Bone axes start aligned with these axes; local XYZ Euler angles are relative to rest.
- Positions are original world coordinates, uniformly scaled by 50, with the lowest rest vertex placed at Y=0 and Z recentered by 0.010 source units before scaling. These are display units, not a claim about real dimensions.

All four leg segments use 100% rigid weights to their corresponding bone, retaining the original hard exoskeleton. Rotations are hierarchical: moving a hip moves the entire leg. The original object-transform clips and unused source armature are replaced in the derived GLB; they remain in `source.glb`.

## Scope and verification

- Stylized motion: a tripod-style walk in place and a slowed, visible wingbeat with hovering body motion.
- No foot-lock IK, ground collision, automatic locomotion, physical flight, retopology, or biological claim. Foot sliding and intersections are possible with large/custom poses.
- Original geometry, materials, UVs and image bytes are preserved. No AI-generated replacement asset.
- Node checks cover every vertex at rest, all 32 joint controls, normalized weights, finite deformed vertices over 242 animation frames, pause/resume, limits, texture hashes and both baked clips.
- The actual browser viewer was checked separately for texture rendering, animation and controls. See the dated JSON reports for executed scope.
- This artifact is independent of FlyLab's neural/world simulation.

Rebuild and recheck from the project root:

```sh
node tools/mutatio/inspect.mjs
npm run motion-lab:rig
npm run motion-lab:validate
npm run motion-lab:test
```

The builder rejects a changed source checksum because its component mapping is specific to this asset.

## Provenance

- NFT: [MUTATIO — SPORES](https://opensea.io/item/base/0xfdb192fb0213d48ecdf580c1821008d8c46bdbd7/1), Base ERC-1155 token 1.
- Original media: https://raw2.seadn.io/base/0xfdb192fb0213d48ecdf580c1821008d8c46bdbd7/af0c7a519f6d739cf04345c9d44fba/76af0c7a519f6d739cf04345c9d44fba.glb
- Retrieved 2026-09-14 through the GLB URL exposed by OpenSea's model-viewer iframe.
- Source SHA-256: `6aabb454efc1a43cbc368feeac86b15b7032140f6d0f2bf24a1c322355ae1f46`.
- Source generator: Khronos glTF Blender I/O v3.4.50.
- Original artwork belongs to its creators. This local derivative does not establish a redistribution license.

Implementation references: [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) and the installed Three.js 0.180.0 GLTFLoader, SkinnedMesh and AnimationMixer sources.
