# Rail geometry

What a track does to the vehicle standing on it: bogies placed on a curve and the centre and end throw that follow, Klingel hunting on a coned wheelset, a pantograph solved to a working height, and a turnout's lead, crossing angle and blade throw.

> 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/rail-geometry.json
```

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

## Notes

- Everything here is exact geometry except `trackCurvature` and `wireStagger`, which are stated shapes and say so. No dynamics: nothing knows about mass, speed, force, adhesion, damping or wear.
- Positive is to starboard everywhere — clockwise seen from above — the same sense as every heading in the set.
- The family this is under: `docs/rail-machines.md`. Its thesis is that every other vehicle in robocn is *steered* and a rail vehicle is not, so the track is the input and the pose is the answer.

## Usage

```tsx
import { bogieRide, curveRadius, huntingPose, klingelWavelength, turnoutGeometry } from "@/lib/robocn/rail"

const ride = bogieRide(curveRadius(10, 88), { pivotSpacing: 88, halfLength: 66 })
ride.centreThrow   // the body's middle, inside the curve
ride.endThrow      // its ends, outside it — and always further

klingelWavelength({ wheelRadius: 19, halfGauge: 33, conicity: 0.1 })
turnoutGeometry(8, 20).crossingAngle   // atan(1/8) in degrees
```

## API

| name | type | default | description |
| --- | --- | --- | --- |
| `curveRadius` | `(turn: number, chord: number) => number` | — | The radius of a curve that turns through `turn` degrees under a chord — how a curve is quoted here, because the angle a vehicle's own bogie spacing subtends is the thing you can see. Zero is straight and returns Infinity. |
| `bogieRide` | `(radius, geometry: BogieGeometry) => BogieRide` | — | A rigid body on two bogies, placed on a curve. The pivots are on the track and the body is the chord between them, so the yaw of each bogie, the centre throw `R(1 − cos θ)` and the end throw `√(R²cos²θ + L²) − R` all fall out with no approximation. |
| `bodyOffset` | `(x, radius, pivotSpacing) => number` | — | Where any point along the body sits across the track, positive outward. Negative between the pivots, zero at them, positive beyond — which is what makes the centre and end throw two readings of one relation. |
| `klingelWavelength` | `(geometry: WheelsetGeometry) => number` | — | λ = 2π√(b r₀ / γ): the distance a coned wheelset takes to weave one full cycle. The one length in railway engineering with nothing to do with speed. A cylindrical tread returns Infinity. |
| `huntingPose` | `(distance, amplitude, geometry) => HuntingPose` | — | Where a hunting wheelset stands after running `distance`: `y = A cos(2πs/λ)`, and the yaw is its own slope, so the two run a quarter cycle apart. The flange is a hard clamp, and `flanging` says when it is on. |
| `radialYaw` | `(offset, radius) => number` | — | The angle a wheelset takes up if it steers radially — square to the radius at its own position along the bogie. |
| `pantographPose` | `(height, geometry: PantographGeometry) => PantographPose` | — | A single-arm collector solved to a working height. Height spends reach, so the knee folds in as the pan rises; past full extension the height clamps and `reachable` goes false. The head's attitude is the output of a second, closed loop — the control rod — not a value pinned to horizontal. |
| `turnoutGeometry` | `(turnoutNumber, gauge) => TurnoutGeometry` | — | The crossing angle is `atan(1/N)`; the crossing is where the inner rails meet, at `cos α = (R − g)/(R + g)`, which fixes the radius and the lead. The offset at the crossing comes out as very nearly one gauge whatever the number, and the lead grows about as N². |
| `turnoutPoint` | `(distance, geometry) => Vec2` | — | A point on the diverging route, measured along the straight from the toe. Tangent to the straight at the toe, so it leaves with no kink, and straight on at the crossing angle past the crossing. |
| `bladePose` | `(position, throwDistance, tolerance?) => BladePose` | — | Two switch blades on one throw bar: the gaps always sum to the throw. The route is detection — a tolerance on the closed blade — so a turnout caught in mid stroke reports `"unset"` rather than a route nobody has. |
| `trackCurvature / wireStagger` | `(distance, amplitude?) => number / (distance, amplitude?, span?) => number` | — | Illustrative. A stretch of line that winds without repeating over a short run, and the triangular zig-zag a contact wire is strung with. Neither is surveyed or solved. |

## Source

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