- soma rotate-align README (was missing): anti-bot core, parts, props incl. targetSnapDeg, keyboard, rotation telemetry, sema events, compass+clock skins. - eidos README links all three challenge morfos/READMEs. - demo: the Live snippet, API tab, morfo tab and Services tab now follow the SELECTED challenge (RotateAlign/Clock knob table added; the two challenge morfos share handle-* names so tables swap instead of merging). - prettier normalization of the compass rotate-align.svelte (pre-existing warn). - handoff marked COMPONENT COMPLETE; only v2 roadmap + foreign text-blur blocker remain. Gates: component:audit 144/144 PASS, svelte-check clean on touched files, prettier clean, demo tabs verified live in Chrome. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>menubar-v4-safe
parent
caa4397b3d
commit
8b06f6bc70
@ -0,0 +1,81 @@
|
||||
# RotateAlign (soma)
|
||||
|
||||
A human-verification **challenge** for [`ProofOfHuman`](../proof-of-human/README.md):
|
||||
rotate a needle onto a randomly placed target bearing and **hold it steady** until
|
||||
it settles. It requires a `ProofOfHuman.Provider` ancestor — it reports its
|
||||
behavioural telemetry up through `ProofOfHumanChallengeHost` and the orchestrator
|
||||
owns the verdict.
|
||||
|
||||
## Why this exists (the anti-bot core)
|
||||
|
||||
- **Two input modes, one signal.** Pointer drag around the dial OR **arrow keys**
|
||||
both swing the same needle and feed the same angular telemetry — the keyboard
|
||||
path is a first-class challenge, not a signal-free bypass.
|
||||
- **Settle-to-lock.** Completing requires holding the needle on target for
|
||||
`settleMs` with no further movement: each move reschedules the settle timer
|
||||
(`uix.timers`), so a wiggle never locks and stillness on target does. The
|
||||
settle dynamics (overshoot, corrections, angular-velocity profile) are the
|
||||
discriminating, non-replayable signal.
|
||||
- **Randomised per mount** (seeded — pass `seed` for reproducible tests/demos).
|
||||
A replay or a two-key script can't solve it.
|
||||
|
||||
## Parts
|
||||
|
||||
| Part | Element | Archetype | Description |
|
||||
| ---------- | ------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Provider` | `<div>` | `provider` | Generates the target bearing, owns the interaction + telemetry. States `idle\|rotating\|aligned\|complete`. |
|
||||
| `Dial` | `<div>` | `track` | The face + target marker. Decorative to AT. Exposes `{ targetAngle, angle, aligned }` for eidos to paint. |
|
||||
| `Needle` | `<div>` | `thumb` | The rotating element (role `button`, `aria-roledescription` "rotating needle"). Rotation is a soma-driven functional transform. |
|
||||
|
||||
Soma exposes these three headless parts; eidos composes them into TWO styled
|
||||
drop-ins over the same engine: `ProofOfHuman.RotateAlign` (the compass — dial +
|
||||
cardinals + a mailbox target) and `ProofOfHuman.Clock` (the cuckoo clock — set
|
||||
the hand to the marked hour; the doors swing open and the messenger pigeon flies
|
||||
off on the verdict). Any rotation metaphor works (a key in a lock, a safe dial…).
|
||||
|
||||
## Props (`Provider`)
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `toleranceDeg` | `number` | `8` | Angular tolerance (deg) that counts as on-target. |
|
||||
| `settleMs` | `number` | `450` | Milliseconds the needle must hold steady on target to lock. |
|
||||
| `stepDeg` | `number` | `6` | Degrees the needle rotates per arrow-key press. |
|
||||
| `targetSnapDeg` | `number` | — | Quantize the random target to multiples of this many degrees — for skins whose face marks discrete positions (the Clock passes `30` so the target always lands on a whole hour). Omit for a free bearing. |
|
||||
| `seed` | `number` | random | Deterministic target (tests/demos). Omit = random per mount (the anti-replay property). |
|
||||
|
||||
Snippet props: `Needle → { rotating, aligned, angle }` · `Dial → { targetAngle, angle, aligned }`.
|
||||
|
||||
Angles are degrees, `0 = up`, clockwise. The provider keeps an unwrapped
|
||||
`visualAngle` for the needle transform so a CSS transition never spins the long
|
||||
way round the 360→0 seam; the logic angle stays normalized `[0, 360)`.
|
||||
|
||||
## Keyboard
|
||||
|
||||
| Focus | Key | Action |
|
||||
| ------ | ----------------- | ------------------------------------------------- |
|
||||
| Needle | `←` `↓` / `→` `↑` | Rotate by `stepDeg` (counter-/clockwise; starts). |
|
||||
| Needle | `Escape` | Cancel the rotation. |
|
||||
|
||||
When the challenge is **armed** (the orchestrator goes disabled → enabled while
|
||||
idle), the provider focuses the needle so the arrow keys work immediately —
|
||||
gated on the transition, so an already-enabled mount never steals focus on load.
|
||||
|
||||
## Telemetry
|
||||
|
||||
Base motion fields + the `rotation` block: `totalRotationDeg`,
|
||||
`maxAngularVelDegMs`, `overshootCount` (sign flips of the target offset outside
|
||||
tolerance), `finalErrorDeg`, `settleMs` (time from first on-target to lock).
|
||||
The tip path is sampled on a 100px-radius circle so the base fields
|
||||
(pathLength/velocity/straightness) stay comparable across challenges.
|
||||
|
||||
## Sema events
|
||||
|
||||
| Event | Family | Verb | Target | When |
|
||||
| ------------- | -------- | ------ | -------- | ---------------------------------------------------------------------------- |
|
||||
| `handle-pick` | `handle` | `pick` | `needle` | Rotation began (pointer down / first arrow). |
|
||||
| `handle-drag` | `handle` | `drag` | `needle` | Detent tick — fires each time the needle crosses a 30° mark. |
|
||||
| `handle-drop` | `handle` | `drop` | `dial` | Settled on the target within tolerance; hands telemetry to the orchestrator. |
|
||||
|
||||
The evaluable verdict is the orchestrator's `commit`, never the challenge's.
|
||||
Pack: `sema/components/rotate-align.ts`. Composes the `Gesture.rotate()` layer
|
||||
(angle + angular velocity + centre-at-pick; zero per-move layout reads).
|
||||
Loading…
Reference in new issue