# physics/motion.md

`@ferret/motion` (stage A4) is a small Lisp for things that happen over time: a stroke of an oar, a gust, a door that opens and closes. It has no Babylon and no physics; hosts add the words that touch the world (`force`, a sensor) with `define` and `action`. Programs run across fixed steps: `wait` suspends, nothing blocks.

## Text and JSON are one tree

```lisp
(defmotion stroke (side :power 600)       ; positional, then keyword pairs with defaults
  (par (for 0.4s (force side forward power))
       (seq (wait 0.4s) (emit "stroke" side))))
```

The JSON form is the same tree: a list is an array, a symbol is a plain string, a string literal is `{ "s": "text" }`:
`["emit", {"s": "stroke"}, "side"]`. Condition objects of the animation graph (`{ "param": "row", "op": ">", "value": 0 }`, `{ "trigger": "abort" }`, `{ "stateTime": 0.4 }`, `{ "finished": true }`) are allowed wherever a condition is.

Units are folded in when reading: `30rpm`, `90deg`, `0.4s`, `250ms`, `600N`, `2m`, `5kg`, `3W` become numbers in SI (radians, seconds). Names are kebab-case. `;` starts a comment.

## Forms

| Group | Forms |
|---|---|
| Time | `(wait t)`, `(wait-until cond)`, `(wait-event name [filter])` (the payload is `event`) |
| Flow | `seq`, `if`, `when`, `unless`, `cond`, `let`, `set!`, `loop`, `(repeat n …)`, `(for t …)`, `(while cond …)`, `(guard cond …)` |
| Together | `(par a b …)` waits for all, `(race a b …)` takes the first and kills the rest |
| Events | `(on name …)` handles every event while the enclosing form lives; `(emit "name" payload)` |
| States | `(states :initial a (a body… [:until cond] -> b) (* :when cond -> c))`; `state-time` counts from entering the state |
| Functions | `(defmotion name (params…) …)` lasts (may wait); `(defn name (params…) …)` and `(lambda …)` are instant calculations |
| Maths | `+ - * / mod min max abs sin cos sqrt floor clamp lerp`, comparisons, `and or not`, `vec +v -v *v dot cross len norm`, `random`, `curve` |
| Data | `(param name)`, `(bb name)`, `(bb-set! name v)`, `time` |

An **action** (`force` and the like, registered by the host) is a *hold*: the thread stays on it, its arguments are evaluated again every step, and it stops when the enclosing `for`, `while`, `guard`, `race` ends. `(ramp t (action …))` fades a hold in over `t`. `half-sine`, `periodic` and `curve` read the age of the action they are in.

A symbol with no binding is a name: `forward`, `oarlock-left`.

## Running

```ts
const rt = new MotionRuntime({ seed: world.seed, budget: 10000 });
rt.define('log', (ctx, ...a) => …);                       // an instant word
rt.action('force', { start: (ctx, args) => ({ step: (a, c) => …, stop: () => … }) });
const mod = rt.load(text, 'rower.motion');                // checked now: names, arity, keywords, states
const p = rt.run(mod, '(stroke left)', { power: 400 });    // or mod.main[0]; JSON works too
rt.tick(1 / 60);                                           // every fixed step
p.set('row', 1); p.trigger('abort'); rt.deliver('ping', 3);
p.pause(); p.resume(); p.stop(); await p.done;
```

`load` rejects unknown functions, wrong argument counts, unknown keywords and missing states with `file:line:col:`. Run-time errors (a number expected, a loop that never waits) stop that program, emit `motion:error` on the runtime, reject `p.done` and name the form.

## Budget, determinism, snapshots

- **Budget**: each thread may do `budget` reductions (default 10 000) per step; a loop with no `wait` stops with an error naming the form.
- **Determinism**: no wall clock; `random` is seeded from the world seed and the program id, and its state is part of the program.
- **Snapshot**: `p.snapshot()` is plain JSON (stack, waits, environments, random state). `rt.restore(json)` continues exactly as an uninterrupted run would; holds are started again in the restoring runtime.

## Physics words (stage A5)

`motionPhysics(actuators)` from `@ferret/physics-actuators` is the capability `motion` (phase `field`, before the actuators, requires `actuators`). It owns a `MotionRuntime`, ticks it once per fixed step and adds the words that touch the world.

