What I built

StarWatch is an offline-only pocket stargazing PWA: open your camera, point it at the sky, and it names the star or constellation in frame. When city haze wins, a one tap fallback opens a full sky map driven by the same compass, gyro, and GPS Google-Sky-Map mode, no camera needed.

Touch Grass fit: the whole point is going outside after dark and looking up. The screen is a viewfinder, not the destination.

How it works

[GPS + compass + gyro + camera] → astronomy-engine + 43-star seed catalog → what should be there → classical star-point detection on a 320px frame (adaptive threshold over a background map, milliseconds, no model) → what is visible → nearest-neighbor match (24px tolerance) → verified / hidden / unknown → canvas overlay draws constellation lines ONLY through verified stars 

No lines through haze, ever. Geometrically-present-but-unseen stars are listed as "washed out." A covered lens yields an honest empty state instead of a guess.

An explicit anti hallucination gate (crossValidate) stands ready for the vision model: ephemeris proposes, detection verifies, the model may only confirm. A 0.99-confidence claim for a constellation that isn't there is rejected by construction and there's a test proving it.

The VLM spike we refused to ship

With hours left I tried to plug a small open VLM instead of training: Florence-2-base (232M, MIT, ONNX q4 via Transformers.js) captioned our Orion photo in ~2s on desktop CPU but only generically ("night sky filled with stars"), never naming anything. SigLIP zero shot scoring looked better at first: Orion 0.52 with descriptive labels. Then the control run with bare constellation names put Scorpius 0.354 over Orion 0.221 on a photo of Orion. Prompt-fragile, confidently wrong, and our own 0.5 gate would have rejected every claim. So it stays out: shipping that as "AI identification" would be a lie with good latency. The slot, the gate, and the gauntlet stay ready for a fine-tuned model that earns its place.

Verifying without a sky

Daylight didn't stop verification:

  • Synthetic skies: a rendered Scorpius frame with known ground truth plus a decoy plane-light runs the exact production path — all three stars named, decoy quarantined as unknown, lying model overruled.
  • Real photons: our detector over a NASA public-domain Orion photo found 200 star points in 16.7ms across 60/64 frame cells. Getting there caught a real bug: the first version flooded on gradients, which forced background subtraction before thresholding.
  • 60 tests green, strict TypeScript, PWA precache verified live.

The Brave war story

Testing stalled for a day on "compass silent" on Brave Android, motion sensors are blocked by default with no per site prompt, and since Chrome 50, plain orientation events carry relative drift that silently corrupts headings. Both got fixed (absolute only alpha, in app settings guidance, manual look around that hands control back when sensors wake). Boring, unglamorous, and exactly the kind of thing a field app lives or dies on.

Why open beats closed here

  • Runs with zero bars: the whole pipeline is on-device. A backcountry ridge with no signal gets the same app as downtown.
  • Location never leaves the phone: sky math needs your GPS no server ever sees it.
  • Swap, don't beg: catalog, detector, and (next) model weights are all swappable files, not API tiers.
  • $0 to run: static hosting, no inference bill, no keys to leak.

Honest scope

No model ships in this build the fine tune didn't fit the week and the zero shot shortcut failed our own tests, so I'm entering Overall + Best Use of Render only, no AI track claims. Classical detection tonight; open-weight vision when it passes the gauntlet.

Built irritation-first, tested against lies, and it works under the exact polluted sky I live under.