# Walker kinematics

Two or four legs on a rectangular hip base, for machines that carry their mass above the hips: the footfall schedule, the support polygon it leaves, and the hull attitude that is the only way such a machine can move its mass over a foot.

> For the complete index, see [llms.txt](https://robocn.dev/llms.txt). A Markdown version of any page is available by appending `.md` to its URL or by sending an `Accept: text/markdown` header.

## Install

```bash
bunx --bun shadcn@latest add https://robocn.dev/r/walker-kinematics.json
```

Registry item: `walker-kinematics` · [`https://robocn.dev/r/walker-kinematics.json`](https://robocn.dev/r/walker-kinematics.json)

## Notes

- The relation the family is built on: the mass is `hull` above the hip line, so a lateral offset costs `asin(offset / hull)` of roll and a fore-aft one the same in pitch, measured on the hull the roll already left. Roll and pitch are outputs. Past the stops the mass cannot reach the polygon at all, and the margin goes negative.
- The inverse of `tripod-kinematics`, which moves the body itself over its feet. A hull bolted to its hips cannot slide, so the same static condition has to be paid for with attitude — which is why a biped heaves over every step and a quadruped on a lateral-sequence walk hardly moves at all.
- Illustrative, not dynamics: the footfall pattern is a chosen schedule, each foot's share is that schedule weighted by how near the mass ended up rather than a ground-reaction solve, and there is no mass, inertia or overturning moment. A negative margin says the machine could not hold that pose standing still. A taller hull needing less roll is a fact about this static geometry and not a claim about a tall machine in motion.
- A flight phase is reported (`airborne`) rather than hidden, and it claims no margin either way, because there is then no support to be inside of.

## Usage

```tsx
import { solveWalker, walkerHullPoint } from "@/lib/robocn/walker"

const pose = solveWalker({ legs: 2, gait: "walk", phase: 0.25 })
pose.roll     // degrees of roll the load demanded — an output, not an input
pose.centre   // where that attitude actually got the mass, in plan
pose.margin   // room left inside the support polygon; negative is over the edge
walkerHullPoint(pose, { x: 0, y: 30, z: 12 }) // a hull-mounted part, in the world
```

## API

| name | type | default | description |
| --- | --- | --- | --- |
| `solveWalker` | `(options?: WalkerOptions) => WalkerPose` | — | Runs the footfall schedule, takes the support polygon it leaves, works out the nearest place inside it the mass can stand, and buys that offset with roll and pitch — then solves each knee as a two-link chain in its own vertical plane, from a hip the attitude has moved. |
| `WalkerOptions` | `{ legs?, gait?, phase?, height?, step?, lift?, halfWidth?, halfLength?, femur?, tibia?, hull?, rollLimit?, pitchLimit?, inset?, knee?, lean? }` | — | `legs` is 2 or 4; `hull` is how far the centre of mass sits above the hip line, which is what sets the price of every lateral move; `lean` pushes the demand in −1..1 of each attitude stop before it is clamped. |
| `WalkerPose` | `{ count, gait, ride, roll, pitch, demand, centre, legs, support, margin, airborne, stable, hull, femur, tibia, rollLimit, pitchLimit }` | — | Plan positions are x starboard, y toward the nose. `demand` is where the load asked the mass to be and `centre` where the attitude got it; `margin` is the signed distance from that to the edge of the support. |
| `WalkerGait` | `"stand" | "walk" | "stride" | "creep" | "pace"` | — | Per leg count: a biped has stand, walk and stride (which has a flight phase); a quadruped has stand, walk and creep in lateral sequence, and pace, which swings both legs of a side together. A gait the leg count does not have falls back to stand. |
| `walkerHullPoint` | `(pose: WalkerPose, local: Vec3) => Vec3` | — | Where a point bolted to the hull ends up in the world, in the hull's own frame — origin at the hip centre, x starboard, y up, z toward the nose. The solver places the hips with this same transform, which is what keeps a drawing on the machine it was solved for. |
| `supportMargin` | `(centre: Vec2, support: readonly Vec2[]) => number` | — | Room inside the convex hull of the contacts: positive inside a polygon, zero at best on a segment, negative outside either. |
| `walkerGaits` | `(legs: 2 | 4) => WalkerGait[]` | — | Which gaits a leg count actually has, for building a control that cannot offer a nonsense one. |

## Source

- `src/lib/robocn/walker.ts`
