Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- **Realistic human figure**: the playground, landing hero, and `<posecode-player>` embeds now render a fully rigged, textured human character (hands with articulated fingers, sneakers, face) instead of the procedural capsule mannequin. All solving (FK, ground-lock, pins, reach-IK) still runs on the driver skeleton, rebuilt to the character's exact proportions and retargeted bone-for-bone every frame; the procedural figure remains as an automatic fallback (and via `?figure=classic` / `character="off"`).
- **Self-collision resolution**: a capsule-based de-penetration pass keeps forearms/hands out of the torso, head, and legs (and shins out of each other), clamped to healthy ROM, so limbs no longer pass through the body mid-movement.
- `viewer.characterActive`, `createViewer({ characterUrl })`, and the embed `character` attribute.
- `scripts/capture-gifs.mjs` (`npm run gifs`): reproducible headless regeneration of the README movement GIFs from the live renderer.

### Fixed

- Deadlift arms now hang toward the bar during the hinge (were authored as shoulder extension, flying up behind the back).
- Crunch keeps the feet planted with bent knees (shins previously folded through the floor and jacked the body up).
- Touch-toes folds like a human (hinge depth and knee bend were over-authored, collapsing the figure).

## [0.1.0] - 2026-07-08

### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ Two safety layers ship with the language:
The protocol and both libraries are **MIT-licensed**: the open core. See [`spec/SPEC.md`](spec/SPEC.md) for the full language and [`spec/llm-authoring.md`](spec/llm-authoring.md) for the authoring prompt.
For where Posecode spreads fastest and the per-domain go-to-market plan, see [`docs/market-research.md`](docs/market-research.md); for the engine roadmap, [`ROADMAP.md`](ROADMAP.md).

The 3D figure is the [Adobe Mixamo](https://www.mixamo.com) anatomical mannequin character, exported from Mixamo and used under the Mixamo license (royalty-free in projects). The renderer also ships a zero-asset procedural figure, used automatically wherever the character can't load — and it accepts any Mixamo-rigged GLB via `characterUrl`.

---

## Scope (v0.1)
Expand Down
Binary file modified docs/media/deadlift.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/jumping-jacks.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/lateral-raise.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/squat.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
"dev": "npm run dev -w playground",
"build": "npm run build -w playground",
"eval": "tsx packages/posecode-eval/src/cli.ts",
"typecheck": "for p in packages/*/tsconfig.json playground/tsconfig.json editors/*/tsconfig.json; do tsc --noEmit -p \"$p\" || exit 1; done"
"typecheck": "for p in packages/*/tsconfig.json playground/tsconfig.json editors/*/tsconfig.json; do tsc --noEmit -p \"$p\" || exit 1; done",
"gifs": "node scripts/capture-gifs.mjs"
},
"devDependencies": {
"@vitest/coverage-v8": "^2.1.8",
"gifenc": "^1.0.3",
"playwright-core": "^1.61.1",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
Expand Down
1 change: 1 addition & 0 deletions packages/posecode-embed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ definePosecodePlayer(); // idempotent
| `controls` | `true` | Show the play/pause bar. |
| `autorotate` | `true` | Slowly orbit the camera when idle. |
| `speed` | `1` | Playback multiplier (`0.1`–`4`). |
| `character` | *(hosted default)* | Realistic figure: a GLB URL (Mixamo rig), or `off` for the procedural mannequin. Load failures fall back to the mannequin. |
| `playground` | `https://posecode.org/play` | Base URL for the "Edit ↗" link. |

Boolean attributes accept `false` / `0` / `no` / `off` to turn them off, so
Expand Down
2 changes: 2 additions & 0 deletions packages/posecode-embed/src/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class PosecodePlayerElement extends HTMLElement {
controls: this.getAttribute("controls"),
autorotate: this.getAttribute("autorotate"),
speed: this.getAttribute("speed"),
character: this.getAttribute("character"),
});
}

