# WAI Extension: Haptics

> Status: Draft. Phase 2 of the cargo roadmap. Touch as instructions:
> ship a keyframed intensity/sharpness envelope, the sink reconstructs an
> actuator signal. Conformance is **sample-equivalence**. Reference impl:
> the `haptic` feature of `wai-rs`; corpus: `haptic-conformance/`; live
> sink: wai.transaction.science/haptics.
Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.

## 1. Scope and model

A `wai.haptic.signal` object is a per-channel keyframed envelope of
**intensity** and **sharpness** (the Core-Haptics-shaped pair every
modern actuator API maps to) that the sink reconstructs to a per-sample
buffer and plays on whatever actuators it has. It is lever 3
(instructions-at-the-sink) for touch — an effect is a few hundred bytes,
not a recorded waveform.

In scope: the container, the deterministic reconstruct contract, the
**sample-equivalence** conformance criterion, the fallback floor, the
receipt profile.

Out of scope: a transport; actuator hardware specifics (the
channel→actuator binding is the sink's); carrier synthesis (the signal
is an amplitude/sharpness envelope, not a modulated waveform — the
deterministic floor stays trig-free).

Relationship to the core spec: mirrors interactive-worlds / spatial
audio. The reconstruct runs in the `wai.det.fixed64` floor; `media`
takes the informational value `"haptic"`; `wai.haptic.signal` is
authoritative.

## 2. Container (`WHAP`)

```
"WHAP" | u16 n_sections | section table (u8 kind, u32 off, u32 len) | sections
```

| kind | section | required |
|---|---|---|
| `0x01` | contract (canonical JSON) | REQUIRED |
| `0x02` | channel signals | REQUIRED |

### 2.1 Contract

```json
{ "sample_rate": 1000, "numeric": "wai.det.fixed64",
  "channels": 1, "duration": 1500 }
```

Haptic rates are low (≈1 kHz); `duration` is in samples.

**Reader limit.** A reader MUST NOT refuse a signal for its length while `duration ×
channels` is at most 134 217 728 (2²⁷, the audio limit of SPEC §2), and MAY refuse one
past it (`duration_too_long`), when it reads the contract, before it reconstructs
anything. A conforming encoder writes no signal past it.

### 2.2 Channel signals

`n_channels u32`, then per channel: `n_keys u16`, then
`n × ( pos_sample u64 | intensity i64 Fx | sharpness i64 Fx )`.
Both fields are in `[0, 1]`. Between keyframes they linear-interpolate
in `Fx`; before the first / after the last key they hold.

## 3. Reconstruct contract

For each channel, for each sample `t ∈ [0, duration)`:
`intensity(t)`, `sharpness(t)` = the `Fx` linear interpolation of the
channel's keyframes at `t`, each clamped to `[0,1]` and quantized to a
`u8` (`round(v · 255)`, half away from zero, in integer space). The
canonical reconstruction is, per channel, the interleaved
`[intensity, sharpness]` `u8` pairs.

## 4. Conformance — sample-equivalence

> Given the same `wai.haptic.signal`, a conforming sink MUST reconstruct
> the identical canonical `u8` buffer, hence the identical BLAKE3 — on
> every machine, no tolerance parameter.

```
signal_hash = BLAKE3("wai:haptic-signal\x01" || per-channel u8 pairs)
```

## 5. Fallback floor

A sink with no actuators ignores the signal (haptics are
supplementary); there is no perceptual substitute to fall back *to*, so
`fallback` is typically `null`. A sink MAY surface the envelope visually
(a waveform) — presentation, not conformance.

## 6. Receipt (JWP profile)

`signal_hash` is the identity; the group's objects are per-channel
reconstruction blocks, Merkle-bound; one Ed25519 signature (verified as
SPEC §7 *Signatures* requires) over signal_hash + root + `work` (= duration × channels) + carried
`joules_micro`; `parent_receipt_hash` chains — the worlds/audio receipt
shape reused. The figure's acquisition class rides beside it as an optional signed label
([energy-measurement](energy-measurement.md) §2.8): absent, the receipt keeps
its legacy bytes and the figure is unlabelled; present, it is inside the
signature. The reference meter labels every figure it measures
`OnChipCounter`, with its declared uncertainty.

## Appendix

Sample-equivalence is replay/mixdown-equivalence with the tick replaced
by the haptic sample. The envelope is trig-free by construction
(amplitude + sharpness, not a modulated carrier), so the `Fx` floor
reconstructs it exactly with `+,−,×,÷` alone — the simplest cargo class
to make bit-exact, which is why it's the fast Phase-2 win.
