Cold Start
README.md
asc_shots — ASCENSION: aspect 2, CAMERA & LENS
One spline, one lens, one cut. Every camera value in the part comes out of
M.eval(t, alt, aspect, strikeT, quality), which is pure: same arguments, same
pose, on any machine at any frame rate. M.update is the only thing that writes
DemoCamera.
What crosses to other aspects
The table M.update returns, and nothing else:
| field | what it is |
|---|---|
pos rot fov near far | the pose the frame was drawn with |
fovAuthored | §4's FOV column — see "The FOV the engine will not draw" |
alt depth | for aspect 1 to validate the bus against the camera, not to stage against (§4) |
r azimuth pitch roll nx cut locked | the pose, decomposed |
focus focusSubject fStop focal cocFar shutter dof | the lens |
Consumed from the bus: ck.t, ck.alt, ck.seedY, ck.aux.strikeT,
ck.aux.quality. Nothing else, and no raw time.
A card rack is one line from aspect 8, no require: set
ctx.data.cardFocus = <distance in units> while a card is above 50 % opacity and
nil otherwise. The lens racks to it and opens to f/2.8.
Framing tools, callable from an execute
The subjects of this part mostly do not exist yet, so framing is checked by projection rather than by eye:
M.project(t, alt, worldPoint, aspect?, strikeT?)→{ ndc, visible, inActiveFrame, dist }.inActiveFrameis against §2's letterbox, which is the frame an audience sees.M.projectDir(t, alt, dir, …)→ the same for a direction at infinity. Feed itck.aux.sunDirto find out where the sun lands.M.axisNdc(t, aspect?, strikeT?)→ the column axis's composed NDC, cut folded in.M.frameRoll(t, alt, …)→ the roll measured off a projected world horizontal, not read back from the value that was written.M.lockSpan(alt, aspect?)→ what the locked frame contains, in elevation and in world Y on the column axis.M.altCheck(altOf)→ §4's y column against the altitude the camera flies.M.colRadius(y)/M.columnFocus(r, y)/M.farCoCpx(...)/M.POS/M.refAlt.
The channels
POS is §4's key list, interpolated in cylindrical coordinates so the twenty-bar
sweep reads as an orbit rather than as a series of chords. The camera's Y is
ck.alt off the bus every frame — never a key here — so depth crossing zero
at t = 5.000 is true of the camera and not merely of the clock. §4's own y column
stays in POS[i].y as a cross-check.
PITCH is an absolute aim elevation. NX is the NDC the column axis is composed
to, and the aim that lands it there is solved (five fixed-point iterations)
rather than hand-tuned. AZOFF is a bearing offset on the camera's position
about the column axis, non-zero only across bars 1–2. ROLL is §4's +4° through
the blackout. FSTOP is the aperture.
The lock is structural. Every channel is evaluated at min(t, 25.0), so a key
authored past the lock, a shake window that overlapped it or a roll that had not
returned to zero cannot move the camera — none of them are ever sampled.
Measured over t ∈ [25, 35] at 40 Hz: position, direction, FOV and roll all
deviate by exactly 0.0.
The one cut
§2 allows exactly one, a 3° angle change, and it fires on ck.aux.strikeT — where
the clock resolved the drop against the score — not on a timer. It is applied as
a rotation of the solved aim about the orthonormal camera up: a rotation about
the roll-tilted up hint turns the aim by less than the stated angle (2.887° for an
authored 3°, measured). The total aim change across the cut frame is 3.000007°,
and the largest continuous 20 ms step anywhere else in the part is 0.880°.
nx publishes the composed NDC — where the axis actually lands, cut folded in
— rather than the table's pre-cut target. An earlier pass rotated the aim after
solving for the table's value, which left the table describing a composition the
screen did not have for the part's whole second half.
The shutter is closed for the frame the cut lands on.
The FOV the engine will not draw
§4's FOV column does not reach the screen in this engine build. Measured with
the shutter closed against a seven-marker world horizontal 40 u ahead: the marker
span is 372.1 px at 1280×720 with Camera.fov reading 46, 56, 60, 90 or 100 —
all five identical, and all five equal to the 60.0° prediction of 372.4 px. The
value reads back correctly from the component field, from
layers.active.camera.fov and from the camera.set tool; only the raster
ignores it. a2_r2_fov_probe_100.png is that frame at fov = 100.
So the module composes against M.FOV_DRAWN (60.0) and writes that one constant
to the camera. Two reasons, both load-bearing:
- Composition. Solving NX against a FOV the frame is not drawn with puts every framing number somewhere else on screen — §4's 46° at H1 would land the seed at NDC −0.243 instead of §8's −0.333.
- The shutter.
@frame.cameraDOES track the written value. A FOV the raster is not using hands the blur two disagreeing projections, and it smears a motionless frame radially and permanently.
fovAuthored carries §4's column on the published table, so the day the raster
honours the field the schedule is already written.
The lens
preseed.asc.asc_lens is this part's lens — defocus, then the shutter. Its own
header carries why neither shipping system could be driven. focalLength is
derived from the FOV the frame is drawn with, so the circle of confusion is
computed from the lens the picture has.
Focus tracks the seed for t 0–2.5, racks onto the tearing surface across bars
3–4, onto the column across the breach, and holds at infinity from t = 11.5. A
card rack overrides all of it. Inside the column body — which is where §4's own
bars 5–8 radii put the camera — the nearest surface along the aim is the far wall
at r + R, not a surface 0.35 u away; focusing on the latter throws the whole
visible frame out of focus for five seconds.
Defocus is switched off (dofOn = 0) from t = 11.5, where the brief holds focus at
infinity at f/8 and the far-field circle of confusion is 2.7e-5 mm. From the lock
both halves are off and the feature is released outright.
M.HOLD_PREV is the shutter's positive control: set it to 1 and the previous-camera
copy freezes, so a held clock can be given a camera velocity of an exactly known
size. Ships at 0.
§10's ladder
Rung ⑥ (DOF quality high → medium) is the only rung this aspect owns:
taps 24 → 12 at ck.aux.quality >= 6. Nothing else here reads the ladder.
Brief-internal contradictions, escalated rather than fudged
- §4's camera is inside §2's column for bars 1–8. §2 puts the base radius at
8 u; §4 puts the camera at r = 0.97 (bars 1–2), 2.61 (t = 5), 5.21 (H2, where
the column's radius is 7.95) and 7.81 (t = 7.5). It only clears the body at
r > R around t ≈ 8.4. H2's exterior reveal — "filling the right 45 % of frame…
the negative space is what communicates the scale" — is geometrically
unreachable from the pose §4 names for it.
M.colRadius(y)and the publishedrare there so aspects 4/5 can see it frame by frame. - The locked frame cannot hold both the seed and the horizon. From
(52, 862, −64), r = 82.46: the seed at y = 780 is at elevation −44.8° and the
anvil top at y = 900 is at +24.7°, a 69.5° span that no lens in §4's column
covers. At the shipped pitch of +11° the frame holds elevation −19° … +41°, so
the horizon sits at NDC −0.337, the anvil top at +0.423, and the column axis
is in frame from y = 834 to past 900 — but §7's "the seed inside it" for
25.0–26.25 is 45° below the bottom edge.
M.lockSpan()reports all of it. - §4's y disagrees with the bus at two keys — −4.15 vs −4.20 at bars 1–2, and
−0.30 at t = 5.000 against a bus altitude of 0.00 while the same row fixes the
waterline at t = 5.000 exactly. The bus wins;
M.altCheck()reports both. - "Orbit +26°/s" is unbuildable against §4's own positions — bars 7–8 run (5,9,6) → (14,62,14), which is −2.1°/s of azimuth. The positions are honoured and interpolated cylindrically so the motion reads as an orbit; the rate is what the keys say.