← Projects

Starlapse

Built on 2026-08-13, the night the Perseids peaked, because no camera app would hold an exposure setting and none would say where to point.

Public, MIT: github.com/fortunto2/starlapse

The three hardware facts it is built around

Every design decision follows from these, and each contradicts something people bring from DSLR astrophotography:

  1. Aperture is not adjustable. iPhone lenses have fixed apertures. “Opening up” means picking a faster lens (main ≈ f/1.78 vs ultra-wide f/2.2), nothing more.
  2. A single frame caps at ~1 second. activeFormat.maxExposureDuration. Night mode’s 30 s is private API. Everything longer is stacked — which is better than a long exposure, because noise falls as √N and the same frames can be developed as pinpoint stars or star trails afterwards.
  3. Capture cannot run in the background. iOS force-stops AVCaptureSession. The honest substitute is isIdleTimerDisabled plus a dimmed screen.

The UI states all three rather than hiding them: a “10 minute exposure” is shown as “600 × 1.00s”.

What is worth stealing from it

The domain is platform-free. SkyKit (where things are in the sky) and StackKit (star detection, frame registration) import only Foundation and simd. 43 tests run in 3 seconds on a Mac with no simulator. A CLI, starlapse-sky tonight --lat … --lon …, drives the same code — CLI-first testing that actually earns its keep.

Tests anchored to external facts, not to their own output. Polaris altitude equals latitude; the synodic month falls out of the periodic terms; the Moon is new on the day of the 2026-08-12 solar eclipse. When a test failed, the maths was right and the test’s own constants were wrong — three of four raw timestamps pointed at a different day than their comment claimed. Now: no magic timestamps, only utc(y, m, d, h).

Alignment proven arithmetically. Star registration is tested against synthetic fields rotated by a known angle, recovering it to 0.05°. A misaligned stack just looks like a slightly soft photo — you would find out in a field at 2am.

Where to aim is not where the radiant is. Meteors streak away from the radiant, so the longest trails are 40° off to the side. The app computes that, keeps clear of the Moon, and picks a height a tripod can hold. That is the part that earns the app.

Lessons that generalise

Status

TestFlight, build 2. Store listing complete, asc validate clean. Release mechanics in docs/store/RELEASE.md.

Unproven where it matters: the time-lapse path has never run to completion and the detector has never seen a meteor. Everything above the domain layer is verified by reasoning and compilation, not by photons.

Sources

Related