# Suction Line — C-Stabilized (Rapier PD-servo seal)

A spawn → conveyor → 3-DOF arm → bin factory line where the end-effector is a **real
suction cup**. The carried block is a **Rapier dynamic rigid body held ONLY by per-step
forces** — no fixed joint, no weld, no parenting, no pose-copy, no teleport. The block
wobbles with real compliance and **falls physically** whenever the seal breaks.

## Suction model (adapted from Dex-Net 3.0)
Seal formation + wrench-resistance limit set adapted from **Dex-Net 3.0** (Mahler et al.,
*Learning ambidextrous robot grasping policies* / suction model, ICRA 2018,
arXiv:1709.06670): a compliant suction contact forms only when the cup is seated and
aligned on a graspable face (sealQuality `q ∈ [0,1]`), and the seal can resist a limited
wrench — a **normal pull-off** up to `V·q`, a **tangential friction cone** `μ`, and a
small **torsional** limit. When any component of the required wrench exceeds its limit the
seal breaks.

## The stabilization fix (this build vs. the unstable original)
The original C build commanded the normal force **open-loop**:
`wantN = max(m·g, m·g + FsN)` — always ≥ weight, with no damping — so held blocks
accelerated upward without bound (y rocketed to ~30,000; held-cup gap 113–353).

This build replaces it with a **force-limited PD servo** about the seated position:

```
gapUp    = -(err · n)              // seat error along the up axis
relVelUp = -(relVel · n)           // block-vs-cup velocity along up
wantN    = m·g + Kp·gapUp − Kd·relVelUp        // weight feed-forward + PD
appliedN = clamp(wantN, 0, V·q)                // clamp to the vacuum pull-off cap
```

with `Kp = 1800 N/m`, `Kd = 140 N·s/m`. The damping term removes the runaway; the block
now hovers flush at the cup mouth (compliance/wobble intact). Fallen/placed blocks that
leave the world are removed from the Rapier world so nothing free-falls forever.
The seal still **breaks honestly** on persistent overload (`appliedN` saturates at `V·q`,
the block sags, the wrench limit is exceeded → seal breaks → block drops).

## Verified headlessly (seeds 501/502/503/777, 10800 steps)
maxAbsY 2.3–7.9 (was 27k–34k), carry-hold gap 0.41–0.47 (was 113–353), **31–33 placed**,
0 dropped, deterministic, no NaN. `?test=seal`: good seal carries seated (no launch);
overweight/misaligned seal breaks and the block falls.

**Throughput (GEN 3):** raised from 15–20 → **31–33 placed / 3-min** purely via feed &
unloaded-move scheduling — belt speed, faster empty arm moves, smarter target selection,
trimmed dwell — with the suction physics (SUCTION_PARAMS, PD servo, seal-break) FROZEN and
byte-identical. Held-move acceleration cap is unchanged, so grasp fidelity is untouched.

## Contract / modes
`window.__game.line` `{spawned,transported,picked,placed,dropped,throughputPerMin,stations}`
· `window.__game.suction` `{sealEngaged,sealQuality,holdForce,appliedLoad,vacuumOn,sealBroken}`
· `window.__harness{reset(seed),step(n),getState()}` (deterministic per seed)
· `window.__views` = `[overview, conveyor, arm, cup, bin]` (cup = suction-contact close-up)
· `window.__spec`, `window.__ontology` (bounds honest, max y = 9)
· `?presentation=1` · `?seed=N` · `?debug=grid` · `?test=seal`
