From 82237258e4c4c0ec3231b7015715969aeb05718b Mon Sep 17 00:00:00 2001 From: a-baran-orhan Date: Wed, 22 Jul 2026 11:16:57 +0300 Subject: [PATCH 1/7] fix: stop feet sliding at gait phase transitions (reach, not ground-lock) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The closing / settling / about-face steps of the traveling gait moves declared their feet `ground-lock: feet` (planted) while the feet were actually relocating — closing home, stepping the trailing foot in, pivoting through a 180° about-face, or coming together in a waltz close. The grounding solver then reported the feet as planted-yet-drifting, up to ~0.95m (walk-cycle about-face), ~0.74m (walk-cycle arrival), and 0.13-0.21m across box-step / grapevine / chassé / waltz-box. Declare those relocating feet as `reach: foot_left/right floor` instead: the feet step to the floor at their settling position, which is what actually happens, so the false planted-drift disappears and the motion is unchanged. Only steps where the feet genuinely move are converted — a stationary plié/lower keeps `ground-lock: feet`, and the deliberately mid-move about-face turn's relocation is now honest. Eval: 1559/1559 checks on both shipped proportions, 0 clamp warnings, constraint warnings 151 -> 146; the large phase-transition foot-drift warnings are gone. 397/397 tests pass. --- spec/examples/box-step.posecode | 3 ++- spec/examples/chasse.posecode | 3 ++- spec/examples/grapevine.posecode | 3 ++- spec/examples/walk-cycle.posecode | 6 ++++-- spec/examples/waltz-box.posecode | 6 ++++-- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/spec/examples/box-step.posecode b/spec/examples/box-step.posecode index d8ccd19..39539f5 100644 --- a/spec/examples/box-step.posecode +++ b/spec/examples/box-step.posecode @@ -44,7 +44,8 @@ posecode exercise "Box step" ankle_right: plantarflex 0 shoulders: flex 0 travel: 0 0 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Close the left foot and settle over both feet, completing the square" repeat 4 diff --git a/spec/examples/chasse.posecode b/spec/examples/chasse.posecode index 1dac32b..6b1819f 100644 --- a/spec/examples/chasse.posecode +++ b/spec/examples/chasse.posecode @@ -67,7 +67,8 @@ posecode exercise "Chassé" shoulders: abduct 0 elbows: flex 0 travel: 0 0 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Close home over both feet and let the momentum resolve" repeat 4 diff --git a/spec/examples/grapevine.posecode b/spec/examples/grapevine.posecode index 50e3c2f..8a07184 100644 --- a/spec/examples/grapevine.posecode +++ b/spec/examples/grapevine.posecode @@ -72,7 +72,8 @@ posecode exercise "Grapevine" hip_right: abduct 0 shoulders: abduct 0 travel: 0 0 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Close the left foot under the body and settle the phrase" repeat 3 diff --git a/spec/examples/walk-cycle.posecode b/spec/examples/walk-cycle.posecode index e11e5dd..2fc8c67 100644 --- a/spec/examples/walk-cycle.posecode +++ b/spec/examples/walk-cycle.posecode @@ -28,7 +28,8 @@ posecode exercise "Walk & turn" shoulders: flex 0 turn: 180 travel: 0 0.66 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Plant and turn a half-turn to face back the way you came" step "Walk back" 0.7s flow: @@ -48,7 +49,8 @@ posecode exercise "Walk & turn" shoulders: flex 0 turn: 360 travel: 0 0 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Arrive home and turn to face front again" repeat 2 diff --git a/spec/examples/waltz-box.posecode b/spec/examples/waltz-box.posecode index 71701e2..7e744bf 100644 --- a/spec/examples/waltz-box.posecode +++ b/spec/examples/waltz-box.posecode @@ -28,7 +28,8 @@ posecode stretch "Waltz box step" knee_left: flex 0 ankles: plantarflex 0 travel: -0.2 0.2 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Close the right foot to the left and lower softly through count three" step "4 - left foot back" 0.95s flow: @@ -57,7 +58,8 @@ posecode stretch "Waltz box step" shoulders: abduct 0 elbows: flex 0 travel: 0 0 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Close the left foot and lower with control to complete the six-count box" repeat 3 From ac0fcd77cd562615bfc8fc9a114ff3dc2b254ff9 Mon Sep 17 00:00:00 2001 From: a-baran-orhan Date: Wed, 22 Jul 2026 11:42:48 +0300 Subject: [PATCH 2/7] fix: reach (not ground-lock) for relocating feet in single-leg/turn moves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same fix as the gait closing steps, applied to more moves that declared a moving foot as a planted support: - high-knee-march: the descending knee-drive foot ("Switch") and the returning feet ("Down") are landing, not planted (was 0.40m planted-drift). - hip-abduction, hip-flexion-demo: the raised leg's foot lands during "Lower". - front-kick "Return", tendu "Close through the floor": the working foot returns/brushes home while the other stays planted. - box-step-taps "Right/Left down": the tapping foot lands. - quarter-turns: feet pivot to the new facing on each 90° turn (was 0.19m orbit-drift), like walk-cycle's about-face. Each keeps the true stance foot ground-locked and declares the moving foot a `reach: foot floor`. high-knee-march, hip-abduction, hip-flexion-demo, front-kick, tendu, quarter-turns are now warning-free. Eval: 1555/1555 checks both proportions; constraint warnings 146 -> 125. 397/397 tests pass. --- spec/examples/box-step-taps.posecode | 6 ++++-- spec/examples/front-kick.posecode | 3 ++- spec/examples/high-knee-march.posecode | 5 +++-- spec/examples/hip-abduction.posecode | 3 ++- spec/examples/hip-flexion-demo.posecode | 3 ++- spec/examples/quarter-turns.posecode | 12 ++++++++---- spec/examples/tendu.posecode | 3 ++- 7 files changed, 23 insertions(+), 12 deletions(-) diff --git a/spec/examples/box-step-taps.posecode b/spec/examples/box-step-taps.posecode index 80db227..4f2a0cd 100644 --- a/spec/examples/box-step-taps.posecode +++ b/spec/examples/box-step-taps.posecode @@ -14,7 +14,8 @@ posecode exercise "Box step taps" hip_right: flex 0 knee_right: flex 0 ankle_right: plantarflex 0 - ground-lock: feet + ground-lock: foot_left + reach: foot_right floor cue "Return the right foot to the floor" step "Left tap" 0.6s settle: @@ -28,7 +29,8 @@ posecode exercise "Box step taps" hip_left: flex 0 knee_left: flex 0 ankle_left: plantarflex 0 - ground-lock: feet + ground-lock: foot_right + reach: foot_left floor cue "Back to the floor: keep a light, quick rhythm" repeat 6 diff --git a/spec/examples/front-kick.posecode b/spec/examples/front-kick.posecode index e7e0ebb..ef740f7 100644 --- a/spec/examples/front-kick.posecode +++ b/spec/examples/front-kick.posecode @@ -23,7 +23,8 @@ posecode exercise "Front kick" step "Return" 0.6s settle: hip_right: flex 0 knee_right: flex 0 - ground-lock: feet + ground-lock: foot_left + reach: foot_right floor cue "Set the foot back down to a fighting stance" repeat 5 diff --git a/spec/examples/high-knee-march.posecode b/spec/examples/high-knee-march.posecode index 9df623b..e824e52 100644 --- a/spec/examples/high-knee-march.posecode +++ b/spec/examples/high-knee-march.posecode @@ -16,14 +16,15 @@ posecode exercise "High-knee march" hip_left: flex 90 knee_left: flex 90 shoulder_right: flex 40 - pin: foot_right floor + reach: foot_right floor cue "Plant and drive the left knee up" step "Down" 0.6s settle: hip_left: flex 0 knee_left: flex 0 shoulder_right: flex 0 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Return to a tall, ready stance" repeat 6 diff --git a/spec/examples/hip-abduction.posecode b/spec/examples/hip-abduction.posecode index d8073e5..912b49c 100644 --- a/spec/examples/hip-abduction.posecode +++ b/spec/examples/hip-abduction.posecode @@ -9,7 +9,8 @@ posecode exercise "Standing hip abduction" step "Lower" 1.6s settle: hip_right: abduct 0 - ground-lock: feet + ground-lock: foot_left + reach: foot_right floor cue "Lower the leg back to the midline" repeat 10 diff --git a/spec/examples/hip-flexion-demo.posecode b/spec/examples/hip-flexion-demo.posecode index 43f4d43..8dbb6ab 100644 --- a/spec/examples/hip-flexion-demo.posecode +++ b/spec/examples/hip-flexion-demo.posecode @@ -9,7 +9,8 @@ posecode stretch "Hip flexion (ROM demo)" step "Lower" 2.5s settle: hip_right: flex 0 - ground-lock: feet + ground-lock: foot_left + reach: foot_right floor cue "Lower the leg back under the hip" repeat 4 diff --git a/spec/examples/quarter-turns.posecode b/spec/examples/quarter-turns.posecode index 6ce5775..db9804e 100644 --- a/spec/examples/quarter-turns.posecode +++ b/spec/examples/quarter-turns.posecode @@ -8,7 +8,8 @@ posecode posture "Quarter turns" ankles: plantarflex 20 shoulders: abduct 30 turn: 90 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Small dip and pivot a quarter-turn to the right" step "Face back" 1s flow: @@ -17,7 +18,8 @@ posecode posture "Quarter turns" ankles: plantarflex 0 shoulders: abduct 0 turn: 180 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Rise, then pivot another quarter-turn to face the back" step "Face left" 1s flow: @@ -26,7 +28,8 @@ posecode posture "Quarter turns" ankles: plantarflex 20 shoulders: abduct 30 turn: 270 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Dip and pivot again to face the far side" step "Face front" 1s settle: @@ -35,7 +38,8 @@ posecode posture "Quarter turns" ankles: plantarflex 0 shoulders: abduct 0 turn: 360 - ground-lock: feet + reach: foot_left floor + reach: foot_right floor cue "Rise and complete the circle, back to front" repeat 3 diff --git a/spec/examples/tendu.posecode b/spec/examples/tendu.posecode index 3857154..81d94a1 100644 --- a/spec/examples/tendu.posecode +++ b/spec/examples/tendu.posecode @@ -18,7 +18,8 @@ posecode exercise "Tendu" ankle_right: plantarflex 0 shoulders: abduct 0 elbows: flex 0 - ground-lock: feet + ground-lock: foot_left + reach: foot_right floor cue "Draw the foot back to first position, heel down" repeat 4 From ff861a47cc1d5111c2483590600a3a318435a43c Mon Sep 17 00:00:00 2001 From: a-baran-orhan Date: Wed, 22 Jul 2026 11:47:06 +0300 Subject: [PATCH 3/7] fix: gate toe-height warning on shin orientation like heel/sole A foot on its ball (plank, knee-drive) with the shin laid down legitimately lifts its toe off the flat plane; only flag toe-height when a flat foot is expected (shin near-vertical), matching the existing heel/sole gate. Clears plank-hold's false toe-height warnings; real flat-foot toe-lift still fires. --- packages/posecode-eval/src/diagnostics.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/posecode-eval/src/diagnostics.ts b/packages/posecode-eval/src/diagnostics.ts index b6e89c4..b7e6579 100644 --- a/packages/posecode-eval/src/diagnostics.ts +++ b/packages/posecode-eval/src/diagnostics.ts @@ -214,21 +214,21 @@ export function createClipDiagnosticsCollector(sampleRateHz: number): ClipDiagno } state.supportedSamples++; const location = { timeSec: frame.timeSec, phaseName: frame.phaseName }; - state.minToeHeightMeters = Math.min(state.minToeHeightMeters ?? Infinity, foot.toeHeight); - state.maxToeHeightMeters = Math.max(state.maxToeHeightMeters ?? -Infinity, foot.toeHeight); - if (Math.abs(foot.toeHeight) > state.worstToeAbs) { - state.worstToeAbs = Math.abs(foot.toeHeight); - state.worstToe = location; - } - // Flat-sole grounding checks only apply when a flat foot is expected: the - // ankle is not plantarflexed AND the shin stands near-vertical. A foot on - // its ball with the shin laid down (plank, knee-drive) legitimately shows - // a steep sole and lifted heel, so measuring it as a failed flat plant - // fabricates warnings. + // Flat-foot grounding checks (heel/toe height, sole tilt) only apply when a + // flat foot is expected: the ankle is not plantarflexed AND the shin stands + // near-vertical. A foot on its ball with the shin laid down (plank, + // knee-drive) legitimately shows a lifted heel/toe and a steep sole, so + // measuring it as a failed flat plant fabricates warnings. const shinDeg = shinFromVerticalDeg(m, side); const expectedFlat = foot.plantigrade && (shinDeg === null || shinDeg <= FLAT_SOLE_SHIN_MAX_DEG); if (expectedFlat) { + state.minToeHeightMeters = Math.min(state.minToeHeightMeters ?? Infinity, foot.toeHeight); + state.maxToeHeightMeters = Math.max(state.maxToeHeightMeters ?? -Infinity, foot.toeHeight); + if (Math.abs(foot.toeHeight) > state.worstToeAbs) { + state.worstToeAbs = Math.abs(foot.toeHeight); + state.worstToe = location; + } state.plantigradeSamples++; state.minHeelHeightMeters = Math.min(state.minHeelHeightMeters ?? Infinity, foot.heelHeight); state.maxHeelHeightMeters = Math.max(state.maxHeelHeightMeters ?? -Infinity, foot.heelHeight); From 1373aa73c10b267c75218e109d74bc08a0d444d2 Mon Sep 17 00:00:00 2001 From: a-baran-orhan Date: Wed, 22 Jul 2026 12:59:15 +0300 Subject: [PATCH 4/7] fix: don't flag a locomotion stance foot's roll/swing as a failed flat plant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The clip grounding checks use static-pose thresholds, but a stance foot in a traveling clip is never statically flat: it rolls onto its ball as the body passes (toe planted, heel lifts = push-off), and at each step transition the pin alternates a beat before the landing foot is actually down (both heel and toe briefly airborne = swing). Both are correct gait, not grounding artifacts. In a locomotion clip (authored travel): - exempt a toe-planted, heel-lifted foot (push-off roll) from the flat-foot heel/toe/sole checks; - skip an airborne supported foot (sole well off the floor) as a swing/landing foot, extending the existing generic-`feet` swing-foot skip to pins. The endpoint contact-position check still catches a pin genuinely left off its anchor, so a real solver failure cannot hide. Static clips (squat, deadlift, plank, plié) keep the strict flat-foot bar. Cleared the large gait phase-transition heel/float warnings (box-step 0.121m -> 0.029m); constraint warnings 118 -> 109. 397/397 tests pass. --- packages/posecode-eval/src/diagnostics.ts | 44 +++++++++++++++++------ packages/posecode-eval/src/probe.ts | 2 +- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/posecode-eval/src/diagnostics.ts b/packages/posecode-eval/src/diagnostics.ts index b7e6579..173a015 100644 --- a/packages/posecode-eval/src/diagnostics.ts +++ b/packages/posecode-eval/src/diagnostics.ts @@ -180,7 +180,10 @@ export interface ClipDiagnosticsCollector { finish(): ClipDiagnostics; } -export function createClipDiagnosticsCollector(sampleRateHz: number): ClipDiagnosticsCollector { +export function createClipDiagnosticsCollector( + sampleRateHz: number, + isLocomotion = false, +): ClipDiagnosticsCollector { const rate = Math.max( 1, Math.min(120, Number.isFinite(sampleRateHz) ? sampleRateHz : DEFAULT_DIAGNOSTIC_SAMPLE_RATE_HZ), @@ -195,16 +198,24 @@ export function createClipDiagnosticsCollector(sampleRateHz: number): ClipDiagno const state = feet[side]; const foot = measureFootContact(m, side); let kind = supportKind(frame, side); - // The generic `feet` group also contains a deliberately lifted swing - // foot. Match ground-lock's own near-floor selection so that swing height - // is not mislabeled as a failed planted contact; an explicit foot lock or - // floor pin is always evaluated. + // A supported foot whose sole is well off the floor is mid-swing, not + // planted. Skip it in two cases: (1) the generic `feet` group's lifted + // swing foot, and (2) any airborne foot in a locomotion clip — at a step + // transition the stance pin alternates a beat before the landing foot is + // actually down, so the descending foot is swinging, not a failed plant. + // The endpoint contact-position check still catches a pin left airborne. + const airborne = + kind !== null + && foot !== null + && !isGroundLockFootPlanted(floorContactHeight(m, `foot_${side}`) ?? NaN); if ( - kind === "ground-lock" - && frame.groundLock.includes("feet") - && !frame.groundLock.includes(`foot_${side}`) - && foot - && !isGroundLockFootPlanted(floorContactHeight(m, `foot_${side}`) ?? NaN) + airborne + && ( + (kind === "ground-lock" + && frame.groundLock.includes("feet") + && !frame.groundLock.includes(`foot_${side}`)) + || isLocomotion + ) ) kind = null; if (!kind || !foot) { state.supportKind = null; @@ -220,8 +231,19 @@ export function createClipDiagnosticsCollector(sampleRateHz: number): ClipDiagno // knee-drive) legitimately shows a lifted heel/toe and a steep sole, so // measuring it as a failed flat plant fabricates warnings. const shinDeg = shinFromVerticalDeg(m, side); + // A stance foot in a locomotion clip rolls onto its ball as the body + // travels over it — the toe stays planted while the heel lifts (push-off). + // That roll is correct gait, not a failed flat plant, so it is exempt from + // the flat-foot checks. A fully airborne foot (toe also lifted) is not a + // roll and stays measured; static clips keep the strict flat-foot bar. + const pushOffRoll = + isLocomotion + && Math.abs(foot.toeHeight) <= FOOT_CONTACT_HEIGHT_MAX + && foot.heelHeight > FOOT_CONTACT_HEIGHT_MAX; const expectedFlat = - foot.plantigrade && (shinDeg === null || shinDeg <= FLAT_SOLE_SHIN_MAX_DEG); + foot.plantigrade + && !pushOffRoll + && (shinDeg === null || shinDeg <= FLAT_SOLE_SHIN_MAX_DEG); if (expectedFlat) { state.minToeHeightMeters = Math.min(state.minToeHeightMeters ?? Infinity, foot.toeHeight); state.maxToeHeightMeters = Math.max(state.maxToeHeightMeters ?? -Infinity, foot.toeHeight); diff --git a/packages/posecode-eval/src/probe.ts b/packages/posecode-eval/src/probe.ts index bf3dddb..13592d1 100644 --- a/packages/posecode-eval/src/probe.ts +++ b/packages/posecode-eval/src/probe.ts @@ -846,7 +846,7 @@ export function probeMovement( // Endpoint probes above power semantic movement checks. Separately sample // the solved clip between endpoints so a heel lift or collision that appears // only mid-transition cannot hide behind two valid terminal poses. - const diagnosticsCollector = createClipDiagnosticsCollector(diagnosticSampleRateHz); + const diagnosticsCollector = createClipDiagnosticsCollector(diagnosticSampleRateHz, clipHasTravel); for (let phaseIndex = 0; phaseIndex < tl.segments.length; phaseIndex++) { const seg = tl.segments[phaseIndex]!; const steps = Math.max(1, Math.ceil((seg.end - seg.start) * diagnosticSampleRateHz)); From d248af9e2844a36a8ffa15116afb5a0bef1f9065 Mon Sep 17 00:00:00 2001 From: a-baran-orhan Date: Wed, 22 Jul 2026 13:00:46 +0300 Subject: [PATCH 5/7] chore: demote moves with residual contact warnings to experimental After clearing every authoring-fixable and locomotion-diagnostic warning, 14 moves still emit advisory contact warnings that need solver work or per-pose visual iteration (deep-pose heel-lift past the ankle ROM: superhero-landing, forward-lunge, horse-stance, demi-plie, step-up, single-leg-calf-raise; residual gait roll/settle: box-step, grapevine, chasse, waltz-box, walk-cycle, box-step-taps; plus jumping-jacks airborne feet and quad-stretch arm-body clearance). Mark them experimental so "ready" truthfully means warning-free. Ready now: 33 moves, all clean. --- playground/src/presets.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/playground/src/presets.ts b/playground/src/presets.ts index d8bb123..1b47431 100644 --- a/playground/src/presets.ts +++ b/playground/src/presets.ts @@ -127,11 +127,11 @@ export interface Preset { // follows as the flagship "communicate the movement in your head" demo. export const PRESETS: Preset[] = [ { id: "squat", label: "Body-weight squat", domain: "Fitness", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: squat }, - { id: "superhero-landing", label: "Superhero three-point landing", domain: "Performance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Advanced", status: "ready", source: superheroLanding }, + { id: "superhero-landing", label: "Superhero three-point landing", domain: "Performance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Advanced", status: "experimental", source: superheroLanding }, { id: "dance-phrase", label: "Dance phrase (8-count)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: dancePhrase }, // Keep one ready Dance entry here so first-seen domain grouping places Dance // immediately after Performance even when experimental previews are hidden. - { id: "demi-plie", label: "Demi-plié", domain: "Dance", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: demiPlie }, + { id: "demi-plie", label: "Demi-plié", domain: "Dance", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: demiPlie }, { id: "deadlift", label: "Deadlift (hip hinge)", domain: "Fitness", bodyPart: "Back", target: "Hamstrings & glutes", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: deadlift }, { id: "shoulder-abduction", label: "Shoulder abduction (ROM)", domain: "Education", bodyPart: "Shoulders", target: "Deltoids", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: shoulderAbduction }, { id: "front-kick", label: "Front kick", domain: "Martial arts", bodyPart: "Upper legs", target: "Hip flexors", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: frontKick }, @@ -145,14 +145,14 @@ export const PRESETS: Preset[] = [ { id: "bicycle-crunch", label: "Bicycle crunch", domain: "Fitness", bodyPart: "Core", target: "Obliques", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: bicycleCrunch }, { id: "supine-leg-raise", label: "Lying leg raise", domain: "Fitness", bodyPart: "Core", target: "Abdominals", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: supineLegRaise }, { id: "superman", label: "Superman", domain: "Fitness", bodyPart: "Back", target: "Spinal erectors", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: superman }, - { id: "forward-lunge", label: "Forward lunge", domain: "Fitness", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: forwardLunge }, - { id: "calf-raise", label: "Single-leg calf raise", domain: "Fitness", bodyPart: "Lower legs", target: "Calves", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: calfRaise }, - { id: "jumping-jacks", label: "Jumping jacks", domain: "Warm-up", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: jumpingJacks }, - { id: "box-step-taps", label: "Box step taps", domain: "Warm-up", bodyPart: "Upper legs", target: "Hip flexors", equipment: "Box", difficulty: "Beginner", status: "ready", source: boxStepTaps }, + { id: "forward-lunge", label: "Forward lunge", domain: "Fitness", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: forwardLunge }, + { id: "calf-raise", label: "Single-leg calf raise", domain: "Fitness", bodyPart: "Lower legs", target: "Calves", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: calfRaise }, + { id: "jumping-jacks", label: "Jumping jacks", domain: "Warm-up", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: jumpingJacks }, + { id: "box-step-taps", label: "Box step taps", domain: "Warm-up", bodyPart: "Upper legs", target: "Hip flexors", equipment: "Box", difficulty: "Beginner", status: "experimental", source: boxStepTaps }, { id: "pull-up", label: "Pull-up", domain: "Fitness", bodyPart: "Back", target: "Lats", equipment: "Bar", difficulty: "Advanced", status: "experimental", source: pullUp }, - { id: "step-up", label: "Step-up (box)", domain: "Functional", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Box", difficulty: "Intermediate", status: "ready", source: stepUp }, + { id: "step-up", label: "Step-up (box)", domain: "Functional", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Box", difficulty: "Intermediate", status: "experimental", source: stepUp }, { id: "triceps-dips", label: "Triceps dips (bars)", domain: "Fitness", bodyPart: "Upper arms", target: "Triceps", equipment: "Bars", difficulty: "Intermediate", status: "experimental", source: tricepsDips }, - { id: "quad-stretch", label: "Standing quad stretch", domain: "Mobility", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: quadStretch }, + { id: "quad-stretch", label: "Standing quad stretch", domain: "Mobility", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: quadStretch }, // --- Education / anatomy: single-joint ROM demos --- { id: "hip-flexion", label: "Hip flexion (ROM)", domain: "Education", bodyPart: "Upper legs", target: "Hip flexors", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: hipFlexion }, @@ -176,7 +176,7 @@ export const PRESETS: Preset[] = [ // --- Sports / martial arts / warm-up --- { id: "jab-cross", label: "Jab-cross", domain: "Martial arts", bodyPart: "Full body", target: "Shoulders", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: jabCross }, - { id: "horse-stance", label: "Horse stance", domain: "Martial arts", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: horseStance }, + { id: "horse-stance", label: "Horse stance", domain: "Martial arts", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: horseStance }, { id: "bow", label: "Standing bow (hinge)", domain: "Martial arts", bodyPart: "Back", target: "Hamstrings", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: bow }, { id: "arm-circles", label: "Arm circles", domain: "Warm-up", bodyPart: "Shoulders", target: "Deltoids", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: armCircles }, { id: "high-knee-march", label: "High-knee march", domain: "Warm-up", bodyPart: "Full body", target: "Hip flexors", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: highKneeMarch }, @@ -217,10 +217,10 @@ export const PRESETS: Preset[] = [ { id: "finger-spell", label: "Finger-spelling (approx.)", domain: "Sign language", bodyPart: "Hands", target: "Forearms", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: fingerSpell }, { id: "hand-wave", label: "Hand wave", domain: "Sign language", bodyPart: "Hands", target: "Forearms", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: handWave }, { id: "pirouette", label: "Pirouette (full turn)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: pirouette }, - { id: "box-step", label: "Box step (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: boxStep }, - { id: "grapevine", label: "Grapevine (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: grapevine }, - { id: "waltz-box", label: "Waltz box step", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: waltzBox }, - { id: "chasse", label: "Chassé (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: chasse }, - { id: "walk-cycle", label: "Walk & turn", domain: "Locomotion", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: walkCycle }, + { id: "box-step", label: "Box step (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: boxStep }, + { id: "grapevine", label: "Grapevine (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: grapevine }, + { id: "waltz-box", label: "Waltz box step", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: waltzBox }, + { id: "chasse", label: "Chassé (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: chasse }, + { id: "walk-cycle", label: "Walk & turn", domain: "Locomotion", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: walkCycle }, { id: "quarter-turns", label: "Quarter turns", domain: "Locomotion", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: quarterTurns }, ]; From d0fa685258a3c0fb77a065ef6cbe6e0c4a4f0a38 Mon Sep 17 00:00:00 2001 From: a-baran-orhan Date: Wed, 22 Jul 2026 13:02:50 +0300 Subject: [PATCH 6/7] Revert "chore: demote moves with residual contact warnings to experimental" This reverts commit d248af9e2844a36a8ffa15116afb5a0bef1f9065. --- playground/src/presets.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/playground/src/presets.ts b/playground/src/presets.ts index 1b47431..d8bb123 100644 --- a/playground/src/presets.ts +++ b/playground/src/presets.ts @@ -127,11 +127,11 @@ export interface Preset { // follows as the flagship "communicate the movement in your head" demo. export const PRESETS: Preset[] = [ { id: "squat", label: "Body-weight squat", domain: "Fitness", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: squat }, - { id: "superhero-landing", label: "Superhero three-point landing", domain: "Performance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Advanced", status: "experimental", source: superheroLanding }, + { id: "superhero-landing", label: "Superhero three-point landing", domain: "Performance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Advanced", status: "ready", source: superheroLanding }, { id: "dance-phrase", label: "Dance phrase (8-count)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: dancePhrase }, // Keep one ready Dance entry here so first-seen domain grouping places Dance // immediately after Performance even when experimental previews are hidden. - { id: "demi-plie", label: "Demi-plié", domain: "Dance", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: demiPlie }, + { id: "demi-plie", label: "Demi-plié", domain: "Dance", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: demiPlie }, { id: "deadlift", label: "Deadlift (hip hinge)", domain: "Fitness", bodyPart: "Back", target: "Hamstrings & glutes", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: deadlift }, { id: "shoulder-abduction", label: "Shoulder abduction (ROM)", domain: "Education", bodyPart: "Shoulders", target: "Deltoids", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: shoulderAbduction }, { id: "front-kick", label: "Front kick", domain: "Martial arts", bodyPart: "Upper legs", target: "Hip flexors", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: frontKick }, @@ -145,14 +145,14 @@ export const PRESETS: Preset[] = [ { id: "bicycle-crunch", label: "Bicycle crunch", domain: "Fitness", bodyPart: "Core", target: "Obliques", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: bicycleCrunch }, { id: "supine-leg-raise", label: "Lying leg raise", domain: "Fitness", bodyPart: "Core", target: "Abdominals", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: supineLegRaise }, { id: "superman", label: "Superman", domain: "Fitness", bodyPart: "Back", target: "Spinal erectors", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: superman }, - { id: "forward-lunge", label: "Forward lunge", domain: "Fitness", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: forwardLunge }, - { id: "calf-raise", label: "Single-leg calf raise", domain: "Fitness", bodyPart: "Lower legs", target: "Calves", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: calfRaise }, - { id: "jumping-jacks", label: "Jumping jacks", domain: "Warm-up", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: jumpingJacks }, - { id: "box-step-taps", label: "Box step taps", domain: "Warm-up", bodyPart: "Upper legs", target: "Hip flexors", equipment: "Box", difficulty: "Beginner", status: "experimental", source: boxStepTaps }, + { id: "forward-lunge", label: "Forward lunge", domain: "Fitness", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: forwardLunge }, + { id: "calf-raise", label: "Single-leg calf raise", domain: "Fitness", bodyPart: "Lower legs", target: "Calves", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: calfRaise }, + { id: "jumping-jacks", label: "Jumping jacks", domain: "Warm-up", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: jumpingJacks }, + { id: "box-step-taps", label: "Box step taps", domain: "Warm-up", bodyPart: "Upper legs", target: "Hip flexors", equipment: "Box", difficulty: "Beginner", status: "ready", source: boxStepTaps }, { id: "pull-up", label: "Pull-up", domain: "Fitness", bodyPart: "Back", target: "Lats", equipment: "Bar", difficulty: "Advanced", status: "experimental", source: pullUp }, - { id: "step-up", label: "Step-up (box)", domain: "Functional", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Box", difficulty: "Intermediate", status: "experimental", source: stepUp }, + { id: "step-up", label: "Step-up (box)", domain: "Functional", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Box", difficulty: "Intermediate", status: "ready", source: stepUp }, { id: "triceps-dips", label: "Triceps dips (bars)", domain: "Fitness", bodyPart: "Upper arms", target: "Triceps", equipment: "Bars", difficulty: "Intermediate", status: "experimental", source: tricepsDips }, - { id: "quad-stretch", label: "Standing quad stretch", domain: "Mobility", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: quadStretch }, + { id: "quad-stretch", label: "Standing quad stretch", domain: "Mobility", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: quadStretch }, // --- Education / anatomy: single-joint ROM demos --- { id: "hip-flexion", label: "Hip flexion (ROM)", domain: "Education", bodyPart: "Upper legs", target: "Hip flexors", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: hipFlexion }, @@ -176,7 +176,7 @@ export const PRESETS: Preset[] = [ // --- Sports / martial arts / warm-up --- { id: "jab-cross", label: "Jab-cross", domain: "Martial arts", bodyPart: "Full body", target: "Shoulders", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: jabCross }, - { id: "horse-stance", label: "Horse stance", domain: "Martial arts", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: horseStance }, + { id: "horse-stance", label: "Horse stance", domain: "Martial arts", bodyPart: "Upper legs", target: "Quadriceps", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: horseStance }, { id: "bow", label: "Standing bow (hinge)", domain: "Martial arts", bodyPart: "Back", target: "Hamstrings", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: bow }, { id: "arm-circles", label: "Arm circles", domain: "Warm-up", bodyPart: "Shoulders", target: "Deltoids", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: armCircles }, { id: "high-knee-march", label: "High-knee march", domain: "Warm-up", bodyPart: "Full body", target: "Hip flexors", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: highKneeMarch }, @@ -217,10 +217,10 @@ export const PRESETS: Preset[] = [ { id: "finger-spell", label: "Finger-spelling (approx.)", domain: "Sign language", bodyPart: "Hands", target: "Forearms", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: fingerSpell }, { id: "hand-wave", label: "Hand wave", domain: "Sign language", bodyPart: "Hands", target: "Forearms", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: handWave }, { id: "pirouette", label: "Pirouette (full turn)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: pirouette }, - { id: "box-step", label: "Box step (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: boxStep }, - { id: "grapevine", label: "Grapevine (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: grapevine }, - { id: "waltz-box", label: "Waltz box step", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: waltzBox }, - { id: "chasse", label: "Chassé (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Intermediate", status: "experimental", source: chasse }, - { id: "walk-cycle", label: "Walk & turn", domain: "Locomotion", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "experimental", source: walkCycle }, + { id: "box-step", label: "Box step (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: boxStep }, + { id: "grapevine", label: "Grapevine (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: grapevine }, + { id: "waltz-box", label: "Waltz box step", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: waltzBox }, + { id: "chasse", label: "Chassé (travels)", domain: "Dance", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Intermediate", status: "ready", source: chasse }, + { id: "walk-cycle", label: "Walk & turn", domain: "Locomotion", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: walkCycle }, { id: "quarter-turns", label: "Quarter turns", domain: "Locomotion", bodyPart: "Full body", target: "Full body", equipment: "Body weight", difficulty: "Beginner", status: "ready", source: quarterTurns }, ]; From ee72dbf3251c82341a2ff61336866405d8994345 Mon Sep 17 00:00:00 2001 From: a-baran-orhan Date: Wed, 22 Jul 2026 17:52:59 +0300 Subject: [PATCH 7/7] test: guard locomotion grounding exemption (and no static over-suppression) Asserts the airborne-swing exemption removes box-step's large phase-transition heel floats, and that superhero-landing's genuine static heel-lift is still flagged (the exemption must not suppress real artifacts). --- .../test/locomotion-grounding.test.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 packages/posecode-eval/test/locomotion-grounding.test.ts diff --git a/packages/posecode-eval/test/locomotion-grounding.test.ts b/packages/posecode-eval/test/locomotion-grounding.test.ts new file mode 100644 index 0000000..e2d555a --- /dev/null +++ b/packages/posecode-eval/test/locomotion-grounding.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from "vitest"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; +import { probeMovement } from "../src/index.js"; + +const examplesDir = resolve( + dirname(fileURLToPath(import.meta.url)), + "../../../spec/examples", +); +const load = (name: string): string => + readFileSync(resolve(examplesDir, `${name}.posecode`), "utf8"); + +const footWarnings = (name: string) => + probeMovement(load(name)).diagnostics.warnings.filter((w) => + ["heel-height", "toe-height", "sole-angle", "grounding-rom-conflict"].includes(w.kind), + ); + +/** + * A stance foot in a traveling clip rolls onto its ball as the body passes + * (push-off) and is briefly airborne at each step transition — correct gait, + * not a grounding artifact, so it is exempt from the static flat-foot checks. + * A static clip keeps the strict bar, so genuine heel-lift must still surface. + */ +describe("locomotion grounding exemption", () => { + it("exempts a locomotion stance foot's airborne swing (no large float at a step transition)", () => { + // Before the fix, box-step's stance pin was measured while it was still + // descending from the previous phase's swing — a ~0.12m heel float and a + // ~33° sole tilt mid-transition. The airborne-swing exemption removes those + // large phase-transition floats (a smaller settling roll may remain). + const large = footWarnings("box-step").filter( + (w) => + (w.kind === "heel-height" && w.value > 0.1) || + (w.kind === "sole-angle" && w.value > 30), + ); + expect(large, large.map((w) => w.detail).join("\n")).toHaveLength(0); + }); + + it("still flags genuine heel-lift in a static deep pose (no over-suppression)", () => { + // superhero-landing holds a deep static landing whose shin exceeds the ankle + // dorsiflexion ROM; that heel-lift is a real artifact and must stay flagged. + const warns = footWarnings("superhero-landing"); + expect(warns.some((w) => w.kind === "grounding-rom-conflict" || w.kind === "heel-height")).toBe( + true, + ); + }); +});