# @borkiss/tre — API reference

Two entries: `@borkiss/tre/core` (framework-free, peer dep `three` only) and
`@borkiss/tre` (everything below). All of `core` is re-exported from the full
entry. Start at /start.md; recipes at /recipes.md.

---

## `@borkiss/tre/core`

### `createAsciiArt(options?): AsciiArt`

The standalone engine: owns a WebGL renderer, a frame-capped rAF loop and one
`AsciiPipeline`. One call per canvas.

```ts
interface AsciiArtOptions extends AsciiPipelineOptions {
  canvas?: HTMLCanvasElement   // created (unattached) when omitted
  source?: AsciiSource         // see Sources below
  fit?: 'cover' | 'contain' | 'fill'   // texture sources; default 'cover'
  fps?: number                 // frame cap, default 60
  dpr?: number                 // default min(devicePixelRatio, 1.5)
  autoStart?: boolean          // default true
  autoPause?: boolean          // pause offscreen / hidden tab; default true
  onFrame?: (frame: AsciiFrame, time: number, delta: number) => void
}

interface AsciiArt {
  canvas: HTMLCanvasElement
  renderer: THREE.WebGLRenderer
  pipeline: AsciiPipeline
  frame: AsciiFrame                     // same object as pipeline.frame
  setSource(src: AsciiSource | null): void
  setRamp(ramp: string | GlyphAtlasOptions): void   // live glyph-set swap
  start(): void
  stop(): void
  running: boolean
  renderOnce(): void                    // works while stopped
  snapshot(type?: string, quality?: number): string // data URL (default PNG)
  dispose(): void
}
```

The canvas must have a CSS size (the engine observes it and resizes buffers).

### `AsciiFrame` — the per-frame look controls

Stable mutable object; write fields any time, no allocations:

| field | range | meaning |
| --- | --- | --- |
| `cellPx` | ~4–20 | cell width in device px (height 2× for glyphs, 1× for marks); smaller = denser |
| `style` | AsciiStyle | what each cell stamps: `'glyphs'` (default) · `'dots'` · `'rings'` · `'blocks'` · `'cross'` · `'diamond'` · `'hex'` · `'slash'` — procedural marks sized by luminance, square cells, no atlas |
| `markScale` | ~0.4–1.5 | procedural mark size multiplier (ignored for glyphs) |
| `pulse` | 0–1 | a diagonal wave rolls through the grid, breathing cells (all styles) |
| `ripple` | 0–1 | a concentric wave spreads from the screen centre (all styles) |
| `flicker` | 0–1 | per-cell random brightness flutter, like a dying sign (all styles) |
| `rain` | 0–1 | falling phosphor streaks overlay, one per column — digital rain (all styles) |
| `spin` | 0–1 | procedural marks twirl in place, each cell on its own phase |
| `tint` | THREE.Color | phosphor colour of lit glyphs |
| `colorize` | 0–1 | glyphs take the source's own chroma (colour ASCII) |
| `scramble` | 0–1 | fraction of cells smeared / swapped for garbage glyphs |
| `power` | 0–1 | CRT power envelope: 1 on, 0 collapsed to black (line → dot) |
| `glitch` | 0–1 | datamosh: row tearing, aberration spike, flicker |
| `jitter` | 0–1 | degauss frame shake |
| `persistence` | 0–0.95 | phosphor afterglow (0 off, 0.5 subtle, 0.85 heavy smear) |
| `glow` | 0–2 | halo strength around bright glyphs |

### `AsciiPipeline` — the raw pipeline (bring your own loop)

```ts
new AsciiPipeline(options?: AsciiPipelineOptions)

interface AsciiPipelineOptions {
  ramp?: string          // glyph ramp dark→bright; default ' .·:-=+x*o%#@'
  style?: AsciiStyle     // initial cell style; default 'glyphs' (same as frame.style)
  font?: string          // CSS font-family for the atlas (loaded fonts only)
  weight?: number|string // CSS font-weight, default 400
  bg?: ColorRepresentation  // void colour between glyphs; default '#04060b'
  minCellPx?: number     // densest cellPx you will drive; sizes buffers once; default 4
  lowCost?: boolean      // 4-tap glow + no MSAA (weak GPUs)
  crt?: { barrel?: number; aberration?: number; scan?: number; vignette?: number }
  scan?: number          // per-cell row shading in the ASCII pass
}

pipeline.frame                       // AsciiFrame (as above)
pipeline.setRamp(ramp | atlasOpts)   // live glyph swap
pipeline.updateSize(renderer)        // called internally by both render paths
pipeline.renderScene(renderer, scene, camera, time, delta)   // 3D path
pipeline.renderTexture(renderer, texture, time, delta, fit?) // graphic path
pipeline.dispose()
```

Passes: source → grid buffer (~1 texel/cell, MSAA ×4) → ASCII stamp →
persistence ping-pong → CRT to screen.

