Overlay Pipeline — (video × variant × chunk)

Last Updated: 2026-07-21

This page covers libs/flight-overlay/ (@rocket-club/flight-overlay), the shared render model behind the Flight Deck, the render jobs, and the Video Overlay Lambda. ADR-0003 (docs/adr/0003-render-pipeline-variant-chunk.md) is the decision of record; this page describes what shipped.

One renderer, three hosts — identical pixels

The lib's header states the contract verbatim (libs/flight-overlay/src/index.ts):

One renderer, three hosts: the browser preview, the local CLI, and the Lambda. They must produce identical pixels.

The three consumers run on three different hosts — the browser preview/sync wizard (native canvas), the local CLI, and the Lambda handler (@napi-rs/canvas) — and must render identical pixels, because the preview's whole job is to promise what the render will produce. A "Boost" clip must be byte-identical to the "Boost" chart button. The only way to guarantee that is one implementation everyone imports; a variant or a chunk is a parameterisation of that single buildOverlayModel / buildPhasePresets / resolveChunk path, never a second phase-math or render implementation.

The overlay-variant axis

An overlay variant is a named preset of buildOverlayModel inputs plus a little render-time presentation. Variants are a closed, code-defined enum — not user-authored content — living in libs/flight-overlay/src/model/variants.ts:

  • OVERLAY_VARIANTS — the seed catalogue.
  • OverlayVariantId — the id union.
  • getOverlayVariant(id) / listOverlayVariants() — the lookup and browse helpers.

Each variant resolves to a partial BuildOverlayOptions (its build field) that buildOverlayModel (model/build.ts) consumes; units is always pinned, and the window knobs default in the builder. The four seed variants are:

  • full-band-metric — Full band (metric)
  • full-band-imperial — Full band (imperial)
  • minimal-ticker — Minimal ticker
  • boost-closeup — Boost close-up

Adding a variant is a data edit to that array — single-tenant and low-ceremony by design, not an authoring subsystem. The app imports the catalogue through the thin passthrough apps/web/src/lib/flights/overlay-variants.ts.

The chunk axis

A chunk selects a [t0, t1] window in flight time (t = 0 at liftoff). The Chunk / ChunkId types live alongside the variants in model/variants.ts (the neutral lib), because variants.ts references them and both the app and the Lambda import them, never the reverse:

type ChunkId = 'full' | 'boost' | 'apogee' | 'descent' | 'custom'

full is the whole trim window (not a phase sub-range); the four named ids (boost/apogee/descent) resolve through the same buildPhasePresets the telemetry charts use. That phase math lives in libs/flight-overlay/src/model/phases.ts — the app's components/charts/telemetry/presets.ts is now a thin re-export of it — so a rendered "Boost" clip is range-identical to the "Boost" chart button.

Chunk normalisation and resolution ship in the lib at model/chunk.ts:

  • normalizeChunk(raw) collapses the loose transport shape ({ id, t0?, t1? }, shared by the render-start input and the Lambda event) into the canonical Chunk union — it rejects an unknown id and a custom chunk lacking finite t0 < t1, and drops stray bounds on the named ids, so exactly one validated shape reaches the resolver.
  • resolveChunk(chunk, window, markerTimes) turns a Chunk into a concrete [t0, t1] (or null when the chunk is unavailable/empty for the flight), reusing buildPhasePresets for the named ids.

Note: resolveChunk / normalizeChunk and the Chunk / ChunkId types ship in the lib (libs/flight-overlay/src/model/), so the app and the Lambda call one function with no second phase implementation to drift. ADR-0003 sketched the resolver app-side; its status header records that open question as resolved to the flight-overlay lib, which is what shipped.

A named chunk whose driving marker is absent (e.g. no Apogee marker) is simply not offered — the same availability rule the chart toolbar follows.