demo.fauna.flight — where every swallow is at film time t
The colony's whole day, as pure functions of t.
local Flight = require("demo.fauna.flight")
local pose = Flight.pose(3, 51.5) -- bird 3 at film second 51.5
-- pose.x/y/z, pose.qx..qw, pose.flap, pose.sweep, pose.inside, pose.speed
How a bird moves
Each bird's day is a list of timed nodes — a perch it holds, a pane it threads, a point in the hall it is aimed at. Between them it flies a Hermite curve with Catmull-Rom tangents; a node the bird is standing still on carries a zero tangent, so it eases off a capital and flares onto one instead of arriving at speed. Over the free stretches a small per-bird swerve (three sines, well under half a metre) is laid on top, so no two birds trace the same line and none of them flies straight.
Everything else is read back OFF that curve:
| heading | the velocity, by central difference at ±1/96 s |
| bank | the horizontal turn rate over ±0.07 s, resolved as a coordinated turn — atan(omega v / g), capped at 66 degrees |
| wingbeat | phase 2 pi f t with a per-bird f near 4.9 Hz; effort drives the AMPLITUDE and never the frequency, so the phase stays a closed form |
| folded wings | below 0.55 m/s the bird is standing: wings swept 73 degrees back along the body, with the occasional quick shuffle |
Held near five beats a second on purpose: at 24 frames a second a real swallow's ten-to-twelve strobes, and the stroke has to read.
Why it is pure
Film.seekTo(41.5) has to put the colony exactly where playing to 41.5 would,
or the film is not recordable. Nothing here integrates dt, nothing carries
state between frames, and the wingbeat's frequency is constant per bird so its
phase never has to be accumulated. pose(i, t) called cold gives the same answer
as pose(i, t) called after ninety-nine seconds of play.
The choreography
The schedule tables at the top of the module are the shot list, written down:
WAKE (who leaves when), NOON_PASSES (the nine crossings of shot 10),
RETURN_ORDER (the dozen coming home in shot 15), RESIDENT (who holds the
hall between named beats so it is never quite empty), and the named birds for
the transom, the counter shadow, the balustrade and the last landing. Each entry
carries the film second it happens at.
What the room has to provide
setCapitals(list) and setTransom(p) take the perches from the hall's own
geometry — demo.fauna calls both at build time off hall_col_abacus_N and
hall_window_transom. The constants in this file are the fallback for a room
that has not been built yet, not the authority.
The broken pane is not a constant at all: it is derived from
Film.WINDOW.brokenCol / brokenRow over the window's grid, so if the plan
moves the missing pane the colony follows it.
Scoped to this part · feeds back into the world's score.