# Keyboard geometry

The mechanisms under a machine you type on: travel with real hysteresis, an asymmetric keystroke, a unit-pitch deck with a stagger, matrix scan order, and caps standing on a raked plane.

> For the complete index, see [llms.txt](https://robocn.dev/llms.txt). A Markdown version of any page is available by appending `.md` to its URL or by sending an `Accept: text/markdown` header.

## Install

```bash
bunx --bun shadcn@latest add https://robocn.dev/r/keyboard-geometry.json
```

Registry item: `keyboard-geometry` · [`https://robocn.dev/r/keyboard-geometry.json`](https://robocn.dev/r/keyboard-geometry.json)

## Notes

- Pure functions over plain objects. No dynamics: no force curve, no tactile bump force, no click leaf, no key rollover, no debounce timing, no ghosting and no character encoding.
- The scan *order* is the real one — rows strobed, columns read across them. The scan *rate* is whatever clock you hand it.
- Travel and actuation are world units, so a switch with more travel really does stand its cap higher and trip further down. A contact placed past the end of the travel simply never closes, rather than being clamped onto the bottom.

## Usage

```tsx
import { keyTravel, keyboardLayout, matrixScan, deckFrame, capSolid } from "@/lib/robocn/keyboard"

const key = keyTravel(0.55, { travel: 4, actuation: 2, closed })  // -> actuated partway down
const deck = keyboardLayout([[1, 1, 1, 1], [1.25, 6.25, 1.25]])   // unit widths, one pitch
const scan = matrixScan(clock, 5, 14)                             // one row energized at a time
const face = deckFrame({ x: 0, y: 18, z: 0 }, 22)                 // a raked key plane
const cap = capSolid(camera, face, deck.keys[3], key.fraction)
```

## API

| name | type | default | description |
| --- | --- | --- | --- |
| `keyTravel` | `(press, options?) => KeyTravelPose` | — | One key's travel. The contact closes at `actuation` on the way down and opens at `reset` on the way up, so pass the previous state as `closed` and the switch has real hysteresis instead of chattering at one point. |
| `pressCurve` | `(t) => number` | — | The shape of one keystroke: a fast fall, a moment bottomed out, and a slower return on the spring. Nothing in this family presses like a sine. |
| `keyboardLayout` | `(rows, options?) => KeyboardDeck` | — | Rows of unit widths laid out on one pitch with a per-row stagger, indexed in scan order. `rowUnits` reports each row's own width, so a row that does not fill the deck is drawn short rather than stretched. |
| `keycapProfile` | `(row, rows, base?) => KeycapProfile` | — | The sculpt of one row: how far its caps stand above the deck and how far their faces tilt. The home row is the low point and the rows either side of it tilt inward. |
| `matrixScan` | `(clock, rows, columns) => MatrixScanPose` | — | Where a scan has got to: one row energized, the columns read across it, wrapping in both directions. Which key is down and which key is being looked at are two different things. |
| `strokePresses` | `(strikes, keys, stroke, options?) => number[]` | — | How far every key of a deck is pressed at a point in a passage, given a schedule of strikes. Overlapping strikes give the key the deeper press. |
| `codeStrikes` | `(indices, keys, options?) => KeyStrike[]` | — | A schedule that strikes each index in turn, evenly across the passage and inside it at both ends. Keys outside the deck are dropped, not clamped onto a key nobody asked for. |
| `deckFrame` | `(origin, rake) => DeckFrame` | — | The frame a raked key face lives in: across, downhill toward the operator, and out of the face. Keys press along the negative normal, which is why a raked deck shows its travel from a front camera and a flat one does not. |
| `capSolid` | `(camera, frame, placement, press, options?) => string` | — | One keycap as a tapered box standing on that frame, projected and hulled — truthful from all four cameras. |
| `deckPanel / capFace` | `(camera, frame, …) => PanelProjection` | — | A rectangle of the deck — a legend, a readout strip — as one affine transform plus whether the camera can see its front at all. |

## Source

- `src/lib/robocn/keyboard.ts`
