# Lantern geometry

A charge transfer that conserves, a recital count, a reserve gauge, and an emission column priced by the inverse-square law — plus the exploded assembly from `assembly-geometry`, re-exported.

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

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

## Notes

- The explode schedule is about parts and fitting order, not about lanterns: any assembly with an axis per part and an order to it takes the same call.
- Exact reassembly is the property worth having. `progress` 0 gives the zero vector rather than a small one, so a machine that has been apart is byte-identical to one that never was.
- No collision model and no fasteners: parts pass through each other's paths the way they do in every exploded drawing, and progress runs backwards as happily as forwards.
- No thermal model, no internal resistance and no discharge curve. The transfer is a rate against a capacity, and that is all it claims to be.

## Usage

```tsx
import { explodeAssembly, stepCharge, emissionBeam } from "@/lib/robocn/lantern"

// Fitted bottom up, so it comes apart top down. Rank 0 leaves first.
const parts = explodeAssembly(
  [
    { id: "base", axis: { x: 0, y: 1, z: 0 }, travel: 0, order: 0 },
    { id: "body", axis: { x: 0, y: 1, z: 0 }, travel: 10, order: 1 },
    { id: "cap", axis: { x: 0, y: 1, z: 0 }, travel: 24, order: 2 },
  ],
  0.4,
)
parts[2].offset       // a world offset — project it and it is a screen offset
parts[2].fraction     // 0 seated, 1 clear of everything under it

// What leaves the reservoir arrives in the ring, less exactly what was drawn.
const step = stepCharge({ reservoir: 1, cell: 0 }, 0.5, { rate: 0.15, docked: true })
step.transferred      // and step.state.reservoir + step.state.cell is conserved

emissionBeam(0.5, 1).length   // reach at √intensity of full range
```

## API

| name | type | default | description |
| --- | --- | --- | --- |
| `explodeAssembly` | `(parts: AssemblyPart[], progress: number, options?: { overlap?: number }) => ExplodedPart[]` | — | Takes an assembly apart in the reverse of the order it was fitted. At progress 0 every offset is exactly the zero vector; at 1 every part is exactly its own `travel` from where it sat. `overlap` runs from a strictly sequential teardown at 0 to every part moving at once at 1. |
| `AssemblyPart` | `{ id, axis: Vec3, travel: number, order: number }` | — | One part: the axis it was fitted along (need not be a unit vector), how far it has to go to be clear, and when it was fitted. Parts sharing an `order` are one stage and leave together — a whole course of ribs, say. |
| `ExplodedPart` | `AssemblyPart & { direction, rank, fraction, distance, offset }` | — | `rank` 0 is the first part off. `offset` is in world units, and projection is linear, so `camera.project(offset.x, offset.y, offset.z)` is the translation to hang on the part. |
| `explodeFraction` | `(rank, count, progress, overlap?) => number` | — | The schedule on its own: how far through its own travel the part at `rank` is, out of `count` stages. |
| `stepCharge` | `(state: ChargeState, dt: number, options?: ChargeOptions) => ChargeStep` | — | The reservoir, the docked cell and the emitter over `dt`. Solved in closed form rather than integrated, so one big step equals a thousand small ones, and `reservoir + cell` afterwards is what it was before less exactly `drawn`. |
| `ChargeOptions` | `{ rate?, cellCapacity?, docked?, draw? }` | — | Transfer rate through the conduit, what the cell can hold in reservoir units, whether anything is docked at all, and what the emitter is taking. |
| `reserveState / chargeSegments` | `(reservoir) => "depleted" | "low" | "nominal" | "full" / (level, count?) => number[]` | — | The reserve read as a state for a lamp, and as a segmented gauge whose fills average back to the level they were made from. |
| `recital / glyphBars` | `(progress, { lines?, glyphs? }) => Recital / (index) => [number, number, number]` | — | How many inscription cells are lit and which line the recital has reached, and the abstract marks in one cell — three bar heights from a hash of the index. No text, nothing to read. |
| `cageRibs / bandCell` | `(count, radius?) => CageRib[] / (angle, radius, halfArc, thickness, steps?) => Vec2[]` | — | Ribs placed so a bay faces the front rather than a rib, and an arc of a band as a plan-view footprint ready to extrude: a collar cell, a rib section, a vent slot. |
| `emissionBeam` | `(charge, power, options?) => Beam` | — | Intensity is the power asked for, with the reserve as a ceiling rather than a multiplier. Reach is `√intensity` of the full-power range, because illuminance goes as the inverse square. |
| `conduitBeads / breathe / cyclePhase` | `(flow, clock, beads?) => number[] / (clock) => number / (clock) => number` | — | Flow markers that stand still when nothing is moving, a 0..1..0 breath over a cycle, and a clock folded into one cycle. |

## Source

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