Log inGet started
World

Cold Start

Branchmain
Commitcfc095f0fc

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:

fieldwhat it is
pos rot fov near farthe pose the frame was drawn with
fovAuthored§4's FOV column — see "The FOV the engine will not draw"
alt depthfor aspect 1 to validate the bus against the camera, not to stage against (§4)
r azimuth pitch roll nx cut lockedthe pose, decomposed
focus focusSubject fStop focal cocFar shutter dofthe 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 }. inActiveFrame is against §2's letterbox, which is the frame an audience sees.
  • M.projectDir(t, alt, dir, …) → the same for a direction at infinity. Feed it ck.aux.sunDir to 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:

  1. 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.
  2. The shutter. @frame.camera DOES 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

  1. §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 published r are there so aspects 4/5 can see it frame by frame.
  2. 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.
  3. §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.
  4. "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.