From 33a8a4e920a41cd000fe6d6db978ca8f263d911b Mon Sep 17 00:00:00 2001
From: a-baran-orhan
Date: Fri, 17 Jul 2026 22:16:42 +0300
Subject: [PATCH 1/2] Fix landing preview and planted movement
---
packages/posecode-eval/src/checks.ts | 34 ++++++++++++++
packages/posecode-eval/test/eval.test.ts | 10 +++++
packages/posecode-language/src/vocab.ts | 2 +-
packages/posecode-mcp/src/guide.ts | 2 +-
packages/posecode-parser/src/protocol.ts | 1 +
packages/posecode-render/src/poses.ts | 14 ++++++
packages/posecode-render/test/render.test.ts | 44 +++++++++++++++++++
playground/index.html | 6 +--
playground/public/llm-guide.html | 4 +-
playground/public/moves/demi-plie.html | 20 ++++-----
.../public/moves/superhero-landing.html | 16 +++----
playground/public/spec.html | 4 +-
playground/src/landing.css | 8 +++-
spec/SPEC.md | 7 +--
spec/examples/demi-plie.posecode | 16 +++----
spec/examples/superhero-landing.posecode | 14 +++---
spec/llm-authoring.md | 4 +-
17 files changed, 158 insertions(+), 48 deletions(-)
diff --git a/packages/posecode-eval/src/checks.ts b/packages/posecode-eval/src/checks.ts
index 55f65bd..7062d7f 100644
--- a/packages/posecode-eval/src/checks.ts
+++ b/packages/posecode-eval/src/checks.ts
@@ -326,6 +326,33 @@ export const MOVEMENT_CHECKS: MovementChecks[] = [
},
],
},
+ {
+ // A demi-plié keeps the turnout established at the hips while both feet
+ // remain planted. Checking each ankle independently catches the symmetric
+ // outward/inward skate that a center-only ground-lock metric cannot see.
+ movement: "demi-plie",
+ checks: [
+ (result) => {
+ if (result.phases.length < 2) {
+ return { id: "demi-plie-feet-stay-planted", pass: false, detail: "plié phases missing" };
+ }
+ let maxDrift = 0;
+ for (let i = 1; i < result.phases.length; i++) {
+ for (const side of ["left", "right"] as const) {
+ maxDrift = Math.max(
+ maxDrift,
+ footSkateDistance(result.phases[i - 1]!, result.phases[i]!, side),
+ );
+ }
+ }
+ return {
+ id: "demi-plie-feet-stay-planted",
+ pass: maxDrift < 0.015,
+ detail: `${maxDrift.toFixed(3)}m maximum ankle drift (want < 0.015m)`,
+ };
+ },
+ ],
+ },
{
// Three-point landing: the front sole, rear knee, and opposite fist must
// form distinct supports while the torso stays above the floor. Contact
@@ -339,6 +366,13 @@ export const MOVEMENT_CHECKS: MovementChecks[] = [
(v) => v >= 65 && v <= 100,
"65–100° character-forward pitch",
),
+ phaseCheck(
+ "planted-fist-palm-inward",
+ "Hold the landing",
+ (p) => palmInwardAngleDeg(p, "left"),
+ (v) => v < 30,
+ "< 30° from inward",
+ ),
(result) => {
const p = phase(result, "Hold the landing");
if (!p) return { id: "exact-three-supports", pass: false, detail: "phase \"Hold the landing\" not found" };
diff --git a/packages/posecode-eval/test/eval.test.ts b/packages/posecode-eval/test/eval.test.ts
index d786fa0..51a22a3 100644
--- a/packages/posecode-eval/test/eval.test.ts
+++ b/packages/posecode-eval/test/eval.test.ts
@@ -286,4 +286,14 @@ describe("fixture scorecard", () => {
expect(movement.passed).toBeLessThan(movement.total);
expect(failed.some((check) => check.id.startsWith("contact-position:"))).toBe(true);
});
+
+ it("rejects a superhero landing whose planted fist faces away from the body", () => {
+ const fixtures = loadFixtures(examplesDir);
+ const source = fixtures.find((fixture) => fixture.movement === "superhero-landing")!.source;
+ const outward = source.replace("elbow_left: pronate 80", "elbow_left: supinate 80");
+ const check = runEval([{ movement: "superhero-landing", source: outward }])
+ .movements[0]!.checks.find((item) => item.id === "planted-fist-palm-inward");
+
+ expect(check).toEqual(expect.objectContaining({ pass: false }));
+ });
});
diff --git a/packages/posecode-language/src/vocab.ts b/packages/posecode-language/src/vocab.ts
index 4362bcf..d20e38a 100644
--- a/packages/posecode-language/src/vocab.ts
+++ b/packages/posecode-language/src/vocab.ts
@@ -55,7 +55,7 @@ export const KEYWORD_DOCS: Record = {
posecode: 'Document header: `posecode ""`.',
rig: "Selects the rig (currently `humanoid`).",
prop: "Adds a scene object: `prop chair | wall | bar | box | dip-bars`. Supplies declared reach, pin, and grip anchors.",
- pose: "Sets the starting pose: `pose start = standing | neutral | plank | supine | prone | seated`.",
+ pose: "Sets the starting pose: `pose start = standing | first-position | neutral | plank | supine | prone | seated`.",
start: "Used in `pose start = `.",
clip: 'Optional mocap clip: `clip "walk"`. A renderer with a matching retargeted animation plays it crossfaded over the procedural pose; others ignore it.',
step: 'A movement phase: `step "" :` where mode is flow | settle | drive | snap | linear.',
diff --git a/packages/posecode-mcp/src/guide.ts b/packages/posecode-mcp/src/guide.ts
index 315ac86..9b34d32 100644
--- a/packages/posecode-mcp/src/guide.ts
+++ b/packages/posecode-mcp/src/guide.ts
@@ -48,7 +48,7 @@ say that Posecode cannot yet represent the missing capability.
posecode "" # kind = exercise | stretch | posture
rig humanoid
prop # optional: chair | wall | bar | box | dip-bars
- pose start = # neutral | standing | plank | supine | prone | seated
+ pose start = # neutral | standing | first-position | plank | supine | prone | seated
step "" : # mode = flow | settle | drive | snap | linear
:
ground-lock: # repeat feet/hands/forearms/back or side-specific supports
diff --git a/packages/posecode-parser/src/protocol.ts b/packages/posecode-parser/src/protocol.ts
index e2a3fdf..9044866 100644
--- a/packages/posecode-parser/src/protocol.ts
+++ b/packages/posecode-parser/src/protocol.ts
@@ -9,6 +9,7 @@ export type RigName = (typeof RIG_NAMES)[number];
export const START_POSE_NAMES = [
"neutral",
"standing",
+ "first-position",
"plank",
"supine",
"prone",
diff --git a/packages/posecode-render/src/poses.ts b/packages/posecode-render/src/poses.ts
index 4251494..7eb7bf1 100644
--- a/packages/posecode-render/src/poses.ts
+++ b/packages/posecode-render/src/poses.ts
@@ -52,6 +52,19 @@ const STANDING: PoseSpec = {
joints: { ...RELAXED_FOREARMS },
};
+// Ballet first position: legs externally rotated from the hips while the
+// straight-leg chain stays vertical. Seeding turnout in the base pose means a
+// demi-plié can keep it constant instead of visibly twisting planted feet on
+// every descent and rise.
+const FIRST_POSITION: PoseSpec = {
+ root: { position: [0, 0, 0], rotationDeg: [0, 0, 0] },
+ joints: {
+ ...RELAXED_FOREARMS,
+ hip_left: [0, 30, 0],
+ hip_right: [0, -30, 0],
+ },
+};
+
// Lying face-up. Rotating the standing figure -90° about X lays it on its back:
// the original front (+Z) ends up facing the ceiling (+Y) and the head points
// toward -Z. groundFigure() (bounding-box drop) then rests the back on the floor.
@@ -81,6 +94,7 @@ const SEATED: PoseSpec = {
const POSES: Record = {
neutral: NEUTRAL,
standing: STANDING,
+ "first-position": FIRST_POSITION,
plank: PLANK,
supine: SUPINE,
prone: PRONE,
diff --git a/packages/posecode-render/test/render.test.ts b/packages/posecode-render/test/render.test.ts
index 653011e..7076d51 100644
--- a/packages/posecode-render/test/render.test.ts
+++ b/packages/posecode-render/test/render.test.ts
@@ -1,4 +1,5 @@
import { describe, it, expect } from "vitest";
+import fs from "node:fs";
import * as THREE from "three";
import { buildMannequin } from "../src/mannequin.js";
import { buildTimeline } from "../src/timeline.js";
@@ -473,6 +474,49 @@ describe("ground-lock (shared solver)", () => {
return m;
}
+ it("keeps the canonical demi-plié turned out without foot friction", () => {
+ const source = fs.readFileSync(
+ new URL("../../../spec/examples/demi-plie.posecode", import.meta.url),
+ "utf8",
+ );
+ const { ir, errors } = parse(source);
+ expect(errors).toEqual([]);
+ const timeline = buildTimeline(ir!);
+ const m = buildMannequin();
+
+ expect(timeline.basePose.joints?.hip_left?.[1]).toBe(30);
+ expect(timeline.basePose.joints?.hip_right?.[1]).toBe(-30);
+
+ timeline.sample(0, m.bones);
+ groundFigure(m);
+ const basePosition = m.root.position.clone();
+ const baseRotation = m.root.quaternion.clone();
+ const anchors = new Map(
+ ["ankle_left", "ankle_right"].map((id) => [
+ id,
+ m.bones.get(id)!.getWorldPosition(new THREE.Vector3()),
+ ]),
+ );
+
+ let maxDrift = 0;
+ for (let t = 0; t < timeline.duration; t += 0.025) {
+ m.root.position.copy(basePosition);
+ m.root.quaternion.copy(baseRotation);
+ const info = timeline.sample(t, m.bones);
+ m.root.updateMatrixWorld(true);
+ applyGroundLock(m, info.groundLock, anchors);
+ levelPlantedFeet(m, info.groundLock);
+ m.root.updateMatrixWorld(true);
+ for (const id of ["ankle_left", "ankle_right"]) {
+ const point = m.bones.get(id)!.getWorldPosition(new THREE.Vector3());
+ const anchor = anchors.get(id)!;
+ maxDrift = Math.max(maxDrift, Math.hypot(point.x - anchor.x, point.z - anchor.z));
+ }
+ }
+
+ expect(maxDrift).toBeLessThan(0.01);
+ });
+
it("drops the body and plants the foot mesh for a feet-only squat", () => {
const m = posedRaw(
[
diff --git a/playground/index.html b/playground/index.html
index 72a879a..f4009c4 100644
--- a/playground/index.html
+++ b/playground/index.html
@@ -177,14 +177,14 @@
Drop into the landing
-
+
# contact-phase excerpt
posecode posture "Superhero Three-Point Landing"
pose start = standing
step "Make three-point contact" 0.3s settle:
- pin: knee_left floor
- reach: foot_right floor
+ ground-lock: foot_right
+ reach: knee_left floor
reach: fist_left floor
cue "Set all three contacts"
diff --git a/playground/public/llm-guide.html b/playground/public/llm-guide.html
index 1112abc..329b6c4 100644
--- a/playground/public/llm-guide.html
+++ b/playground/public/llm-guide.html
@@ -129,7 +129,7 @@ Grammar
posecode <kind> "<Name>" # kind = exercise | stretch | posture
rig humanoid
prop <type> # optional: chair | wall | bar | box | dip-bars (repeatable)
- pose start = <pose> # neutral | standing | plank | supine | prone | seated
+ pose start = <pose> # neutral | standing | first-position | plank | supine | prone | seated
step "<Phase name>" <Ns> <mode>: # mode = flow | settle | drive | snap | linear
<joint>: <action> <degrees>
reach: <effector> <target> # limb IK to a landmark, floor, or declared prop anchor
@@ -209,7 +209,7 @@ Reaching, props, lying poses & hands
ground-lock: feet
cue "Hinge and reach toward the ankles"
A hand or fist sent to the floor is also oriented onto its palm or knuckles. That explicit surface contact can adjust forearm/wrist roll within ROM, so do not fight it with a contradictory palm-facing cue.
-- 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 (grip: hands bars + elbow flex = triceps dips). - Pins:
pin: <effector> <anchor> moves the whole BODY so the effector sits on the anchor (vs reach, which moves just the limb). Use one primary pin for body translation: pin: foot_right box can support a step-up, and pin: pelvis floor can keep the pelvis on the mat. Use grip, not several simultaneous hand pins, for a bar or rails. - Grips:
grip: hands bar or grip: hands bars is the dedicated two-hand prop contact. It assigns separate left/right anchors, solves both arms, and closes the fingers. Declare prop bar or prop dip-bars first. Prefer this to multiple pins for a pull-up, hang, or dip. - Lying / seated:
pose start = supine | prone | seated for floor and mat work (glute bridge, dead bug, cobra, seated forward fold). In a supine exercise whose torso stays down, add ground-lock: back to each phase. - Hands:
fingers: flex 80 makes a fist; curl individual fingers for shapes (index_right: flex 95). Single-DOF per finger, good for grip and rough gesture, not exact sign language.
+- 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 (grip: hands bars + elbow flex = triceps dips). - Pins:
pin: <effector> <anchor> moves the whole BODY so the effector sits on the anchor (vs reach, which moves just the limb). Use one primary pin for body translation: pin: foot_right box can support a step-up, and pin: pelvis floor can keep the pelvis on the mat. Use grip, not several simultaneous hand pins, for a bar or rails. - Grips:
grip: hands bar or grip: hands bars is the dedicated two-hand prop contact. It assigns separate left/right anchors, solves both arms, and closes the fingers. Declare prop bar or prop dip-bars first. Prefer this to multiple pins for a pull-up, hang, or dip. - Dance turnout:
pose start = first-position for ballet movements that must keep the legs externally rotated without twisting planted feet - Lying / seated:
pose start = supine | prone | seated for floor and mat work (glute bridge, dead bug, cobra, seated forward fold). In a supine exercise whose torso stays down, add ground-lock: back to each phase. - Hands:
fingers: flex 80 makes a fist; curl individual fingers for shapes (index_right: flex 95). Single-DOF per finger, good for grip and rough gesture, not exact sign language.
Three-point superhero landing
A landing is defined by its contacts, not by a dramatic cue. Keep the existing front-foot support planted while reach constraints blend the rear knee and same-side fist down. Once the knee has arrived, hand the whole-body anchor to that knee and solve the foot and fist independently. Repeat the three contacts through the hold; never combine a whole-root pin with ground-lock in one step.
posecode posture "Superhero Three-Point Landing"
diff --git a/playground/public/moves/demi-plie.html b/playground/public/moves/demi-plie.html
index 9f0862b..4694a6f 100644
--- a/playground/public/moves/demi-plie.html
+++ b/playground/public/moves/demi-plie.html
@@ -137,11 +137,11 @@ Movement phases
-
Plié2.2s · settle
- Turn out from the hips and bend the knees over the toes: heels down
+ Lower straight down with the knees tracking the turned-out toes and both heels anchored
-
Straighten2.2s · settle
- Press the floor away and rise to a tall first position
+ Press the floor away and rise without changing the turnout or moving the feet
@@ -150,29 +150,29 @@ The .posecode source
angles, not 3D transforms.
posecode exercise "Demi-plié"
rig humanoid
- pose start = standing
+ pose start = first-position
step "Plié" 2.2s settle:
- hips: flex 20
- hips: rotate-out 30
+ hips: flex 31.5
+ hips: abduct 17
knees: flex 55
ankles: dorsiflex 15
shoulders: abduct 70
elbows: flex 15
spine: extend 4
ground-lock: feet
- cue "Turn out from the hips and bend the knees over the toes: heels down"
+ cue "Lower straight down with the knees tracking the turned-out toes and both heels anchored"
step "Straighten" 2.2s settle:
hips: flex 0
- hips: rotate-out 0
+ hips: abduct 0
knees: flex 0
- ankles: plantarflex 0
+ ankles: dorsiflex 0
shoulders: abduct 0
elbows: flex 0
- spine: flex 0
+ spine: extend 0
ground-lock: feet
- cue "Press the floor away and rise to a tall first position"
+ cue "Press the floor away and rise without changing the turnout or moving the feet"
repeat 4
diff --git a/playground/public/moves/superhero-landing.html b/playground/public/moves/superhero-landing.html
index 0a5affa..7a9152b 100644
--- a/playground/public/moves/superhero-landing.html
+++ b/playground/public/moves/superhero-landing.html
@@ -141,7 +141,7 @@ Movement phases
Make three-point contact0.3s · settle
- Set the left knee and left fist on the floor beside the planted right foot
+ Keep the right foot planted as the left knee and left fist settle onto the floor
Hold the landing0.8s · linear
@@ -168,12 +168,12 @@ The .posecode source
knee_right: flex 123
ankle_right: dorsiflex 15
hip_left: extend 12
- knee_left: flex 105
+ knee_left: flex 110
ankle_left: plantarflex 28
shoulder_left: flex 98
shoulder_left: abduct 2
elbow_left: flex 8
- elbow_left: supinate 80
+ elbow_left: pronate 80
fingers_left: flex 80
shoulder_right: extend 24
shoulder_right: abduct 22
@@ -185,14 +185,14 @@ The .posecode source
step "Make three-point contact" 0.3s settle:
neck: extend 25
- pin: knee_left floor
- reach: foot_right floor
+ ground-lock: foot_right
+ reach: knee_left floor
reach: fist_left floor
- cue "Set the left knee and left fist on the floor beside the planted right foot"
+ cue "Keep the right foot planted as the left knee and left fist settle onto the floor"
step "Hold the landing" 0.8s linear:
- pin: knee_left floor
- reach: foot_right floor
+ ground-lock: foot_right
+ reach: knee_left floor
reach: fist_left floor
cue "Hold the three contacts with the free right arm swept behind you"
diff --git a/playground/public/spec.html b/playground/public/spec.html
index f42c78c..47a91e8 100644
--- a/playground/public/spec.html
+++ b/playground/public/spec.html
@@ -133,7 +133,7 @@ 1. Grammar
rig = "rig" "humanoid" ;
prop = "prop" ("chair"|"wall"|"bar"|"box"|"dip-bars") ;
pose = "pose" "start" "=" startPose ;
-startPose = "neutral"|"standing"|"plank"|"supine"|"prone"|"seated" ;
+startPose = "neutral"|"standing"|"first-position"|"plank"|"supine"|"prone"|"seated" ;
clip = "clip" STRING ; (* optional mocap clip; renderer may retarget & blend *)
repeat = "repeat" NUMBER ;
step = "step" STRING DURATION timingMode ":" { child } ;
@@ -219,7 +219,7 @@ 4. Configured range-of-motion limits
5. Rendering model
- 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: contacts listed in
ground-lock (hands, forearms, feet, or the per-side aliases hand_left|hand_right, elbow_left|elbow_right, foot_left|foot_right) stay planted while the body moves. back holds the pelvis-to-ribcage surface on the floor for supine work such as dead bugs. Unsupported contact names are line-anchored validation errors. - Reach-IK: a
reach: line drives an effector (hand_*, fist_*, elbow_*, knee_*, or foot_*, plus their supported groups) 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 configured range-of-motion limits (expressed as a per-axis box in the bone's local Euler frame), so a reach toward an unreachable target settles on the closest pose available within that configured joint-angle box; solved angles obey the same limits as authored ones. The viewer records a post-solve residual for every active reach. A reach target is not reported as reached merely because its syntax parsed: missing, unsupported, and geometrically unreachable reach targets remain explicit diagnostics. A palm or fist declared against the floor also presents its matching contact surface to the floor. For a palm, the solver may redistribute incompatible authored roll into a legal forearm/wrist frame; the explicit floor contact takes priority, and every adjusted joint remains inside the same configured ROM. - 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, or grip targets. Selected prop surfaces declare sampled blocking faces (the wall's surface, the chair's backrest and seat edge, the box's near face). A bounded contact pass reduces penetration, 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 one primary effector sits on the anchor. Where ground-lock preserves an already planted floor support and reach moves a limb to a target, a pin moves the body. Typical uses include pin: knee_left floor, pin: foot_right box, and pin: pelvis floor. A phase accepts one pin because each pin translates the same floating root; express additional simultaneous contacts with independent reach constraints. Use grip instead of hand pins for a two-handed bar or rail contact. - Grips:
grip: hands bar|bars is the dedicated two-hand contact for an overhead bar or dip rails; side-specific hand_left / hand_right forms are also available. A grip resolves independent left/right anchors, uses arm IK for each hand, orients the terminal contact, and closes the fingers. The matching prop must be declared. Use grips, rather than hand pins, for hangs, pull-ups, and dips. - Spatial choreography:
turn: <deg> rotates the figure's facing (yaw about vertical) and travel: <x> <z> moves it across the floor (world metres from the load spot). Both are absolute targets carried across phases (like joint angles) and both return home on the loop wrap, so a box-step traces a square back to start and a pirouette spins a full turn. They layer under grounding (feet still rest on the floor) and power pirouettes, grapevines, traveling combos, and walk cycles. Standing poses only: combining with lying/seated bases (whose root is already tilted) is out of scope. - Looping: the timeline loops base → phases → base;
repeat is the rep 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 active grip.
-Start poses: neutral, standing, plank, supine (face-up), prone (face-down), seated (long-sit on the floor).
+Start poses: neutral, standing, first-position (ballet turnout), 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. Selected limb-vs-body and body-vs-prop penetrations are reduced with sampled, bounded correction passes; this is not comprehensive collision detection or a physics simulation. Two-person/dual-IK and figure-vs-figure collision remain deferred (research §5.2, §6.2).
6. Intermediate Representation (IR)
diff --git a/playground/src/landing.css b/playground/src/landing.css
index 9adf25b..7678290 100644
--- a/playground/src/landing.css
+++ b/playground/src/landing.css
@@ -775,8 +775,12 @@ a.dev-card:hover {
.studio-bar { background: #0d0f10; }
.studio-bar .dots { display: none; }
.code-chip {
- right: -28px;
- bottom: -30px;
+ position: static;
+ width: max-content;
+ max-width: 100%;
+ box-sizing: border-box;
+ margin: 14px 0 0 auto;
+ overflow-x: auto;
border-radius: 2px;
background: #0b0d0e;
backdrop-filter: none;
diff --git a/spec/SPEC.md b/spec/SPEC.md
index 54ccba8..e3e5f9e 100644
--- a/spec/SPEC.md
+++ b/spec/SPEC.md
@@ -28,7 +28,7 @@ directive = rig | prop | pose | clip | step | repeat ;
rig = "rig" "humanoid" ;
prop = "prop" ("chair"|"wall"|"bar"|"box"|"dip-bars") ;
pose = "pose" "start" "=" startPose ;
-startPose = "neutral"|"standing"|"plank"|"supine"|"prone"|"seated" ;
+startPose = "neutral"|"standing"|"first-position"|"plank"|"supine"|"prone"|"seated" ;
clip = "clip" STRING ; (* optional mocap clip; renderer may retarget & blend *)
repeat = "repeat" NUMBER ;
step = "step" STRING DURATION timingMode ":" { child } ;
@@ -258,8 +258,9 @@ 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 active grip.
-**Start poses:** `neutral`, `standing`, `plank`, `supine` (face-up), `prone`
-(face-down), `seated` (long-sit on the floor).
+**Start poses:** `neutral`, `standing`, `first-position` (ballet turnout),
+`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
diff --git a/spec/examples/demi-plie.posecode b/spec/examples/demi-plie.posecode
index c0e7e0f..b1a35aa 100644
--- a/spec/examples/demi-plie.posecode
+++ b/spec/examples/demi-plie.posecode
@@ -1,27 +1,27 @@
posecode exercise "Demi-plié"
rig humanoid
- pose start = standing
+ pose start = first-position
step "Plié" 2.2s settle:
- hips: flex 20
- hips: rotate-out 30
+ hips: flex 31.5
+ hips: abduct 17
knees: flex 55
ankles: dorsiflex 15
shoulders: abduct 70
elbows: flex 15
spine: extend 4
ground-lock: feet
- cue "Turn out from the hips and bend the knees over the toes: heels down"
+ cue "Lower straight down with the knees tracking the turned-out toes and both heels anchored"
step "Straighten" 2.2s settle:
hips: flex 0
- hips: rotate-out 0
+ hips: abduct 0
knees: flex 0
- ankles: plantarflex 0
+ ankles: dorsiflex 0
shoulders: abduct 0
elbows: flex 0
- spine: flex 0
+ spine: extend 0
ground-lock: feet
- cue "Press the floor away and rise to a tall first position"
+ cue "Press the floor away and rise without changing the turnout or moving the feet"
repeat 4
diff --git a/spec/examples/superhero-landing.posecode b/spec/examples/superhero-landing.posecode
index 8a930b2..d091e60 100644
--- a/spec/examples/superhero-landing.posecode
+++ b/spec/examples/superhero-landing.posecode
@@ -10,12 +10,12 @@ posecode posture "Superhero Three-Point Landing"
knee_right: flex 123
ankle_right: dorsiflex 15
hip_left: extend 12
- knee_left: flex 105
+ knee_left: flex 110
ankle_left: plantarflex 28
shoulder_left: flex 98
shoulder_left: abduct 2
elbow_left: flex 8
- elbow_left: supinate 80
+ elbow_left: pronate 80
fingers_left: flex 80
shoulder_right: extend 24
shoulder_right: abduct 22
@@ -27,14 +27,14 @@ posecode posture "Superhero Three-Point Landing"
step "Make three-point contact" 0.3s settle:
neck: extend 25
- pin: knee_left floor
- reach: foot_right floor
+ ground-lock: foot_right
+ reach: knee_left floor
reach: fist_left floor
- cue "Set the left knee and left fist on the floor beside the planted right foot"
+ cue "Keep the right foot planted as the left knee and left fist settle onto the floor"
step "Hold the landing" 0.8s linear:
- pin: knee_left floor
- reach: foot_right floor
+ ground-lock: foot_right
+ reach: knee_left floor
reach: fist_left floor
cue "Hold the three contacts with the free right arm swept behind you"
diff --git a/spec/llm-authoring.md b/spec/llm-authoring.md
index 4ae555b..4f4e02e 100644
--- a/spec/llm-authoring.md
+++ b/spec/llm-authoring.md
@@ -27,7 +27,7 @@ beginning `Posecode cannot yet represent...` and name the missing capability.
posecode "" # kind = exercise | stretch | posture
rig humanoid
prop # optional: chair | wall | bar | box | dip-bars (repeatable)
- pose start = # neutral | standing | plank | supine | prone | seated
+ pose start = # neutral | standing | first-position | plank | supine | prone | seated
step "" : # mode = flow | settle | drive | snap | linear
:
reach: # limb IK to a landmark, floor, or declared prop anchor
@@ -229,6 +229,8 @@ posecode exercise "Body-weight hip hinge"
prop contact. It assigns separate left/right anchors, solves both arms, and
closes the fingers. Declare `prop bar` or `prop dip-bars` first. Prefer this
to multiple pins for a pull-up, hang, or dip.
+- **Dance turnout**: `pose start = first-position` for ballet movements that
+ must keep the legs externally rotated without twisting planted feet
- **Lying / seated**: `pose start = supine | prone | seated` for floor and mat
work (glute bridge, dead bug, cobra, seated forward fold). In a supine
exercise whose torso stays down, add `ground-lock: back` to each phase.
From 8b1108447da6b6b95fb5c387e9ee3d4eab1e7332 Mon Sep 17 00:00:00 2001
From: a-baran-orhan
Date: Fri, 17 Jul 2026 22:22:17 +0300
Subject: [PATCH 2/2] Add movement polish changeset
---
.changeset/calm-feet-land.md | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 .changeset/calm-feet-land.md
diff --git a/.changeset/calm-feet-land.md b/.changeset/calm-feet-land.md
new file mode 100644
index 0000000..047b377
--- /dev/null
+++ b/.changeset/calm-feet-land.md
@@ -0,0 +1,8 @@
+---
+"posecode-parser": patch
+"posecode-render": patch
+"posecode-mcp": patch
+---
+
+Add a ballet first-position start pose, keep superhero landing contacts stable
+with an inward planted fist, and prevent visible foot friction in demi-plié.