diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 44e211e..3f60b0c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,6 +25,9 @@ jobs:
- name: Typecheck
run: npm run typecheck
+ - name: Build publishable packages
+ run: npm run build:packages
+
- name: Build playground
run: npm run build
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b515f74..fccf4b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,8 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [0.2.0] - 2026-07-15
+
### Added
+- **Canonical timing modes**: `flow`, `settle`, `drive`, `snap`, and `linear`
+ now describe continuous motion, deliberate rests, acceleration, and sharp
+ arrivals. The v0.1 easing spellings remain accepted as deprecated aliases.
+- **Third-party validation CLI**: `npx posecode-parser@0.2.0 validate `
+ recursively checks `.posecode` libraries, with `--strict` and `--json` modes
+ for CI.
+- **Embed compatibility metadata**: the CDN bundle exports `version`,
+ `languageVersion`, and `validatePosecode()`. Player ready/error events now
+ include structured version and diagnostic details, while host elements expose
+ a machine-readable loading/ready/error state.
+- **Release contract checks**: CI builds the publishable package chain so a
+ source-only language change cannot land without a valid CDN bundle.
- **Realistic human figure**: the playground, landing hero, and `` 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.
- **Solid props**: props now declare blocking faces (the wall's surface, the chair's backrest and seat edge, the box's near face) and a contact pass keeps the body out of them — translating the whole figure along the face normal, or bending the offending leg's hip clear (ROM-clamped). Limbs pinned/gripped/reached to a prop anchor stay exempt as declared support. A new `solid-props` eval invariant (independent geometry re-derivation) guards every prop movement against this bug class.
diff --git a/README.md b/README.md
index 14594d9..f0f3a59 100644
--- a/README.md
+++ b/README.md
@@ -171,7 +171,7 @@ A `.posecode` file describes movement as timed phases with targeted joint action
| 1. Write `.posecode` | 2. Render the movement |
| :--- | :--- |
-| **`posecode`** `exercise "Body-weight squat"` **`rig`** `humanoid` **`pose`** `start = standing`
rig humanoid
pose start = standing
- step "Lower" 1.8s ease-in-out:
+ step "Lower" 1.8s settle:
pelvis: hinge 95
knees: flex 25
shoulders: flex 90
ground-lock: feet
cue "Hips back, flat back: let the arms hang to the bar"
- step "Lift" 1.4s ease-out:
+ step "Lift" 1.4s drive:
pelvis: hinge 0
knees: flex 0
shoulders: flex 0
@@ -196,7 +203,7 @@
Hip-hinge example
Reaching, props, lying poses & hands
Reach a target: reach: <effector> <target> drives a hand or foot to a
world point via IK. Effectors: hand_left hand_right foot_left foot_right, or hands / feet for both sides at once. Targets: a body landmark bone (ankle_left, knee_right…), floor, or a prop anchor (bar, seat, wall). The solve is ROM-constrained: the arm/leg can never exceed the safe joint limits chasing a target, so an out-of-reach target just yields the closest healthy pose. Author the gross pose (e.g. a pelvis: hinge), then let reach finish the hand placement. Example, touch your toes:
-
``posecode step "Fold" 2.5s ease-in-out: pelvis: hinge 95 knees: flex 12 reach: hand_left ankle_left reach: hand_right ankle_right ground-lock: feet cue "Hinge and reach toward the ankles" ``
+
``posecode step "Fold" 2.5s settle: pelvis: hinge 95 knees: flex 12 reach: hand_left ankle_left reach: hand_right ankle_right ground-lock: feet cue "Hinge and reach toward the ankles" ``
Props: prop chair | wall | bar | box | dip-bars (top level). The chair
sits behind the figure (sit-to-stand, box squat), the wall behind that (wall sit), the bar overhead, the box in front (step-ups), and the dip bars either side at hip-press height (pin: hands bars + elbow flex = triceps dips).
Pins: pin: <effector> <anchor> moves the whole BODY so the effector sits
@@ -213,8 +220,8 @@
Authoring by domain
singular joint (knee_right: flex 95) and skip ground-lock so the standing leg stays planted.
Desk / posture: slow stretch documents with ground-lock: feet;
contrast a "collapsed" phase with a "tall" reset.
-
Sports / martial arts: short, snappy phases (0.3–0.6s) with ease-out
-
on the strike; chamber → extend → re-chamber → return.
+
Sports / martial arts: short phases (0.2–0.6s); use flow through a
+
gather/chamber, drive for takeoff, and snap on the strike or release.
Dance / choreography
Posecode shines for showing the movement in your head. Build a phrase as a sequence of phases, one per count or musical beat, and let later phases inherit unset joints:
Turnout:hips: rotate-out 25–30.
Port de bras (arm positions): first shoulders: flex 30, elbows: flex 35;
- Step forward-right0.9s · flow
- Step the right foot forward and out to the corner
+ Right foot forward0.9s · flow
+ Step forward onto the right foot with an easy contralateral arm swing
- Close the feet0.7s · flow
- Bring the left foot to meet it, standing tall on the corner
+ Left foot side0.85s · flow
+ Transfer left to the side, keeping the torso lifted
- Step back-left0.9s · flow
- Step the left foot back to the start on the diagonal
+ Right foot back0.9s · flow
+ Step back onto the right foot to trace the third side of the box
- Close home0.7s · settle
- Close the feet together, back home and ready to repeat
+ Left foot closes home0.85s · settle
+ Close the left foot and settle over both feet, completing the square
@@ -159,43 +159,50 @@
The .posecode source
rig humanoid
pose start = standing
- step "Step forward-right" 0.9s flow:
- hip_right: flex 35
- knee_right: flex 40
- ankle_right: plantarflex 40
- shoulder_left: flex 25
- shoulder_right: extend 15
- travel: -0.35 0.35
+ step "Right foot forward" 0.9s flow:
+ hip_right: flex 30
+ knee_right: flex 32
+ ankle_right: plantarflex 24
+ shoulder_left: flex 22
+ shoulder_right: extend 12
+ travel: 0 0.32
pin: foot_left floor
- cue "Step the right foot forward and out to the corner"
+ cue "Step forward onto the right foot with an easy contralateral arm swing"
- step "Close the feet" 0.7s flow:
+ step "Left foot side" 0.85s flow:
hip_right: flex 0
knee_right: flex 0
ankle_right: plantarflex 0
- shoulders: flex 0
- travel: -0.35 0.35
- ground-lock: feet
- cue "Bring the left foot to meet it, standing tall on the corner"
-
- step "Step back-left" 0.9s flow:
- hip_left: flex 35
- knee_left: flex 40
- ankle_left: plantarflex 40
- shoulder_right: flex 25
- shoulder_left: extend 15
- travel: 0 0
+ hip_left: abduct 24
+ knee_left: flex 22
+ ankle_left: plantarflex 20
+ shoulder_left: flex 0
+ shoulder_right: flex 20
+ travel: -0.32 0.32
pin: foot_right floor
- cue "Step the left foot back to the start on the diagonal"
+ cue "Transfer left to the side, keeping the torso lifted"
- step "Close home" 0.7s settle:
- hip_left: flex 0
+ step "Right foot back" 0.9s flow:
+ hip_left: abduct 0
knee_left: flex 0
ankle_left: plantarflex 0
+ hip_right: extend 18
+ knee_right: flex 30
+ ankle_right: plantarflex 24
+ shoulder_right: flex 0
+ shoulder_left: flex 22
+ travel: -0.32 0
+ pin: foot_left floor
+ cue "Step back onto the right foot to trace the third side of the box"
+
+ step "Left foot closes home" 0.85s settle:
+ hip_right: extend 0
+ knee_right: flex 0
+ ankle_right: plantarflex 0
shoulders: flex 0
travel: 0 0
ground-lock: feet
- cue "Close the feet together, back home and ready to repeat"
+ cue "Close the left foot and settle over both feet, completing the square"
repeat 4
- Reach & push0.8s · settle
- Reach the lead foot out and push off to travel sideways
+ Right foot reaches0.65s · flow
+ Reach the right foot sideways and push away from the left leg
- Gallop close0.6s · drive
- Chase the trailing foot to the lead: feet meet in the air
+ Left foot chases0.5s · flow
+ Let the left foot chase under the body without breaking the sideways flow
- Reach & push back0.8s · settle
- Change direction and travel back the other way
+ Right foot reaches again0.65s · flow
+ Reach right once more and finish the outward chassé
- Gallop home0.6s · drive
- Close home, ready to chassé again
+ Left foot reaches back0.65s · flow
+ Reverse cleanly and reach the left foot back across the floor
+
+
+ Right foot chases0.5s · flow
+ Let the right foot chase under the body on the return
+
+
+ Left foot closes home0.7s · settle
+ Close home over both feet and let the momentum resolve
@@ -159,47 +167,73 @@
The .posecode source
rig humanoid
pose start = standing
- step "Reach & push" 0.8s settle:
- hip_right: flex 30
- knee_right: flex 25
- ankle_right: plantarflex 25
+ step "Right foot reaches" 0.65s flow:
+ hip_right: abduct 26
+ knee_right: flex 20
+ ankle_right: plantarflex 24
ankle_left: plantarflex 20
shoulders: abduct 60
elbows: flex 18
- travel: 0.5 0
+ travel: 0.34 0
pin: foot_left floor
reach: foot_right floor
- cue "Reach the lead foot out and push off to travel sideways"
+ cue "Reach the right foot sideways and push away from the left leg"
- step "Gallop close" 0.6s drive:
- hip_right: flex 0
+ step "Left foot chases" 0.5s flow:
+ hip_right: abduct 0
knee_right: flex 0
ankle_right: plantarflex 0
- ankle_left: plantarflex 0
- travel: 0.9 0
- ground-lock: feet
- cue "Chase the trailing foot to the lead: feet meet in the air"
+ hip_left: abduct 18
+ knee_left: flex 22
+ ankle_left: plantarflex 24
+ travel: 0.66 0
+ pin: foot_right floor
+ cue "Let the left foot chase under the body without breaking the sideways flow"
+
+ step "Right foot reaches again" 0.65s flow:
+ hip_left: abduct 0
+ knee_left: flex 0
+ ankle_left: plantarflex 18
+ hip_right: abduct 26
+ knee_right: flex 20
+ ankle_right: plantarflex 24
+ travel: 1 0
+ pin: foot_left floor
+ reach: foot_right floor
+ cue "Reach right once more and finish the outward chassé"
- step "Reach & push back" 0.8s settle:
- hip_left: flex 30
- knee_left: flex 25
- ankle_left: plantarflex 25
+ step "Left foot reaches back" 0.65s flow:
+ hip_right: abduct 0
+ knee_right: flex 0
ankle_right: plantarflex 20
- travel: 0.4 0
+ hip_left: abduct 26
+ knee_left: flex 20
+ ankle_left: plantarflex 24
+ travel: 0.66 0
pin: foot_right floor
reach: foot_left floor
- cue "Change direction and travel back the other way"
+ cue "Reverse cleanly and reach the left foot back across the floor"
- step "Gallop home" 0.6s drive:
- hip_left: flex 0
+ step "Right foot chases" 0.5s flow:
+ hip_left: abduct 0
knee_left: flex 0
ankle_left: plantarflex 0
- ankle_right: plantarflex 0
+ hip_right: abduct 18
+ knee_right: flex 22
+ ankle_right: plantarflex 24
+ travel: 0.34 0
+ pin: foot_left floor
+ cue "Let the right foot chase under the body on the return"
+
+ step "Left foot closes home" 0.7s settle:
+ hip_right: abduct 0
+ knee_right: flex 0
+ ankles: plantarflex 0
shoulders: abduct 0
elbows: flex 0
travel: 0 0
ground-lock: feet
- cue "Close home, ready to chassé again"
+ cue "Close home over both feet and let the momentum resolve"
repeat 4
diff --git a/playground/public/moves/dance-phrase.html b/playground/public/moves/dance-phrase.html
index b7b4354..421fdd4 100644
--- a/playground/public/moves/dance-phrase.html
+++ b/playground/public/moves/dance-phrase.html
@@ -147,7 +147,7 @@
How to do it
Press up to relevé and lift the arms into a frame overhead
- 7-8 - close2s · settle
+ 7-8 - close2.2s · settleLower the heels and resolve, arms floating back to the sides
- Step out0.7s · flow
- Step the leading foot out to the side
+ Left step side0.65s · flow
+ Step the left foot to the side and begin travelling
- Cross behind0.7s · flow
- Cross the trailing foot behind and keep travelling
+ Right crosses behind0.65s · flow
+ Cross the right foot behind without stopping the sideways momentum
- Step out again0.7s · flow
- Step out to the side once more
+ Left step side again0.65s · flow
+ Step left again and keep the shoulders quiet
- Return home1s · settle
- Travel back the other way to the starting spot
+ Right touch0.55s · flow
+ Touch the right foot in lightly before reversing
+
+
+ Right step side0.65s · flow
+ Step the right foot to the side to travel back
+
+
+ Left crosses behind0.65s · flow
+ Cross the left foot behind and continue through the beat
+
+
+ Right step side again0.65s · flow
+ Step right and arrive back near the starting place
+
+
+ Close home0.65s · settle
+ Close the left foot under the body and settle the phrase
@@ -159,40 +175,77 @@
The .posecode source
rig humanoid
pose start = standing
- step "Step out" 0.7s flow:
- hip_left: abduct 30
+ step "Left step side" 0.65s flow:
+ hip_left: abduct 28
shoulders: abduct 40
- travel: 0.4 0
+ travel: 0.3 0
pin: foot_right floor
reach: foot_left floor
- cue "Step the leading foot out to the side"
+ cue "Step the left foot to the side and begin travelling"
- step "Cross behind" 0.7s flow:
+ step "Right crosses behind" 0.65s flow:
hip_left: abduct 0
hip_right: rotate-in 20
knee_right: flex 25
ankle_right: plantarflex 25
- travel: 0.8 0
+ travel: 0.6 0
pin: foot_left floor
reach: foot_right floor
- cue "Cross the trailing foot behind and keep travelling"
+ cue "Cross the right foot behind without stopping the sideways momentum"
- step "Step out again" 0.7s flow:
+ step "Left step side again" 0.65s flow:
hip_right: rotate-in 0
knee_right: flex 0
ankle_right: plantarflex 0
- hip_left: abduct 30
- travel: 1.2 0
+ hip_left: abduct 28
+ travel: 0.9 0
pin: foot_right floor
reach: foot_left floor
- cue "Step out to the side once more"
+ cue "Step left again and keep the shoulders quiet"
- step "Return home" 1s settle:
+ step "Right touch" 0.55s flow:
hip_left: abduct 0
+ knee_right: flex 16
+ ankle_right: plantarflex 20
+ travel: 1 0
+ ground-lock: feet
+ cue "Touch the right foot in lightly before reversing"
+
+ step "Right step side" 0.65s flow:
+ knee_right: flex 0
+ ankle_right: plantarflex 0
+ hip_right: abduct 28
+ travel: 0.7 0
+ pin: foot_left floor
+ reach: foot_right floor
+ cue "Step the right foot to the side to travel back"
+
+ step "Left crosses behind" 0.65s flow:
+ hip_right: abduct 0
+ hip_left: rotate-in 20
+ knee_left: flex 25
+ ankle_left: plantarflex 25
+ travel: 0.4 0
+ pin: foot_right floor
+ reach: foot_left floor
+ cue "Cross the left foot behind and continue through the beat"
+
+ step "Right step side again" 0.65s flow:
+ hip_left: rotate-in 0
+ knee_left: flex 0
+ ankle_left: plantarflex 0
+ hip_right: abduct 28
+ travel: 0.1 0
+ pin: foot_left floor
+ reach: foot_right floor
+ cue "Step right and arrive back near the starting place"
+
+ step "Close home" 0.65s settle:
+ hip_right: abduct 0
shoulders: abduct 0
travel: 0 0
ground-lock: feet
- cue "Travel back the other way to the starting spot"
+ cue "Close the left foot under the body and settle the phrase"
repeat 3
diff --git a/playground/public/moves/index.html b/playground/public/moves/index.html
index 4c0b6c0..929724c 100644
--- a/playground/public/moves/index.html
+++ b/playground/public/moves/index.html
@@ -176,8 +176,8 @@
Triceps dips is a intermediate-level fitness movement targeting the
triceps, written in Posecode, a small open-source language
diff --git a/playground/public/moves/waltz-box.html b/playground/public/moves/waltz-box.html
index 39a48c1..dffbfa5 100644
--- a/playground/public/moves/waltz-box.html
+++ b/playground/public/moves/waltz-box.html
@@ -8,11 +8,11 @@
-
+
-
+
@@ -99,7 +99,7 @@
@media(max-width:600px){.wrap{padding:0 18px}}
-
+
@@ -135,20 +135,28 @@
Waltz box step
How to do it
- 1 - forward, rise1s · flow
- Step forward onto the right foot and rise, arms in a soft frame
+ 1 - right foot forward0.95s · flow
+ Step forward onto the right foot and begin the waltz rise
- 2 - side, lower1s · flow
- Side step to the corner and lower through the heels
+ 2 - left foot side0.95s · flow
+ Step left to the side and continue rising through count two
- 3 - back, rise1s · flow
- Step back onto the left foot and rise again
+ 3 - close and lower1.05s · flow
+ Close the right foot to the left and lower softly through count three
- 4 - close home1s · settle
- Close to the start, completing the box
+ 4 - left foot back0.95s · flow
+ Step back onto the left foot and begin the second rise
+
+
+ 5 - right foot side0.95s · flow
+ Step right to the side, staying buoyant through count five
+
+
+ 6 - close and lower1.05s · settle
+ Close the left foot and lower with control to complete the six-count box
Posecode is a small text language for describing a single person's kinematic movement so it can be rendered as an animated 3D figure in a web browser.
It is to human movement what Mermaid is to diagrams: an LLM (or a human) writes a compact, readable document; a client-side parser + renderer turns it into a moving mannequin. The model never produces 3D matrices: it expresses the *semantic phases* of a movement, which it already understands.
-
Version keyword: documents declare nothing; this is posecode 0.1.
File extension:.posecode
Compute model: generation is pure text (server-cheap); all 3D math runs
+
Version keyword: documents declare nothing; this is posecode 0.2.
Compatibility: v0.2 parsers continue to accept the v0.1 easing aliases.
File extension:.posecode
Compute model: generation is pure text (server-cheap); all 3D math runs
on the client (Three.js). See the project research §6.
cue = "cue" STRING ;
DURATION = NUMBER "s" ; (* e.g. 2s, 1.5s *)
A step is one phase of the movement. Phases run in sequence; within a phase, all joint targets apply concurrently.
+
Ground locks accept the groups hands, forearms, and feet, plus the single-side forms hand_left|hand_right, elbow_left|elbow_right, and foot_left|foot_right. Human-readable left foot, right foot, left hand, and related forms normalize to the canonical side-specific names.
+
Timing modes describe how motion crosses the phase boundary:
+
Mode
Use
+
flow
Carry velocity smoothly through the pose.
+
settle
Decelerate into a deliberate rest.
+
drive
Accelerate out of rest.
+
snap
Arrive quickly and stop sharply.
+
linear
Even timing without a shaped acceleration curve.
+
+
Legacy v0.1 spellings remain valid and normalize to canonical modes: ease-in → drive, ease-out → settle, and ease-in-out → settle. New documents should use the canonical v0.2 names.
2. Joints
Group (plural)
Bones
Singular forms
@@ -195,8 +205,8 @@
4. Range of Motion (safety)
> These are general literature values, not medical advice. Consult a > qualified professional for physiotherapy or exercise prescription.
5. Rendering model
-
Forward kinematics: each phase sets joint angles; the renderer slerps
-
bone rotations between phases with the phase's easing.
+
Forward kinematics: each phase sets joint angles; the renderer uses
+
C1-continuous quaternion splines between keyframes, shaped by the destination phase's timing mode.
Grounding: the figure is dropped so its lowest point rests on the floor
(a bounding-box drop), which grounds standing, plank, and the lying/seated poses alike.
Ground-lock IK: effectors listed in ground-lock (hands, forearms,
@@ -204,7 +214,7 @@
5. Rendering model
Reach-IK: a reach: line drives an effector (`hand_left|hand_right|
foot_left|foot_right, or the groups hands/feet for both sides) to a world target via Cyclic Coordinate Descent (CCD) over the arm/leg chain. A target is a body landmark bone (e.g. ankle_left), the keyword floor, or a prop anchor (bar, seat, wall`). The solve is ROM-constrained: each iteration clamps every chain joint into its §4 Range-of-Motion limits (expressed as a per-axis box in the bone's local Euler frame), so a reach toward an unsafe or unreachable target settles on the closest *healthy* pose; solved angles obey the same hard limits as authored ones.
Props: prop chair|wall|bar|box|dip-bars adds a scene object at a
-
fixed default placement (chair/wall behind, bar overhead, box in front, dip bars either side); its named anchors (seat, wall, bar, box, bars) become reach/pin targets.
+
fixed default placement (chair/wall behind, bar overhead, box in front, dip bars either side); its named anchors (seat, wall, bar, box, bars) become reach/pin targets. Props are solid: each prop declares blocking faces (the wall's surface, the chair's backrest and seat edge, the box's near face) and a contact pass removes any body overlap — either by translating the whole figure out along the face normal (a wall-sit slides down the wall's *surface*, feet walking forward, instead of the torso hinging through the slab) or by bending the offending limb's hip clear, ROM-clamped like every other solve. Limbs pinned, gripped, or reached to a prop anchor are that phase's declared support and are exempt (a foot standing on the box top is not "inside" the box).
Pins: pin: <effector> <anchor> translates the whole figure so the
effector sits on the anchor (effectors accept the same hands/feet groups as reach: pin: hands bar pins both). Where ground-lock keeps a foot on the floor and reach moves a limb to a target, a pin moves the body while the contact stays put, so the figure hangs from a bar, pulls up toward it, rises onto a box, or lowers into a dip as the joints work. Symmetric bar contacts resolve to side-specific anchors automatically (bar.left / bar.right, bars.left / bars.right). Contact post-processing also keeps floor-contacting soles level and gives bar-contacting wrists a stable overhand orientation; existing Posecode syntax remains unchanged.
Spatial choreography: turn: <deg> rotates the figure's facing (yaw
@@ -213,12 +223,12 @@
5. Rendering model
count surfaced to the UI.
When a mocap clip is active, the renderer selects the take containing the most actual bone motion (rather than blindly choosing the longest embedded take), retargets and blends it, then restores solved terminal contacts on the visible character. Mocap therefore cannot overwrite a planted sole or pinned grip.
Start poses:neutral, standing, plank, supine (face-up), prone (face-down), seated (long-sit on the floor).
-
IK note: Three.js's bundled CCDIKSolver targets SkinnedMesh; the Posecode mannequin is rigid capsule segments, so Posecode implements CCD directly over the Object3D bone hierarchy (posecode-render/ik.ts) for both ground-lock and reach. Two-person/dual-IK and collision detection remain deferred (research §5.2, §6.2).
+
IK note: Three.js's bundled CCDIKSolver targets SkinnedMesh; the Posecode mannequin is rigid capsule segments, so Posecode implements CCD directly over the Object3D bone hierarchy (posecode-render/ik.ts) for both ground-lock and reach. Self-collision (limb-vs-body) and body-vs-prop contact are solved; two-person/ dual-IK and figure-vs-figure collision remain deferred (research §5.2, §6.2).
6. Intermediate Representation (IR)
parse(source) returns { ir, warnings, errors }. The IR is renderer-agnostic; angles are in degrees.
phases: {
name: string;
durationSec: number;
- easing: "linear" | "ease-in" | "ease-out" | "ease-in-out";
+ easing: "flow" | "settle" | "drive" | "snap" | "linear";
targets: { boneId: string; euler: { x: number; y: number; z: number } }[];
groundLock: string[]; // ["hands","feet"] or ["foot_right"]
cue?: string;
diff --git a/playground/public/squat.posecode b/playground/public/squat.posecode
index 97db8f2..fde149e 100644
--- a/playground/public/squat.posecode
+++ b/playground/public/squat.posecode
@@ -2,7 +2,7 @@ posecode exercise "Body-weight squat"
rig humanoid
pose start = standing
- step "Descend" 1.6s ease-in-out:
+ step "Descend" 1.6s settle:
hips: flex 80
knees: flex 95
ankles: dorsiflex 15
@@ -13,7 +13,7 @@ posecode exercise "Body-weight squat"
ground-lock: feet
cue "Sit the hips back, chest proud, knees track over the toes"
- step "Drive up" 1.2s ease-out:
+ step "Drive up" 1.2s drive:
hips: flex 0
knees: flex 0
ankles: plantarflex 0
diff --git a/playground/src/editor.ts b/playground/src/editor.ts
index 6c6a3bf..a9d6968 100644
--- a/playground/src/editor.ts
+++ b/playground/src/editor.ts
@@ -75,6 +75,10 @@ const ACTIONS = new Set([
"plantarflex",
]);
const ATOMS = new Set([
+ "flow",
+ "settle",
+ "drive",
+ "snap",
"linear",
"ease-in",
"ease-out",
diff --git a/scripts/generate-content-pages.mjs b/scripts/generate-content-pages.mjs
index b118633..ead5fdc 100644
--- a/scripts/generate-content-pages.mjs
+++ b/scripts/generate-content-pages.mjs
@@ -62,7 +62,11 @@ function slugTitle(label) {
}
async function main() {
- const server = await createServer({ root: playgroundRoot, server: { middlewareMode: true } });
+ const server = await createServer({
+ root: playgroundRoot,
+ appType: "custom",
+ server: { middlewareMode: true, hmr: false, ws: false },
+ });
let PRESETS;
try {
({ PRESETS } = await server.ssrLoadModule("/src/presets.ts"));
@@ -199,7 +203,7 @@ ${stepsHtml}
const specHtml = pageShell({
title: "Posecode Language Specification: The .posecode Kinematic Motion DSL",
description:
- "The full Posecode v0.1 grammar, joints, actions, and range-of-motion tables: a small text language LLMs write to describe human movement as an animated 3D figure.",
+ "The full Posecode v0.2 grammar, timing modes, joints, actions, and range-of-motion tables: a small text language LLMs write to describe human movement as an animated 3D figure.",
canonicalPath: "/spec.html",
bodyHtml: `
Reference
\n${renderMarkdown(specMd)}`,
});
diff --git a/spec/SPEC.md b/spec/SPEC.md
index 1dd0d38..d459cc2 100644
--- a/spec/SPEC.md
+++ b/spec/SPEC.md
@@ -1,4 +1,4 @@
-# Posecode Protocol Specification v0.1
+# Posecode Protocol Specification v0.2
Posecode is a small text language for describing a single person's **kinematic
movement** so it can be rendered as an animated 3D figure in a web browser.
@@ -8,7 +8,8 @@ a compact, readable document; a client-side parser + renderer turns it into a
moving mannequin. The model never produces 3D matrices: it expresses the
*semantic phases* of a movement, which it already understands.
-- **Version keyword:** documents declare nothing; this is `posecode 0.1`.
+- **Version keyword:** documents declare nothing; this is `posecode 0.2`.
+- **Compatibility:** v0.2 parsers continue to accept the v0.1 easing aliases.
- **File extension:** `.posecode`
- **Compute model:** generation is pure text (server-cheap); all 3D math runs
on the client (Three.js). See the project research §6.
@@ -29,8 +30,8 @@ prop = "prop" WORD ; (* chair|wall|bar|box|di
pose = "pose" "start" "=" WORD ; (* neutral|standing|plank|supine|prone|seated *)
clip = "clip" STRING ; (* optional mocap clip; renderer may retarget & blend *)
repeat = "repeat" NUMBER ;
-step = "step" STRING DURATION easing ":" { child } ;
-easing = "linear" | "ease-in" | "ease-out" | "ease-in-out" ;
+step = "step" STRING DURATION timingMode ":" { child } ;
+timingMode = "flow" | "settle" | "drive" | "snap" | "linear" ;
child = jointTarget | groundLock | reach | pin | turn | travel | cue ;
jointTarget= joint ":" action [ NUMBER ] ;
groundLock = "ground-lock" ":" effector { "," effector } ;
@@ -45,6 +46,25 @@ DURATION = NUMBER "s" ; (* e.g. 2s, 1.5s *)
A `step` is one **phase** of the movement. Phases run in sequence; within a
phase, all joint targets apply concurrently.
+Ground locks accept the groups `hands`, `forearms`, and `feet`, plus the
+single-side forms `hand_left|hand_right`, `elbow_left|elbow_right`, and
+`foot_left|foot_right`. Human-readable `left foot`, `right foot`, `left hand`,
+and related forms normalize to the canonical side-specific names.
+
+Timing modes describe how motion crosses the phase boundary:
+
+| Mode | Use |
+| --- | --- |
+| `flow` | Carry velocity smoothly through the pose. |
+| `settle` | Decelerate into a deliberate rest. |
+| `drive` | Accelerate out of rest. |
+| `snap` | Arrive quickly and stop sharply. |
+| `linear` | Even timing without a shaped acceleration curve. |
+
+Legacy v0.1 spellings remain valid and normalize to canonical modes:
+`ease-in` → `drive`, `ease-out` → `settle`, and `ease-in-out` → `settle`.
+New documents should use the canonical v0.2 names.
+
---
## 2. Joints
@@ -119,8 +139,9 @@ research §5.1 normative tables. Selected ceilings (degrees):
## 5. Rendering model
-1. **Forward kinematics**: each phase sets joint angles; the renderer slerps
- bone rotations between phases with the phase's easing.
+1. **Forward kinematics**: each phase sets joint angles; the renderer uses
+ C1-continuous quaternion splines between keyframes, shaped by the destination
+ phase's timing mode.
2. **Grounding**: the figure is dropped so its lowest point rests on the floor
(a bounding-box drop), which grounds standing, plank, and the lying/seated
poses alike.
@@ -194,7 +215,7 @@ angles are in **degrees**.
```ts
interface PosecodeIR {
- version: string; // "0.1"
+ version: string; // "0.2"
kind: string; // "exercise" | "stretch" | "posture"
name: string;
rig: string; // "humanoid"
@@ -203,7 +224,7 @@ interface PosecodeIR {
phases: {
name: string;
durationSec: number;
- easing: "linear" | "ease-in" | "ease-out" | "ease-in-out";
+ easing: "flow" | "settle" | "drive" | "snap" | "linear";
targets: { boneId: string; euler: { x: number; y: number; z: number } }[];
groundLock: string[]; // ["hands","feet"] or ["foot_right"]
cue?: string;
diff --git a/spec/llm-authoring.md b/spec/llm-authoring.md
index 50f0450..d28f1f8 100644
--- a/spec/llm-authoring.md
+++ b/spec/llm-authoring.md
@@ -17,10 +17,10 @@ posecode "" # kind = exercise | stretch | posture
rig humanoid
prop # optional: chair | wall | bar | box | dip-bars (repeatable)
pose start = # neutral | standing | plank | supine | prone | seated
- step "" : # easing = linear | ease-in | ease-out | ease-in-out
+ step "" : # mode = flow | settle | drive | snap | linear
:
reach: # optional: drive a hand/foot to a target via IK
- ground-lock: # groups (hands/forearms/feet) or per-side aliases such as foot_right
+ ground-lock: # groups, foot_left/foot_right, or natural left foot/right foot
turn: # optional: face this yaw by phase end (standing only)
travel: # optional: move to this x z (metres) by phase end
cue ""
@@ -57,6 +57,19 @@ wrists hips knees ankles`. Plural names move both sides symmetrically; use
floor (feet when standing; hands and feet in a plank).
5. `repeat` the rep count.
+## Timing modes
+
+- `flow`: carry momentum through an interior pose; use for continuous dance,
+ locomotion, and multi-part sports motion.
+- `settle`: decelerate into a real rest; use at a squat bottom, landing, hold,
+ or final pose.
+- `drive`: accelerate from rest; use for a jump, push, lift, or recoil.
+- `snap`: arrive quickly and stop sharply; use for a strike or release.
+- `linear`: constant timing; best for deliberate holds or mechanical motion.
+
+The older names `ease-in`, `ease-out`, and `ease-in-out` still parse for
+compatibility, but do not author new documents with them.
+
## Example
```posecode
@@ -64,14 +77,14 @@ posecode exercise "Body-weight squat"
rig humanoid
pose start = standing
- step "Descend" 1.6s ease-in-out:
+ step "Descend" 1.6s settle:
hips: flex 80
knees: flex 95
ankles: dorsiflex 14
ground-lock: feet
cue "Sit the hips back, chest proud, knees track over the toes"
- step "Drive up" 1.2s ease-out:
+ step "Drive up" 1.2s drive:
hips: flex 0
knees: flex 0
ankles: dorsiflex 0
@@ -91,14 +104,14 @@ posecode exercise "Deadlift"
rig humanoid
pose start = standing
- step "Lower" 1.8s ease-in-out:
+ step "Lower" 1.8s settle:
pelvis: hinge 95
knees: flex 25
shoulders: flex 90
ground-lock: feet
cue "Hips back, flat back: let the arms hang to the bar"
- step "Lift" 1.4s ease-out:
+ step "Lift" 1.4s drive:
pelvis: hinge 0
knees: flex 0
shoulders: flex 0
@@ -120,7 +133,7 @@ posecode exercise "Deadlift"
let `reach` finish the hand placement. Example, touch your toes:
```posecode
- step "Fold" 2.5s ease-in-out:
+ step "Fold" 2.5s settle:
pelvis: hinge 95
knees: flex 12
reach: hand_left ankle_left
@@ -155,8 +168,8 @@ The same grammar covers many fields. A few patterns that read well:
leg stays planted.
- **Desk / posture**: slow `stretch` documents with `ground-lock: feet`;
contrast a "collapsed" phase with a "tall" reset.
-- **Sports / martial arts**: short, snappy phases (0.3–0.6s) with `ease-out`
- on the strike; chamber → extend → re-chamber → return.
+- **Sports / martial arts**: short phases (0.2–0.6s); use `flow` through a
+ gather/chamber, `drive` for takeoff, and `snap` on the strike or release.
### Dance / choreography