### Sources

```ts
type AsciiSource =
  | THREE.Texture | HTMLImageElement | HTMLVideoElement
  | HTMLCanvasElement | OffscreenCanvas | ImageBitmap
  | { scene: THREE.Scene; camera: THREE.Camera; update?: (time, delta) => void }

resolveSource(src): ResolvedSource     // what createAsciiArt uses internally
isSceneSource(src): boolean
createWebcamSource(constraints?): Promise<{ video, stream, stop() }>
```

Images get mipmaps + sRGB automatically; video self-updates; 2D canvases are
re-uploaded every frame; your own `THREE.Texture` passes through untouched.
Remember `webcam.stop()` — the camera light stays on otherwise.

### Glyphs

```ts
makeGlyphAtlas(ramp?: string | GlyphAtlasOptions, cellW?): { texture, count }
DEFAULT_RAMP   // ' .·:-=+x*o%#@'
RAMPS          // classic · minimal · dense · blocks · braille · katakana · binary · lines
STYLES         // ['glyphs','dots','rings','blocks','cross','diamond','hex','slash'] — for frame.style
```

A ramp is just a string ordered dark → bright — bring any characters; the
atlas is drawn at runtime on a 2D canvas (no font assets). Or skip characters
entirely: `frame.style = 'dots'` stamps procedural marks instead (see
`AsciiFrame` above) — halftone dots, rings, blocks, crosses, diamonds,
hexagons, slashes — with `spin` to twirl them and `pulse`/`ripple`/`flicker`/
`rain` waves that work for glyphs too.

### Audio

```ts
audio   // hot store, all 0..1: { level, bass, mid, treble, beat, active }
await audioReactor.connectMic()
await audioReactor.connectDisplayAudio()   // tab/system audio (Chrome)
audioReactor.connectElement(mediaEl)       // an <audio>/<video> you play
audioReactor.disconnect()
```

Auto-gained against a running peak; `beat` snaps to 1 on bass onsets and
decays exponentially. Read `audio.*` inside `onFrame`/`useFrame`.

### Device triage

```ts
coarseLowPower(): boolean   // mobile / few cores / low memory — pick lowCost
asciiCellScale(width): number
```

### Acts (pure math, no deps)

```ts
const story = defineActs([{ key: 'boot', weight: 0.6, ...yourFields }, ...])
story.bounds / centers / count / total / index[key]
story.actLocal(p, i)              // local 0..1 progress inside act i
story.actIndex(p)                 // which act contains progress p
story.spanGate(p, from, to, edge?) // smooth 0→1→0 gate across acts [from..to]
clamp01(x); smooth01(x)
```

### Low-level

`FullscreenPass`, shader sources `ASCII_FRAG`, `CRT_FRAG`, `PERSIST_FRAG`,
`FIT_FRAG` — for building custom pipelines.

---

## `@borkiss/tre` (adds React / scroll / assets)

### `<AsciiRenderer />` (React Three Fiber)

All `AsciiPipelineOptions` as props, plus
`onFrame?: (frame, state: RootState, delta) => void`. Mount inside `<Canvas>`;
it owns rendering via a priority-1 `useFrame` (R3F's auto-render turns off).
Recommended canvas flags: `frameloop="demand" dpr={[1, 1.5]} gl={{ antialias: false }}`.

### `ScrollStage` — the one scroll loop

```tsx
<ScrollStage timeline={tl} energySource={() => audio.level}
             onProgress={(p) => setActiveSection(story.actIndex(p))}>
  <Canvas ...>...</Canvas>
</ScrollStage>
```

Owns Lenis; writes the hot stores; scrubs the paused GSAP timeline
(`timeline.progress(p)` — never `.play()`).

### Hot stores (read in frame loops; never cause re-renders)

```ts
progress.value   // scroll progress 0..1
energy.value     // smoothed drive: scroll velocity ∪ audio level, non-rewindable
pointer.x/.y     // -1..1
useScrollProgress()          // React hook returning the progress holder
useEngineStore               // zustand: ready / isLowTier / activeSection (discrete)
```

### `createActTimeline(story, place, ease?)`

Builds the one paused GSAP timeline on the story's weight axis;
`place(tl, act, at, duration)` keys your params at each act's centre.

### Performance scaffolding

`<AdaptiveQuality />` (dpr clamp + weak-GPU triage → `isLowTier`),
`<FrameCap fps={60} />`, `<ShaderWarmup />`, `coarseLowPower()`.

### Materials & assets

`SubjectMaterial` (lambert+rim GRAYSCALE luminance shader — subjects should
output brightness, the pipeline adds colour), `createAssetResolver`,
`<GltfSlot />`, `<AssetErrorBoundary />`, `configureGLTFLoader`,
`disposeLoaders`, `DRACO_DECODER_PATH`, `KTX2_TRANSCODER_PATH`.