Expand Down Expand Up @@ -178,6 +179,7 @@ export class PosecodePlayerElement extends HTMLElement {
const { createViewer } = await import("posecode-render");
const viewer = createViewer(this.#canvas, {
autoRotate: opts.autoRotate && !reduceMotion,
...(opts.characterUrl ? { characterUrl: opts.characterUrl } : {}),
});
this.#viewer = viewer;
viewer.onPhase(({ phaseName }) => {
Expand Down
22 changes: 22 additions & 0 deletions packages/posecode-embed/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,25 @@ export interface PlayerOptions {
autoRotate: boolean;
/** Playback speed multiplier (0.1–4). */
speed: number;
/**
* Realistic skinned figure: a GLB URL, the default hosted character when
* absent, or `""` (attribute `character="off"`) for the procedural figure.
* Load failures fall back to the procedural figure, so an offline page
* degrades instead of blanking.
*/
characterUrl: string;
}

/** The character the hosted playground uses, served from the same origin. */
export const DEFAULT_CHARACTER_URL = "https://posecode.org/models/character.glb";

export const DEFAULT_OPTIONS: PlayerOptions = {
autoplay: true,
loop: true,
controls: true,
autoRotate: true,
speed: 1,
characterUrl: DEFAULT_CHARACTER_URL,
};

const SPEED_MIN = 0.1;
Expand All @@ -38,6 +49,7 @@ export interface RawAttributes {
controls?: string | null;
autorotate?: string | null;
speed?: string | null;
character?: string | null;
}

const FALSEY = new Set(["false", "0", "no", "off"]);
Expand All @@ -54,6 +66,15 @@ function clamp(n: number, lo: number, hi: number): number {

export function parseOptions(attrs: RawAttributes): PlayerOptions {
const speedRaw = attrs.speed != null ? Number(attrs.speed) : NaN;
// `character` accepts a GLB URL, a falsey word to opt out, or absent for
// the hosted default.
const characterRaw = attrs.character?.trim();
const characterUrl =
characterRaw === undefined || characterRaw === null
? DEFAULT_OPTIONS.characterUrl
: FALSEY.has(characterRaw.toLowerCase())
? ""
: characterRaw;
return {
autoplay: boolAttr(attrs.autoplay, DEFAULT_OPTIONS.autoplay),
loop: boolAttr(attrs.loop, DEFAULT_OPTIONS.loop),
Expand All @@ -62,5 +83,6 @@ export function parseOptions(attrs: RawAttributes): PlayerOptions {
speed: Number.isFinite(speedRaw)
? clamp(speedRaw, SPEED_MIN, SPEED_MAX)
: DEFAULT_OPTIONS.speed,
characterUrl,
};
}
4 changes: 4 additions & 0 deletions packages/posecode-eval/src/probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
applyGroundLock,
buildMannequin,
buildTimeline,
depenetrate,
groundFigure,
} from "posecode-render";

Expand Down Expand Up @@ -64,6 +65,7 @@ export function probeMovement(source: string): ProbeResult {
m.root.rotation.set(rx * DEG, ry * DEG, rz * DEG);
tl.sample(0, m.bones);
m.root.updateMatrixWorld(true);
depenetrate(m);
groundFigure(m);
const baseRootPos = m.root.position.clone();
const baseRootQuat = m.root.quaternion.clone();
Expand Down Expand Up @@ -92,6 +94,8 @@ export function probeMovement(source: string): ProbeResult {
m.root.position.x += info.rootOffset.x;
m.root.position.z += info.rootOffset.z;
m.root.updateMatrixWorld(true);
// Self-collision resolution, then contact solving (same order as the viewer).
depenetrate(m);
// Mirror the viewer's per-frame anchors: captured targets carried along
// by this phase's yaw/travel so planting composes with choreography.
const anchors = new Map<string, THREE.Vector3>();
Expand Down
13 changes: 11 additions & 2 deletions packages/posecode-render/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# posecode-render

Renders a [`posecode-parser`](https://www.npmjs.com/package/posecode-parser) IR
as an animated low-poly mannequin with [Three.js](https://threejs.org):
as an animated 3D human figure with [Three.js](https://threejs.org):
forward kinematics plus ground-lock CCD IK, live in the browser at 60fps.
Pass `characterUrl` to show a realistic skinned character (any Mixamo-rigged
GLB); without it — or while it loads, or if it fails — a procedural athletic
figure renders instead, so the scene is never blank. Either way, a capsule
self-collision pass keeps limbs from passing through the body.

Part of [Posecode](https://posecode.org): a kinematic-motion protocol LLMs can
write, rendered as text-to-motion 3D animation.
Expand All @@ -20,7 +24,12 @@ import { parse } from "posecode-parser";
import { createViewer } from "posecode-render";

const canvas = document.querySelector("canvas")!;
const viewer = createViewer(canvas, { autoRotate: false });
const viewer = createViewer(canvas, {
autoRotate: false,
// Optional: realistic skinned character (Mixamo bone naming). Omit for the
// zero-asset procedural figure.
characterUrl: "https://posecode.org/models/character.glb",
});

const { ir } = parse(myPosecodeSource);
if (ir) {
Expand Down
2 changes: 1 addition & 1 deletion packages/posecode-render/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "posecode-render",
"version": "0.1.0",
"description": "Render a Posecode IR as an animated low-poly mannequin with Three.js.",
"description": "Render a Posecode IR as an animated 3D human figure (skinned character or procedural mannequin) with Three.js.",
"license": "MIT",
"type": "module",
"main": "./src/index.ts",
Expand Down
Loading
Loading