git:20260624.65d9d78 to git:20260708.299a322

1 added, 1 removed. Audit A to A.

---
name: kernelcad-kinematic
description: Use when verifying whether a moving assembly is buildable — sampled-pose collision sweeps across joint ranges, IK reachability for end-effector targets, mounting-hole fastener consistency, and static-load capacity on cantilever-shaped parts. Loads alongside kernelcad-authoring to gate design-time mechanism feasibility.
---
# kernelcad-kinematic — feasibility gates for moving assemblies
## Units (read this first)
Joint angles throughout the kinematic API are **degrees for revolute joints**
and **millimetres for prismatic joints**. This matches the unit convention
used by `arm.mate(..., 'revolute', { limitsDeg })`, `arm.mate(..., 'prismatic', { limitsMm })`,
and `arm.solvedModel({poses})` — there is no degree-vs-radian split anywhere
on the user-facing surface.
That includes the IK seed: `kinematic.checkReachable({ seed: { shoulder: 60 } })`
means 60 degrees, not 60 radians and not 60 of anything else. Authors porting
code from URDF / MoveIt / ROS — where radians are conventional — must convert
(`deg = rad * 180 / Math.PI`) before passing values to this API.
A small seed value like `0.3` is interpreted as 0.3°, which is effectively
zero — fine as a "near rest pose" hint, but not the ~17° you'd get if you
were thinking in radians.
## When to load
Load this skill whenever the user asks any of:
- "Will this arm hit itself?" / "Does this mechanism clear across its motion?"
- "Can this robot reach this point?" / "Is this target in the workspace?"
- "Will these mounting holes line up?" / "Are the bolt patterns compatible?"
- "Will this bracket hold this load?" / "What's the safety factor?"
Do NOT load this skill for static CAD authoring (use `kernelcad-authoring`) or
for visualization (use the renderer). This skill is feasibility-checking only.
## The 4 entry points
All four live under the `kinematic.*` namespace exposed to every `.kcad.ts`
script. The same surface is reachable from host code via `import * as kinematic from
'src/kinematic'`. Every entry returns a `Promise<…Result>` envelope carrying
`source: 'local'`.
| Call | Question answered | Key diagnostics |
|------|-------------------|-----------------|
| `kinematic.checkMountingHoleConsistency(arm)` | Do fastener holes match across all mates? | `kinematic.mounting-hole.diameter-mismatch` (K9) |
| `kinematic.checkSweptCollision(arm, opts)` | Does any link pair penetrate across the swept range? | `kinematic.collision.swept` (K1), `kinematic.collision.swept.sample-density-warning` (K2) |
| `kinematic.checkReachable(arm, opts)` | Can the end-effector reach this target? | `kinematic.unreachable` (K3, axis-discriminated), `kinematic.reachability.iteration-cap-hit` (K4), `kinematic.solver.unsupported-config` (K5) |
| `kinematic.checkLoadCapacity(arm, loads, opts)` | Will any beam-shaped element exceed yield under these loads? | `kinematic.load-exceeds-yield` (K6), `kinematic.load.beam-not-applicable` (K7), `kinematic.no-material-declared` (K8) |
## MCP tools (one per facade entry)
Every facade entry has a paired MCP tool that accepts the same shape from an
agent context. The tools load the `.kcad.ts` source, run it, capture the
named assembly off the script's session, and dispatch to the facade.
- `verify({ check: 'mounting-holes' })` — wraps the facade; accepts `file` or `code`
- `verify({ check: 'swept-collision' })` — wraps the facade; accepts `file|code`, `joint`, `range`, `collision_tolerance_mm3`
- `verify({ check: 'reachable' })` — wraps the facade; accepts `file|code`, `tip_link`, `target_position`, `target_orientation`, `prefer_solver`, `max_iterations`, `seed`
- `verify({ check: 'load-capacity' })` — wraps the facade; accepts `file|code`, `loads`, `materials`, `mode`, `safety_factor_threshold`
- `verify({ check: 'assembly' })` (extended) — composes all four when called with `gates: ['kinematic']`
## Recovery loop — code → nextAction → repair
Every emitted diagnostic carries a structured `nextAction` field describing
the smallest repair likely to clear the gate. Use the table below to pick a
repair, edit the `.kcad.ts` source, then re-run the same `kinematic.check*`
call to confirm.
| Code | `nextAction.kind` | Typical recovery |
|------|------|----|
| K1 `kinematic.collision.swept` | `rewrite-feature` | Inspect `result.collidingPoses[]`; narrow joint limits OR reshape the colliding part OR insert clearance |
| K2 `kinematic.collision.swept.sample-density-warning` | `fix-arg` (`opts.range`) | Halve the step size OR widen the range |
| K3 `kinematic.unreachable` | conditional by `axis` | `axis: 'position' \| 'both'` → restructure (lengthen link, add DOF, move target). `axis: 'orientation'` → relax orientation tolerance or drop the orientation constraint |
| K4 `kinematic.reachability.iteration-cap-hit` | `fix-arg` (`opts.maxIterations`) | Bump iterations OR relax tolerances; inspect `closestApproach` to choose |
| K5 `kinematic.solver.unsupported-config` | `rewrite-feature` | Cut the closed-loop cycle OR switch `preferSolver: 'numeric'`; closed-loop kinematics is a separate slice |
| K6 `kinematic.load-exceeds-yield` | `rewrite-feature` | Thicken the cross-section, change material, shorten the moment arm |
| K7 `kinematic.load.beam-not-applicable` | `fix-arg` (`crossSection`) | Add the part's `crossSection` declaration; only beam-shaped parts qualify for the closed-form path |
| K8 `kinematic.no-material-declared` | `fix-arg` (`opts.materials`) | Add `materials: { partName: { material: 'steel' \| 'aluminum' \| 'pla' \| 'abs' \| 'pet' } }` for every loaded part |
| K9 `kinematic.mounting-hole.diameter-mismatch` | `fix-arg` | Set both connectors' hole diameter to the same value |
## Trade-off note on K2 sparse-sampling
K2 is a signal, not a hard cap. The sweep still ran end-to-end; the warning is
that the (range, step) you supplied falls below the safe floor (36 samples for
revolute, 25 for prismatic) and the sweep may have stepped over a narrow
collision window. Agents can ignore K2 for fast-prototyping iteration; for a
production design review, never ignore K2 — re-run with a tighter step.
## Boundary
This skill is feasibility-checking. CAD authoring stays on
`kernelcad-authoring`. Visualization stays on the renderer. To *fix* a flagged
mechanism, return to `kernelcad-authoring` to edit the `.kcad.ts` source, then
re-run the relevant `kinematic.check*` to confirm.
## Range-based vs timeline-based motion verification
Two interference checks cover motion, answering different questions — pick by
what you are validating:
- **Range-based swept collision (this skill)** —
`kinematic.checkSweptCollision(arm, opts)` / the `verify({ check: 'swept-collision' })` MCP
tool sweep ONE joint across its full `range` at a sample density and ask "is
any pose in this joint's whole envelope a collision?" Use it as a feasibility
gate while designing the mechanism — it does not care about timing, easing,
or how multiple joints move together.
- **Timeline-based animation verification (`kernelcad-authoring`)** —
`kernelcad animate` / the `capture_animation` MCP tool verify the specific
multi-track poses an `animationView({...})` timeline actually visits
(keyframe times + segment midpoints, eases and dwells included), then capture
the MP4/frames. Use it to confirm a particular authored MOTION (several joints
moving on a shared timeline) stays clear and to produce the motion artifact.
Both reuse the mechanism-validity 20 mm³ interference threshold and honor
declared `solvedModel({ ignore })` pairs, so a pose clean under one is judged by
the same rule under the other. Range-based proves the joint's envelope is safe;
timeline-based proves the choreographed cycle is safe and renders it.
## Non-robotics mechanism coverage
These four calls work on any moving assembly, not just robot arms:
- **Linkages** — 4-bar, scissor jacks (closed-loop → K5 fires; cut to an
open chain to use checkSweptCollision)
- **Latches** — over-center, pawl-ratchet (`checkSweptCollision` over the
latch handle; `checkReachable` for the locking-pin engagement target)
- **Hinges** — laptop clamshell, butterfly knife (`checkSweptCollision` over
the hinge angle)
- **Watch movements** — gear-train clearance, escapement engagement
(`checkSweptCollision` over the escape-wheel rotation; `checkLoadCapacity`
for the mainspring torque)
- **Scissor jacks** — `checkSweptCollision` over the lift parameter; the
closed-loop variant is rejected by K5 — author the open-chain leg instead
## Mechanism delivery — non-bypassable
A mechanism build is **not deliverable** if any of these fail. No `ignore[]` workarounds for joint pairs; no shipping with a render that looks right while the assembly is broken.
1. `kernelcad validate --include-interference` returns CLEAN (this flag also **auto-enables the physics gate**, item 5 — so a deliverable mechanism must hold under gravity too; run `--include-interference --no-include-physics` to assert the kinematic gate alone: interference + disconnect + joint-mesh). `ignore[]` is reserved for true intra-part design contacts (a spring "bolted" to a beam, a captured washer); joint-pair contacts (the parts on either side of a `revolute` / `prismatic` mate) **may not be ignored** — they are the test signal for whether the mechanism is physically realized.
- 2. Every declared mate passes Gate 6 (mate physical realization): the pin/equivalent feature actually constrains the two parts, the pin stays in both holes at every pose in the mate's limits, and bearing surfaces align. Surfaces an advisory `assembly.mate.not-physically-realized` (`info` severity; revolute / prismatic only; `fastened` mates are exempt). The merge gates under the physics-grounded loop are `mechanism.disconnect` and `mechanism.interpenetration`, which fire under motion at validate-time. `joint.clevis(...)` passes by construction.
+ 2. Every declared mate passes Gate 6 (mate physical realization): the pin/equivalent feature actually constrains the two parts, the pin stays in both holes at every pose in the mate's limits, and bearing surfaces align. Surfaces an advisory `assembly.mate.not-physically-realized` (`info` severity; revolute / prismatic only; `fastened` mates are exempt). The merge gates under the physics-grounded loop are `mechanism.disconnect` and `mechanism.interpenetration`, which fire under motion at validate-time. `joint.clevis(...)` passes by construction only when its returned geometry and connectors are actually used.
3. Every revolute joint passes Gate 4 (visual exposure): the hinge mechanism reads as a hinge from at least one canonical view.
4. The render-inspect loop is followed: a `kernelcad render inspect` pass after every geometry change, with visible issues called out.
5. (Opt-in) `kernelcad validate --include-interference --include-physics` exercises the MuJoCo-based physics gate (criteria 5+6). Adds two failure modes: `mechanism.unstable-under-gravity` (non-finite required torque at a sampled pose → singular kinematic configuration) and `mechanism.drops-on-release` (any joint drifts > 5° or any body translates > 50 mm in a 0.5 s drop-test from rest). Bare revolutes without a closed-loop spring / actuator fail this gate by construction — single-body "spring" parts fastened to one arm produce zero restoring moment around the joint they should brace. The closed-loop tendon API tracked in issue #361 is the kit-level fix.
If any of these fail, iterate the design until they pass. Do not widen `ignore[]`. Do not ship.
### Use `joint.clevis(...)` for revolute joints — do not hand-roll forks
A clevis joint is the canonical revolute-joint hardware: two fork plates on the parent, one tongue on the child, a pin drilled through both knuckles. Hand-rolling these from `box`/`cylinder`/`union` is the leading cause of "every gate green, mechanism falls apart" failures (knuckle alignment drifts, through-hole misaligned across plates, bridge tabs land in the tongue's swing volume).
Use the primitive instead:
```ts
import { joint } from 'kernelcad';
const shoulder = joint.clevis({
parentBody: basePart.shape,
childBody: lowerArmPart.shape,
axis: [0, -1, 0],
pivotParent: [0, 0, COLUMN_TOP_Z],
pivotChild: [0, 0, 0],
limitsDeg: [-10, 110],
style: { knuckleR: 14, forkGapY: 18, tongueY: 14, plateT: 4, pinR: 3.5 },
});
basePart.connector('shoulder', {
type: 'axis',
origin: { kind: 'vec3', value: shoulder.parentConnector.origin },
axis: shoulder.parentConnector.axis,
jointClearanceRadius: shoulder.parentConnector.clearanceRadius,
});
lowerArmPart.connector('shoulder', {
type: 'axis',
origin: { kind: 'vec3', value: shoulder.childConnector.origin },
axis: shoulder.childConnector.axis,
jointClearanceRadius: shoulder.childConnector.clearanceRadius,
});
arm.mate('shoulder', 'base.shoulder', 'lower-arm.shoulder', 'revolute', {
pose: shoulderDeg,
limitsDeg: [-10, 110],
});
```
The primitive returns the parent/child geometry to assign back to each part's `Shape` AND the connectors to bind the mate to. Do not pick `origin: [x, y, z]` by hand — bind to the returned connectors, which are kinematically consistent with the pin axis and the tongue/fork through-hole. Pass each connector's `clearanceRadius` through as `jointClearanceRadius`: the pin clearance bore is drilled through BOTH knuckles (an ISO 286 running fit — the pin floats in air, so pin-in-tongue shared volume is ~0), which means the joint-mesh-gap gate must know the pivot sits in a clearance bore rather than in solid. Omit it and the gate will false-flag the drilled knuckle as a `mechanism.joint-mesh-gap`.
Authoring discipline that PAIRS with the primitive:
- Author each part body so the user-supplied `pivotParent` (or the child's part-local origin) sits on (or just below) the body's surface, with clearance for the tongue's rotational sweep. For a vertical revolute axis, the parent body should terminate at least `knuckleR` below the pivot; for a horizontal beam-end joint, the beam should end `knuckleR + body_half_thickness` short of the pivot.
- Set `style.forkGapY > ARM_W` (the child's beam width) so the beam slips through the fork plates' gap without rubbing the inner faces.
- Set `style.tongueY ≈ ARM_W` so the tongue's plate thickness matches the beam — the tongue + beam form a continuous solid.
- For wider swing ranges, the primitive automatically lifts the pivot by `knuckleR · max(|sin|) + 1 mm`; you can opt out with `liftPivot: false` and lift manually.
Worked example: `examples/kinematic/luxo-lamp.kcad.ts` ships with `joint.clevis(...)` at all three revolute joints (shoulder, elbow, wrist) and validates clean under the **kinematic gate** — interference + disconnect + joint-mesh — with zero `ignore[]` entries. It does **not** pass the physics drop-test: the lamp's bare revolutes are braced only by single-body springs (zero joint moment), so the shoulder drifts ~5° under gravity and `mechanism.drops-on-release` fires (the closed-loop tendon fix is #361 — see item 5 above). Caveat on the invocation: the bare `kernelcad validate --include-interference` CLI flag **auto-enables `--include-physics`** (validate.ts defaults the physics tier on whenever interference is on), so that exact command returns `mechanism: broken` / exit 2 on this lamp. To run the kinematic gate alone — the gate this example clears — use `kernelcad validate --include-interference --no-include-physics` (verdict `mechanism: real`, exit 0).
## Cookbook
Six runnable snippets live in `cookbook/`. Each begins with a `// expected:`
header listing the diagnostic codes the run should emit. Snippets are
self-contained `.kcad.ts` files that build their own fixture, run one or two
`kinematic.check*` calls, and assert the expected outcome in-script with
`throw new Error(...)` so a regression fails fast under `kernelcad evaluate`.
1. `01-swept-collision-shoulder.kcad.ts` — 2-DOF arm; K1 fires across the
colliding band of the shoulder sweep
2. `02-reachable-with-seed.kcad.ts` — 6-DOF spherical-wrist arm reaching a
nearby target with a seed-pose hint
3. `03-cantilever-beam-stress.kcad.ts` — steel-vs-PLA cantilever beam stress
(steel passes; PLA fires K6)
4. `04-scissor-jack-swept.kcad.ts` — single-leg cut of a scissor jack swept
across the lift parameter (the closed-loop variant would emit K5)
5. `05-clamshell-hinge-swept.kcad.ts` — laptop-clamshell hinge across
[0°, 135°]; K1 fires when the lid touches the table
6. `06-over-center-latch-reachable.kcad.ts` — over-center latch
locking-pin reachability with self-collision avoidance