ph-haptics 0.1.0

Host-compiled haptics DSL and no-std, no-alloc scheduling runtime modeling ERM and LRA motors using ph-curves
Documentation
# `ph-haptics` 0.1.0 public API


## Scope


This document covers the runtime library API. Generator CLI internals are
documented separately in [`compiler-design.md`](compiler-design.md).

## Goals

- Keep the runtime crate focused on deterministically evaluating precompiled haptics into timed, abstract motor commands.
- Make the compiled path the default and supported construction path.
- Keep `no_std` / `no_alloc` and deterministic behavior for embedded use.

## Non-Goals

- No textual parsing/compilation in the runtime crate.
- Handwritten programs are not the supported integration path; consumers use
  catalogs emitted by `ph-haptics-gen`.
- No allocator-dependent runtime features.
- No GPIO, PWM, bus, HAL, motor-driver, or board-integration code in this crate.
- No claim about electrical safety, actuator compatibility, physical motor response, or HIL results.

## Integration boundary

- Embedded consumers depend on `ph-haptics` with **default features only** (never `features = ["gen"]`).
- Consuming firmware applies `DriveCommand` through its own hardware adapter.
- Host-side compilation stays in `ph-haptics-gen` (`--features gen`).
- Product contract: [`contract.md`]contract.md. Package rules: [`architecture.md`]architecture.md.

## Public API Surface


### Modules

- `compiled`
- `error`
- `motor`
- `runtime`

### Public Re-exports (`src/lib.rs`)

- Compiled model:
  - `CompiledHapticDef`
  - `CompiledCatalog`
  - `CompiledCatalogError`
- DSL (crate-root; used by generated code):
  - `Instruction`
  - `LoopMode`
  - `Program`
  - `Ramp`
- Runtime:
  - `Runner`
  - `Frame`
- Motor/domain:
  - `MotorKind`
  - `MotorConfig`
  - `ErmConfig`
  - `LraConfig`
  - `MotorProfile`
  - `DEFAULT_ERM_PROFILE`
  - `DEFAULT_LRA_PROFILE`
  - `DriveCommand`
- Error:
  - `Error`
- Curves ecosystem passthrough (`ph-curves` 0.2.1, default features off):
  - `ph_curves` module itself
  - `Curve`
  - `MonotonicCurve`
  - `MonotonicCurveLut`
  - `MonotonicCurveLut256`
  - `Rounding`
  - `Tickless`
  - `TicklessDeadline`
  - `TicklessSchedule`
  - `UnitValue`
  - Do **not** re-export 0.2 transfer / filter / calibration APIs unless the
    haptics runtime needs them.

## Primary command flow (supported path)

1. Include generated compiled asset file (from `ph-haptics-gen`).
2. Use generated `COMPILED_CATALOG` (or `CompiledCatalog::new(&COMPILED_HAPTICS)`).
3. Construct a runner through:
   - `CompiledCatalog::runner(...)` / `runner_started(...)`, or
   - `CompiledHapticDef::runner(...)` / `runner_started(...)`.
4. Evaluate the current clock with `Runner::poll(...)`.
5. Apply `Frame.command` through the consumer's hardware adapter and sleep until
   `Frame.next_transition_ms` when appropriate. Deadlines use wrapping `u32`
   arithmetic, so wait with a wrap-aware remaining duration
   (`deadline.wrapping_sub(now_ms)`) or a rollover-aware timer — do not compare
   `now_ms` and the deadline with absolute `<` / `>`. See the wrap-safe tickless
   notes in [`compiler-design.md`]compiler-design.md and
   [`AGENTS.md`]https://github.com/photon-circus/ph-haptics/blob/main/AGENTS.md.

## API Contract Details


### `CompiledHapticDef`

- Contains:
  - name
  - compiled `Program`
  - optional `MotorProfile`
- Public construction helpers:
  - `new(name, program, profile)`
  - `runner(motor)`
  - `runner_started(motor, now_ms)`
- Runtime behavior note:
  - profile gamma (if present) is applied during command evaluation.
  - profile kick (if configured) is applied when scheduled output recovers from rest / level 0.

### `CompiledCatalog`

- Name lookup and runner construction over a definition slice.
- Case-insensitive name search.
- Public helpers:
  - `new(definitions)`
  - `definitions()`, `is_empty()`
  - `find(name)`, `require(name)`
  - `runner(name, motor)`, `runner_started(name, motor, now_ms)`
- Error boundary:
  - `NotFound`
  - wrapped runtime `Error`.

### `Runner`

- Public lifecycle:
  - `start`, `restart`, `stop`
  - `poll`, `poll_or_start`, `start_and_poll`
- Public state access:
  - `is_running`, `motor`, `start_ms`
- Construction is intentionally routed through compiled APIs (catalog/definition).

### `Frame`

- Scheduler output payload:
  - `command: DriveCommand`
  - `next_transition_ms`
  - `instruction_index`
  - `finished`
- Helpers:
  - `is_idle`, `is_active`, `is_finished`, `command()`, `next_transition_ms()`

### `MotorProfile`

- Runtime command evaluation uses profile information emitted by generator:
  - kick (`kick_ms`, `kick_frac`)
  - run range (`min_run_frac`, `max_frac`)
  - gamma mapping (`gamma_curve`)
  - ramp defaults (`ramp_step_ms`, `min_dt_ms`, `duty_step`)
- Built-in defaults: `DEFAULT_ERM_PROFILE`, `DEFAULT_LRA_PROFILE`.

## Compatibility and Stability Notes

- Supported integration contract is:
  - `ph-haptics-gen` output + `ph-haptics` runtime + a consumer-owned hardware adapter.
- Generated code imports DSL types via crate-root re-exports (`ph_haptics::{Instruction, LoopMode, Program, Ramp}`).
- Module-level access to `dsl` is intentionally private.

## Contributor validation (local CI)


[`scripts/ci.sh`](https://github.com/photon-circus/ph-haptics/blob/main/scripts/ci.sh)
is the canonical gate list and the
contributor source of truth:

```sh
bash scripts/ci.sh
```

That covers formatting, host tests, generator tests, clippy over all host
targets, bare-metal check/clippy, a no_std/alloc feature guard, catalog
generation over `assets/phh-library/`, a generate→compile roundtrip, and the
mock-machine verify. See [`CONTRIBUTING.md`](../CONTRIBUTING.md).

### Internal-only compatibility surface

- `dsl` module is crate-private (`mod dsl;`) and not part of external module API.
- DSL items needed by generated code are available via root re-exports.

## Change Control Guidance

- Any change to:
  - `CompiledHapticDef`,
  - `CompiledCatalog`,
  - `Runner` construction paths,
  - root DSL type re-exports (`Instruction`, `LoopMode`, `Program`, `Ramp`),
  - `DEFAULT_ERM_PROFILE` / `DEFAULT_LRA_PROFILE`,
  should be treated as a cross-crate contract change and validated with local CI (including the generate→compile roundtrip).