| Kind | Words |
|---|---|
| Actions (last while their form does) | `(force point dir magnitude)`, `(torque axis magnitude)`, `(motor axis speed :max-torque t :gain g)`, `(flow-ramp source seconds value)`; `:on Node` picks another body |
| Instants | `(impulse point dir magnitude)`, `(command actuator v)`, `actuator-on`, `actuator-off`, `(fire actuator)`, `flow-on`, `flow-off`, `(flow-source name :key v …)`, `(flow-move name (list (vec …) …) speed)`, `list`, `(world x y z)` |
| Sensors | `speed`, `speed-along`, `angular-speed`, `position`, `height`, `tilt`, `rpm`, `angle`, `in-angle`, `in-zone`, `in-medium`, `flow-at`, `apparent-wind`, `wind-speed`, `wind-dir`, `power`, `work`, `stalled`, `phase` |
| Macro | `(over-angle shaft (from a b [axis]) body…)`: the body runs while the shaft is between the angles, at any speed and either way |

A **point** is a vector in the body's frame or a node name (`oarlock-left` finds `Oarlock_L`, ignoring case, `-` and `_`; `addPoint(name, node)` maps other names). A **direction** is `forward` (+z), `back`, `up`, `down`, `right`, `left`, a vector in the body's frame, or `(world x y z)`.

A program has a **target** (the node it pushes): `motion.run { target }` or `(bind "Boat" program)` in a file. A level-wide program (weather) has none and may use only the flow words; a body word without a target stops that program with "no target body".

Events: `actuator:*`, `machine:stall`, `zone:enter`, `zone:exit`, `interact:use` reach `wait-event` (the payload is the actuator or zone name); `emit`, `motion:done`, `motion:stop`, `motion:error` come out as world events `motion:<name>`.

Commands: `motion.run`, `motion.stop`, `motion.pause`, `motion.resume`, `motion.set`, `motion.trigger`, `motion.state`, `motion.eval` (run to the first wait and return the value); `edit.motion.set { name, source }` (text or JSON; a file with an error is refused whole and the old one stays), `edit.motion.remove`, `edit.motion.list`. A level's `physics.motions` is a list of `{ name?, source }` (the loader that reads the file hands the text over). The programs of a file that are not definitions start when it is set and stop when it is replaced or removed.

## Not yet

Machine and vessel words (`throttle`, `cutoff`, `ignition`, `pitch`, `set-surface`, `pressure`) register from their packages as those get their controls (engines: A10); `touching` and `contact-force` need the contacts in the `field` phase; `depth`; the `RunMotion` leaf of the behaviour tree. Code both ways is A6, strokes A7, networking A8.

## Code and the language, both ways

`MotionBridge` (in `@ferret/motion`) joins a runtime to code.

```ts
const br = new MotionBridge(rt, { commands: world.commands });
br.define('stamina', { args: [], returns: 'number' }, ctx => stats.stamina);                    // instant: usable in conditions
br.define('pressed', { args: ['symbol'], returns: 'boolean', where: 'owner' }, (c, a) => input.pressed(a));   // owner only
br.lasting('walk-to', { args: ['vec3'] }, (ctx, p) => agent.moveTo(p));                         // a {done(), result} or a promise
br.action('vibrate', { args: ['number'] }, { start: (c, [s]) => ({ step() { pad.rumble(s); }, stop() { pad.rumble(0); } }) });
br.load('(defmotion stroke (side :power 600) ...)');
const h = br.run(br.tag`(par (stroke left) (stroke right))`, { power: 600 });   // a handle: stop, pause, resume, set, trigger, on, state, done
br.get('stroke')(sym('left'), { power: 700 });                                  // a defmotion as a TypeScript function
```

- A **signature** (`args`, `min`, `rest`, `returns`, `where`) is checked when the program loads (argument count, literal types) and when the function is called (values). An argument typed `function` arrives as a plain JS function: a `(lambda ...)` run to the end (it must not wait).
- A **lasting** call is a statement (like `wait`): the program sleeps until the waitable is done; the value is the statement's. A snapshot taken while it runs resumes after it.
- `where: 'owner'` marks a function that reads something local; `br.run(..., {}, { owner: false })` refuses a program that uses one.
- `(cmd "name" :key value ...)` calls a registered command. A program may call only the allow list (`actuator.`, `flow.`, `rotor.`, `vessel.`, `motion.`, `gravity.`, `wind.`, `body.` by default; the game can replace it) and never `edit.*`, `physics.configure` / `enable` / `disable`, `pkg.*` or `catalog.*`.
- `br.parse`, `br.print`, `br.toJson`, `br.fromJson` give programs as data for tools.

## Handing a program to another machine (A8)

`netPrograms(rt, net, onAdopt).handOver(name, program, peer)` stops the program here and sends its snapshot; the peer restores it and continues exactly where it stopped (the same actions must be defined there).
