Recent changes
What landed in each shipped version, most recent first.
v22.2.10 - the first real dome in the south
- The GEM offset sign follows the hemisphere. ASDM applied the offset
with the northern-hemisphere convention; south of the equator the declination axis direction in the model reverses, so d_ota pushed the slit the wrong way and looked "reversed". Three sync points on a 3 m dome at -36 deg missed by +17 / -24 / -36 deg; with the flip they miss by the same -4 deg (the dome's azimuth zero). "Pier-side sign" is back to meaning only "my mount reports SideOfPier the other way round".
- Sync points remember their geometry. A set recorded under another dome
radius, offset, GEM offset, pier sign or latitude - or before this version - is set aside as dome_sync.json.stale-<time>.json (never deleted) and the page says so; capture new ones.
- The Geometry & offsets tile is live. The slit wedge sits at the dome's
reported azimuth (it used to be painted at north whatever the dome did), with the ASDM target, the telescope's pointing and the remembered home.
- Find home is a job; Go home is new. Find home starts the driver's
home-sensor search and returns at once (a driver may block for the whole rotation); Starship remembers the azimuth the dome reports At home at. Go home rotates there without the search ([observatory] dome_home_azimuth_deg overrides). Both on the Dome page and as the dome_find_home / dome_home sequence actions; POST /api/console/dome/gohome.
- Slaving holds while the mount is parked (a parked mount at 7 deg
altitude had the slit heading for 81 deg), and the Slave button takes one click at a time.
v22.2.9 - what a customer's log taught
- Find Home says what the driver meant. A mount driver that answers
NotImplemented to Find Home (a ZWO AM5 behind an Alpaca bridge did, eight times in ten minutes) now gets "this mount driver does not implement Find Home - home it from its own controller or app, or use Park; turn off 'Find home after a mount power reset' for it", instead of the raw HTTP 400 body.
- A weather station that never answered is not a dead safety thread. With
Safety on and the Solo host left pointing at nothing, the heartbeat check escalated to safe-state every few minutes ("no safety tick") and tried to park a mount that was not there. While the Solo poll keeps erroring and no reading has ever arrived, the check now reports DEGRADED with the advice (the verdict is already UNKNOWN, so nothing runs); a poll thread that is silent altogether still escalates.
- **"Imminent OOM" needs a small amount of free memory, not just a small
percentage.** 8 % of a 32 GB PC is 2.6 GB; a customer's rig went to safe-state twice on it. The percentage rule now only counts below [health] sys_mem_critical_pct_max_mb (2048 MB); the 512 MB floor is unchanged.
- Weather-station errors are throttled in the audit log - the first of a
streak, then one every ten minutes - instead of 2,700 rows a day (90 % of that customer's log). The safety supervisor still sees every one.
v22.2.8 - Auto Flat meets a real dimmable panel; sky flats meet a real dusk
- **"Get from Sesame" works on a PC that has never met CDS's certificate
authority.** CDS Strasbourg moved to a certificate from the European academic CA (HARICA); Windows only fetches a root certificate when a browser first meets it, and Starship's own TLS never asks for it, so both Strasbourg mirrors failed with "certificate verify failed" - and the message showed only the third mirror's "timed out". Starship now ships its own root bundle, uses a mirror over plain http when its certificate still cannot be verified (a public catalogue lookup, labelled as such), reports every mirror's reason in plain words, bounds the whole attempt to about 20 s, and resolves NGC / IC / Messier numbers and common names from the built-in OpenNGC catalogue when no server answers at all.
- The panel light is given time to come up. The first metering frame was
taken the instant the calibrator was switched on: it read the camera's bias, and the exposure search ran away from that. The runner now waits for the light (three seconds, and for as long as the device says NotReady).
- A saturated metering frame is not a measurement. It used to scale the
exposure by target / 65535 as if it were one, and could win "closest to target". It now steps the exposure down by five and never wins.
- The panel brightness moves when the exposure cannot. When the shortest
allowed exposure still clips, a dimmable panel is dimmed (and brightened when the longest exposure is still too dark); each step shows in the log.
- Clipped or black "best effort" sets are never saved. A filter whose
metering never left saturation (or never saw light) is reported - "not saved: saturated even at the shortest exposure and lowest brightness" - instead of banking fifteen useless frames labelled FLAT.
- A panel flat wants an exposure of a second or more: a PWM-dimmed panel
delivers its light in pulses, and an exposure only a few pulses long is quantised in level and banded by the rolling shutter. If the lowest brightness still needs milliseconds, put a sheet of paper or ND film over the panel. The Auto Flat topic says so.
- Sky flats: the mount gets ready while the roof opens. The first real
dusk run lost its twilight to a roof that takes over three minutes: the runner waited for it, then unparked and slewed. It now issues the roof command, unparks, slews to the flat spot and stops tracking during the roof's travel, and looks at the sky the moment the roof reports open. A site whose roof can hit an unparked mount ([observatory] roof_motion_requires_safe_mount) keeps the old order.
- A roof that says it is opening is waited for (up to ten minutes);
verify_shutter_timeout_s is only the patience for a roof that says nothing. The run used to end on "roof did not report open within 60 s" with the roof half way.
- A weather station that flaps does not end the run. If the verdict turns
UNSAFE again while the roof is opening (the hardware shuts it again), the run goes back to waiting for SAFE and asks again while the window lasts.
- "The roof opened too late" is said in those words, with the sun altitude
at which the camera first saw the sky, when every filter is already out of reach at the first look - not "no flats saved (4 of 4 filter(s) failed)".
- The camera cooler is started twenty minutes before the window (`[flat]
sky_cool_camera, on by default; towards [cooling] default_setpoint_c or the set-point the camera still holds). The run never waits for it; a set about to be shot warm carries a warning and the result records ccd_temp_c`. The first real set was shot at +25 C with the cooler off: hot pixels do not stack out.
- Long sky flats stay on target. The exposure now allows for the sky
changing during the frame. Nothing changes at 2 s; a 41 s frame at dusk came out 7.5 % under target before, and each frame lower than the last.
- **
GET /api/fits/listwithsince,untilorprefixlooks into
subfolders** whatever [fits] watch_recursive says: nine new flats in flats_l/ were invisible to "what was written in the last ten minutes?".
v22.2.7 - sky flats, and what two more nights on the rigs taught
- Sky (twilight) flats. The Auto Flat page has Sky flats (dawn) and
Sky flats (dusk) buttons, the API has POST /api/console/flat/sky, and a plan can run the sky_flats action in on_start (dusk) or on_end (dawn). The runner waits for the sun window ([flat] sky_sun_alt_min_deg .. sky_sun_alt_max_deg, default -7 .. -1.5 deg), sets the rig up three minutes before it opens - roof claim, flat cover, unpark, a slew to the flat spot one hour from the meridian away from the sun at the site's latitude, tracking off and read back - and shoots late and short (0.5 .. 5 s): with the mount stopped a star's trail is as bright per pixel in any twilight, so what a brighter sky buys is a short trail. Each filter is probed at two exposures (the rate is the slope, the camera's bias the intercept), the filter about to leave its exposure window is shot first (the most sensitive at dawn, the least at dusk), the brightening rate is learnt from the run's own frames, and every saved frame is metered from the file. Off-target frames are moved to skyflat_rejects (never deleted). The run stops the moment the safety verdict goes UNSAFE or a frame comes out dark - the roof closing - instead of saving dark flats. See the Auto Flat topic.
- Auto Flat waits for each frame. The panel runner counted a flat as saved
when the capture job was accepted, so a plan of 12 frames fired 12 captures in five seconds at one camera and three files landed. Each frame is now on disk before the next is taken. POST /api/console/capture accepts frame_type and subfolder (the console had them; the route dropped them).
- Guiding through the imaging optics. The sequencer refocus pauses guiding
before the focus sweep and restarts it after (a sweep through an OAG or a guider on the imaging OTA defocuses the guide star and PHD2 chased a donut). Start-guiding deselects the old star and picks a fresh one in the central part of the guide frame (fresh_star, star_central_frac in the plan's guiding policy) instead of reusing a star that is not there after a slew or a flip. The pre-sub guiding hold now waits for PHD2 to finish settling; no dither is attempted on a lost star or a guider that is not guiding; the settle tolerance is [phd2].settle_px (default 1.5 px) or the plan's settle_px; and the driver's RMS no longer includes guide steps taken during a dither.
- Meridian flip re-centre. The final re-slew of the plate-solve re-centre
sent of-date coordinates as J2000, precessing them a second time (about 17 arcminutes at 2026). Fixed.
- Refocus every N frames survives a re-centre. A precise re-centre slew to
the same target no longer resets the refocus counter or counts as a new target in the run outcome; a checkpoint-resumed run refocuses before its first sub.
- A sequence parks before it closes the roof, whatever order the plan lists.
A plan whose End phase had Close roof before Park mount never parked: the console refuses to close the roof on an unparked mount, the refusal cancelled the rest of the phase, and the park - the one action that would have satisfied the interlock - was skipped ("MAKE-SAFE INCOMPLETE ... close_roof failed ...; skipped: park"), leaving the mount tracking into the morning. The on_end, on_error and on_suspend phases now order that pair by [observatory] park_before_roof_close (park first by default), as the blockscript engine always has, and say so in the journal.
- Stop leaves an idle focuser alone.
halt_all(a sequencer Stop) only
halts a focuser that is moving; on a GRBL focuser a halt is a feed hold that silently ignored every later move until a reconnect.
- The plan's end time is honoured between subs.
end_by_utcand
end_sun_alt_deg were only checked between steps, so a 110-sub expose step that started a second before the end time ran its full length. The step now ends after the current sub and the run ends as planned.
- A night colder than the cooling setpoint settles. A sensor at or below
the setpoint with the cooler idle counts as settled (a TEC cannot heat); the wait notes it instead of failing the run after the settle timeout.
wait_untilvalidation matches the runtime (safe,alt_min_deg,
time, time_local, sun_altitude_deg), and sun_altitude_deg now runs: wait until the Sun is at or below the given altitude.
/api/fits/listpages a night:limit,offset,since,until,
prefix.
- The dashboard no longer grades subs during a run. The per-frame HFD
request (about 1 GB and minutes of CPU on a 26 Mpx sub) is skipped while a run is active; grade on demand.
v22.2.6 - the folder layout setting survives a Settings save
- v22.2.5 added
[fits] layoutandservice_dir_wipe_on_start, but the config
API's field list did not carry them: the Settings page always showed "Everything in the images directory" whatever the file said, and saving any setting could revert a night_target file to flat. Both keys are in the settings document now, and changing the layout reports that a restart is needed (the listing mode and the service-folder wipe are read at start).
- The same audit found three sibling keys with the same hole and closes them:
[camera] imagearray_fast_path and [health] sys_mem_critical_polls (v22.2.3) were missing from the settings document, and [mount] precess_targets (v22.2.0) was missing from the document AND from the file writer, so any save silently switched precession back on. A test now pins every scalar config field to the settings document and to the written file.
v22.2.5 - a frame is labelled with the filter's name, and filed by night and target
- Images by night and target.
[fits] layout = "night_target"(the default for
a fresh install; an existing config keeps flat until you change it in Settings) files science subs in <night>/<target>/ - the night rolls over at local noon so a session across midnight stays together - calibration frames in <night>/calibration/, and pointing / recenter / autofocus / focus-star frames in one _service/ folder that Starship empties when the service starts. Nothing already on disk is moved.
- The filename and the FITS
FILTERkeyword carry the Filter Library name.
A wheel whose driver reports generic slot labels (a QHYCFW says "Filter1" to "Filter29") had every sub of an LRGB night named and tagged Filter4, although the plan asked for b and the library resolved it to that slot correctly: the capture stamped the driver's name. The label is now the library name for the slot that was used, then the driver's name, then the text you typed.
- Short forms resolve.
l/lum,r,g,b,h/ha,o/o3,s/s2
(and the driver's own slot names, and slot numbers) all find the right library entry, in captures and in sequencer filter steps alike, so renaming your library to Luminance, Red, Green, Blue, Ha, OIII, SII does not break a single saved plan.
v22.2.4 - the help describes this build
- The public edition documents the public edition. The Professional modules
are compiled out of the public build, but their help topics (collimation, Navigator, Users & Access, Research) were still listed, served and shipped, and shared topics carried Professional passages. Topics are now keyed to the capability that defines them and passages are fenced, both dropped when the capability is absent; the public installer ships a copy of the help with them already removed, and the build check refuses a build that leaks one.
- New topic: HTTP API (scripts & AI agents). Every route the server exposes,
generated from the route table at release time, with request bodies, response notes for the routes that matter, and the playbook an automation agent should follow (connect, validate/save/start a plan, poll state and health, make safe).
- Getting started now describes the installer, the activation screen and
what has to be on the PC (ASCOM Platform, PHD2, ASTAP) instead of a source checkout; the Roadmap reflects v22.2.4; the Configuration file and Health monitor topics document imagearray_fast_path and sys_mem_critical_polls.
v22.2.3 - the ImageArray no longer costs a gigabyte
Two unattended nights on an 8 GB rig, and a customer's report, had the same shape: the first bin-1 download of a 26 Mpx frame pushed system memory under the health monitor's floor, and the safe-state parked the mount mid-run.
- The COM ImageArray read costs one frame, not forty bytes per pixel.
pywin32 converts the camera's SAFEARRAY into nested tuples of Python ints inside the property call itself (~1.1 GB for 26 Mpx, for the seconds numpy needs to read them back). Starship now calls IDispatch::Invoke on the very interface pointer pywin32 holds and copies the SAFEARRAY into numpy in one step: about 200 MB peak and 0.1 s at 26 Mpx instead of ~1.1 GB and 3 s. Same thread, same call, same values. The classic conversion stays as the fallback (logged once) and [camera] imagearray_fast_path = false turns the new path off for a driver that misbehaves. Needs comtypes (bundled).
- A memory dip must persist before it is an emergency. The system-memory
floor now has to hold for [health] sys_mem_critical_polls consecutive polls (default 3, i.e. 15 s) before it counts as imminent OOM, and a dip during a frame download never counts at all - the download is the dip.
- An escalation stops the running sequence. When the health monitor does
make the rig safe, it stops the run first (journal safety.stop, reason on the run) instead of parking underneath a sequencer that kept exposing.
v22.2.2 - the meridian flip finishes the job
The first fully unattended night on v22.2.1 (Antwerp, 2026-09-10) put a real meridian flip through GS Server: the SideOfPier write flipped the mount and the pier side verified. Two things around the flip were wrong, both fixed and pinned by tests that fail on v22.2.1.
- The recenter after the flip read the capture path at the wrong level.
The capture worker returns its result under job_result; the recenter looked one level up, so every real flip with recenter_after_flip = true ended in "recenter capture produced no file path" -> on_error -> park, with the recenter frame sitting on disk and the flip already verified.
- An expose that ends early for the meridian now finishes its job. A
multi-sub expose stops at the meridian so the flip (or halt) can happen between subs. The run loop then moved on to the NEXT step, and the handler only runs at the top of an expose step, so when the truncated expose was the plan's last one the remaining subs were dropped, nothing flipped, and the run reported SUCCEEDED with the mount still tracking on the pre-flip side. The loop now runs the meridian handler right there and resumes the same step for the subs it still owes (journal: meridian.resume); a halt or a failed flip ends the run exactly as before.
- The mid-step "meridian action due" check knows when the flip is done. It
ignored the pier side, so after a successful flip it stayed true for the rest of the target and every later multi-sub expose ended after one sub.
v22.2.1 - the park that did not happen, told straight
A customer's EQ6 on GS Server "did not park" at the end of the night. His diagnostics bundle could not say why, and neither could Starship: the journal read park: timeout - timed out after 120s waiting for mount parked (at_park=False), the rest of make-safe was cancelled, and the run still ended with "Sequence complete". Two things were wrong on Starship's side, and one is worth knowing about GS Server.
- The park wait now says what it saw. It reads
AtParkandSlewinglive
from the driver once a second instead of trusting the five-second snapshot, remembers whether a park slew was ever seen, and a timeout names one of three very different nights: the driver accepted Park but never started a park slew, the park slew ran but ended without AtPark (aborted or interrupted), or the park slew is still in progress. The budget comes from [observatory] verify_park_timeout_s (it was a 120 s literal that ignored the setting).
- A mount that already says it is parked will not move. Every driver
treats Park as a no-op then. The journal, and the dashboard Park button, say so before the wait passes in one poll, so "I pressed Park and nothing happened" gets an explanation instead of a green tick.
- An incomplete make-safe is a CRITICAL alert. A park (or any make-safe
action) that fails still cancels the rest of the phase, because a closing roof over an unparked mount can strike the telescope. Until now that was one journal line: the run ended SUCCEEDED, the alert said "Sequence complete", and the roof stayed open. It now sends a critical notification naming the failed action and every action it skipped, the run outcome carries MAKE-SAFE INCOMPLETE, and the completion alert says so in its subject. (Alerts must be enabled to reach your phone.)
- The dashboard Park button reads back. A manual park is followed by a
live check and a mount_park verify line in the console log with the reason when the mount did not get there.
- A cancelled slew can no longer cancel the park. Pressing Stop during a
slew cancels the slew job, which sends AbortSlew from its own thread on its next poll. That could land after on_end had already issued Park, and per ITelescope an AbortSlew aborts a park in progress: the mount stopped part-way and "did not park". Stop, weather suspend and the console's abort now wait (a few seconds at most) for a cancelled mount job to actually stop before anything else commands the mount.
- The roof interlock no longer falls open on an unreadable AtPark. A mount
whose AtPark could not be read this poll is "cannot confirm", not "not unparked"; the close is refused unless you confirm it.
About GS Server. Its source shows why a park can silently do nothing: Park is ignored when GS Server already believes the mount is parked - and it remembers that across restarts, assuming the axes are exactly at the stored park position - and when no park position is selected; a park slew that stalls is still recorded as parked. If your mount does not park: look at the GS Server window before you send Park (if it already shows Parked, unpark and park again), make sure a park position is selected in the dropdown on its main window (when GS Server starts unparked it picks the first position in the list, not the one you used last), and keep your park position above the horizon limit. Starship's new park messages tell you which of these you are looking at.
v22.2.0 - nothing answers "fine" when it does not know
This release carries the v22.1.0 polar-alignment rebuild (finished and verified on 2026-07-31, never published on its own) plus everything found since. Two more device audits and a sequencer audit went line by line through what the equipment layer, the capture path and the sequencer do when a driver misbehaves, and confirmed twenty defects. They share one shape: something reported success, or "done", or "healthy", on evidence it did not have. Every one of them is now pinned by a test that fails on the v22.1.0 tree.
Equipment: a hung driver no longer takes the night with it
- The mount and the roof are served by their own COM worker. ASCOM devices
used to share one worker thread. On 2026-08-17 a flat-cover driver blocked inside Connect for over twenty seconds, and the park and close-roof requests queued behind it - an accessory could veto making the observatory safe. There are now three lanes: the mount, the roof or dome, and everything else. Each has its own queue, liveness beacon and watchdog, so a wedged accessory leaves the mount and the roof responsive.
- A request sent to a wedged lane fails at once, with the reason. Before, it
waited forever - which is how a blockscript's device-lost retry could hang the engine thread for the rest of the night. The queue is drained with ASCOM <lane> worker wedged: unresponsive inside <device>.<op>, and a disconnect that could not complete is reported instead of assumed.
- Health and Guardian see the wedge. The watchdog used to demote a wedged
lane's devices to "not connected", and every health check skipped not-connected devices, so /api/health said HEALTHY while the mount was frozen. A wedged lane is now a CRITICAL condition with one notification, the "reconnect stale device" rung actually reconnects on both transports within a 45-second budget, and Guardian's blast-radius guard treats a device it cannot read fresh as BUSY - it will not cut a shared relay under a mount whose driver is hung.
- A poll in which every read failed is a failed poll. It was being
published as a fresh, error-free success with the properties wiped, which every safety check read as "nothing to worry about". Both transports now report it as unreadable, and the console status lists what is unreadable.
- Connect tells the truth on both transports. A device that never reached
Connected was still reported ok, so a cold start could log "connected 5/5", unpark, open the roof and then fail every exposure with "no camera connected". The connect now fails with the reason. Platform 7's asynchronous connect also gets the budget the old blocking write always had (up to three minutes while the driver says Connecting), so a slow camera is slow rather than failed, and a half-connected device is disconnected rather than left dangling.
- Cooling on connect never blocks Connect all. The
[cooling]
on_connect_action write is no longer issued inside a connect sequence. The camera-connected event only arms it; it runs afterwards as its own bounded job (visible in the job list as cooling_on_connect and in /api/ascom). Measured on the rig on 2026-08-15, a ToupTek camera accepted the cooler write and never returned, which left Connect all hung at 2/5. If the driver blocks, the warning names the camera and the setting, and the other devices on that lane say which write blocked them. The action is keyed on the driver's own per-connect event, so it can no longer re-fire "cooler off" mid-run when a watchdog demotion clears.
- Platform 7 DeviceState. A Platform 7 driver is asked for its whole
operational state in one call per poll instead of one call per member - up to ten fewer synchronous COM calls, or HTTP round trips, every tick. It is self-checking: for the first three quiet polls after connect Starship still reads every member the old way and compares; any disagreement sends that device back to per-member reads for the session with one warning naming the member. Drivers without it are read as before. Each slot in /api/ascom and /api/alpaca reports interface_version and a device_state block. Kill switch: use_device_state under [ascom] and [alpaca] (also on the Settings page).
- ToupTek cameras: no more exception spam while idle.
PercentCompleted
is only read while the camera is busy; some drivers throw when it is read idle, once per poll, all night.
- Your mount's coordinate frame is honoured. Every target Starship commands
is J2000 - the resolver, the catalogue, focus stars, plate solves - but most ASCOM mounts expect the equator of date, about 22 arcmin away in 2026. Slews and syncs to a mount that reports a topocentric EquatorialSystem are now precessed first, so blind pointing lands and a plate-solve sync no longer writes a frame offset into the mount's model. A mount that reports J2000 gets the numbers unchanged; one that reports another frame, or cannot say, gets raw J2000 and one warning. Coordinates read back from the mount itself (the meridian flip, the return from a focus star) are never converted. Kill switch: [mount] precess_targets = false.
Capture: a failed exposure is reported when the camera says so
- Camera errors fail the frame immediately. A driver error on
ImageReady
- the specification's only failure signal - and a camera entering its Error state were retried as "transient" until exposure time plus the download timeout, then reported as "did not become ready", which Guardian did not classify as a device fault. Three attempts at a 300 s sub was eighteen minutes of dark sky per episode. The frame now fails within seconds with the driver's own message ("camera error ...", "camera timed out ..."), and transport blips are still tolerated for a short grace.
- A COM read inside the capture cannot park the sequencer for the night. The
raw ImageArray and ImageReady reads had no time bound; a hung download kept the job RUNNING, with no error, no Guardian ladder and no alert, until a six-hour ceiling. They are now bounded by [camera] download_save_timeout_s and a hang fails the job as a timeout.
- A rejected gain or offset fails the frame. A camera in Gains-index mode,
or a value outside its range, silently shot the whole night at whatever gain it held, with one warning per frame in the log. The frame now fails with camera rejected gain=... so the plan's error policy runs. Binning is still clamped by the camera and recorded honestly.
- Alpaca image errors carry the server's text. A failed ImageBytes download
reported only a number; the server's UTF-8 message (a 32-bit ASCOM Remote out of memory, "no image available") is now shown, a memory error gets the same guidance as the JSON path in English, German, Italian and Spanish, and a server that answered the ImageBytes request with JSON is not asked again: from the second frame it gets the compressed JSON request directly, instead of an uncompressed 250 MB frame on every sub.
Sequencer: waits and make-safe steps act on evidence they have
cool_cameracan settle. Withramp_minutesabove about five the fixed
300 s wait budget could never outlast the ramp, so the obvious plan the editor offers timed out every night at on_start. The budget now follows the ramp, and a timeout cancels the orphaned ramp. Per-action timeout_s, wait_s and settle_s are accepted on every phase action; the plan normaliser used to strip them, which left open_roof and close_roof with a hard 60 s cap that failed slow-but-healthy hardware.
- Make-safe finishes. A
cooler_rampinon_endoron_errorbailed on
the stop flag and cancelled the rest of the phase - park, close roof, close cover never issued. The warm-up in on_end ran BEFORE the park, so after a meridian halt the mount kept tracking past the meridian for the whole warm-up. Order is now: your on_end actions (park first), then the policy warm-up; after a halt the mount's tracking is stopped; a second Stop no longer cancels a park already in progress; and a script run in a make-safe phase is not aborted by the stop flag.
- Cover and dome waits do not accept silence as "done". An unreadable cover
or shutter state counted as complete (the v22.0 fix conflated the two), and the observatory state used for these decisions is now read fresh instead of from the five-second snapshot. blind_solve and blind_resync waited on a job kind that was never created and returned "done" before the mount had moved; the phase now follows the job it actually started.
- Weather resume opens the roof before it moves the mount. The re-point
after a weather hold unparked and slewed to the target BEFORE on_resume ran - under a closed roof. on_resume runs first, then the re-point.
- Meridian flip execution. The first live flip on the rig failed because the
driver re-slewed to the same pier side and the (correct) post-flip check halted the run. Before the flip re-slew Starship now asks the driver which side it would land on (DestinationSideOfPier); if that would not flip, it writes SideOfPier where the driver allows it, and otherwise holds exposures for up to 30 minutes waiting for the driver's own flip while watching the reported side. The post-flip verification is unchanged and still halts on a side that did not change.
- Plan meridian mode
inherit. A plan's meridian mode silently overrode
[mount] flip_mode = "off". Runtime precedence stays - an explicit plan mode is the operator's decision - but the silence goes: inherit is the editors' default and is carried explicitly, the run-view chip resolves the mode the same way the acting path does, and a run whose plan widens the mount setting shows a banner for the whole run and journals it once.
- Scheduler credit counts the whole run. Campaign progress was summed from
the sequencer journal, which is capped at the last 200 events for the UI, so a long chunk silently lost its earliest exposures and an unattended campaign could never complete. The sequencer publishes a total over the full journal and the scheduler uses it.
Polar alignment
- An optional third position. Two solved frames carry no spare
information: a hand slip in Dec during the turn reads as polar error, and nothing inside the pair can tell. After position 2 Starship asks Add a third position (recommended) or Continue with two. Turn a further ~60 degrees the same way; three positions give two independent turns whose axes must agree. The disagreement is reported as the spread and the run refuses above [polar] max_spread_arcmin (0.75'), naming the lowest position when refraction near the horizon is the likely cause. It is the only cross-check that works with no mount connection at all. positions = 2 never asks.
- Southern hemisphere, fixed. The live readout and the "mount is not
tracking" detector rotated the field about the hemisphere-signed pole. The sky turns about the north pole of date everywhere, so on a southern rig the readout walked about 0.5'/min hands-off and an untracked mount could not be detected. Both use the pole of date now, and the two-position fit accounts for the minutes between its two solves (up to ~1' at 3 degrees off with 90 s between them).
- Two smaller ones: the polar job in this tree died silently right after
position 1 (it wrote progress to a job field that did not exist); the tracking-off halt crashed in its own log line and marked the job failed. Both run cleanly now.
Diagnostics
starship-crash.txtstays readable. Some .NET drivers raise handled
first-chance exceptions that the fault tracer cannot filter, so the file grew to megabytes of identical dumps and buried the one real access violation. It is now compacted every time the tracer is armed (the handled-exception dumps are dropped and counted in one line), capped at 1 MiB with one rotated generation, and the diagnostics bundle carries the filtered version with a count of what was omitted.
Skyhunter
- Deep-space satellites are propagated correctly. Objects with a period of
225 minutes or more (geostationary, Molniya, GPS) use the SDP4 model, validated against the published test vectors. Before, they were skipped, and propagated with the near-earth model they were wrong by thousands of kilometres. A Molniya gets ordinary rise, peak and set passes; an object that never sets over your site is listed once as always up rather than as a fake day-long pass. The bundled satellite catalogue is refreshed (space stations and the visual-pass set).
Under the hood
- A time-aware target model (fixed, alt/az, ephemeris, TLE) and a pluggable
observing-programme interface are in the tree; the dispatcher is not yet wired to them, so scheduling behaviour is unchanged.
- The FITS writer accepts caller-supplied header cards (none are added in this
edition).
- Design note for the next release: an out-of-process ASCOM driver host, so a
driver that blocks inside a COM call can be judged dead by its process handle and replaced without restarting Starship. Designed, not built.
New configuration keys, all with safe defaults: [mount] precess_targets (true), [ascom] / [alpaca] use_device_state (true), [polar] positions (3) and max_spread_arcmin (0.75). No changes are required to an existing config.toml.
v22.1.0 - polar alignment, rebuilt
Robert, running v22.0.0 in Germany, reported polar alignment landing "way off the real celestial pole", and asked whether it was a north/south hemisphere problem.
It was not. The maths was right in both hemispheres and the tests prove it. The problem was the slew. The old routine drove the mount itself across three to five RA positions and fit a circle through the solved pointings, which made it hostage to everything about that motion: mounts Starship cannot drive, a mis-reported slew completion, and - most damagingly - a meridian flip landing mid-sweep, which puts the measurement points on two different arcs and fits an axis nowhere near the pole.
So the slew is gone. Starship no longer moves your mount during polar alignment at all.
The new routine solves one frame, asks you to turn the mount about 80 degrees in RA by hand, and solves again. Turning in RA rotates the whole camera frame rigidly about the polar axis, so the rotation carrying the first frame onto the second is a rotation about that axis - and that axis is the measurement. The plate solve's roll angle is what makes two positions enough where position alone would need three.
Turn too little to condition the fit and it refuses, and tells you how far you actually turned. A confidently wrong pole is worse than no answer — which is the principle behind the other two guards as well:
- If the mount is not tracking, it stops. Once the axis is measured,
Starship reads any movement of the star field as your knob adjustment. With tracking off the field drifts on its own — and the displayed number does not run away, it converges tidily onto your target while the real axis walks off the pole. You would be congratulated while being steered wrong. Starship now spots the signature of an untracked field in the solved frames themselves and stops with an explanation. That works even with no mount connected, which is exactly where a Tracking flag is unavailable.
- If the mount's declination moved during the turn, it refuses. Two frames
carry no redundancy, so a Dec slip is mathematically indistinguishable from polar error — about 1.3' of it fabricates 1' of error. When a mount is connected Starship compares its Dec either side of the turn. When there is no mount, it records that the turn's purity was unverified rather than implying it was checked.
Then it simply keeps solving and shows you the error, live: the pole at the centre, your axis as a dot, a target ring at your tolerance, and a short trail so you can see which way you are driving it. Altitude, azimuth and total in arcminutes underneath. You turn the knobs and watch the number fall.
It deliberately does not tell you which bolt to turn. Which knob moves which way is mount-specific and easy to get backwards; a number that falls while you turn is unambiguous, and if it rises you turn the other way.
Two consequences worth knowing:
- It now works with mounts Starship cannot drive - including a mount on a
hand controller with no ASCOM connection at all. It needs a camera, a plate solver, and your site coordinates. Nothing else.
- Tracking should be on during the live readout. Once the axis is measured,
any movement of the star field is read as your knob adjustment; if the mount is not tracking, the field drifts on its own and the number quietly goes wrong. If a mount is connected and reports tracking off, the panel now says so.
v22.0.0 - the equipment layer, told straight
Two independent compliance audits went through every line of how Starship talks to your equipment, against the ASCOM Platform 7 and Alpaca specifications. They found the same mistake in sixteen places, and it has two halves:
Starship could not tell "your driver has no such property" from "I asked and got no answer." Both arrived as the same silence, and silence was read as "not applicable, carry on."
Starship could not tell "the operation finished" from "it has not started yet." Many checks read a status that is refreshed every five seconds, so they saw the state from BEFORE the command was sent and called it done.
What that was actually doing to your nights
- A slew that never completed was reported as SUCCESSFUL. If the mount
stalled, hit a limit, or the driver reported a fault, Starship waited its full ten minutes and then said "on target" - and the sequence went on to plate solve, sync or expose on the wrong piece of sky.
- The post-flip safety check could be skipped by a single bad read. After a
meridian flip Starship confirms the mount really changed sides. A mount that cannot report which side it is on, and a mount whose reading just timed out, looked identical - so a momentary glitch declared the flip verified. It now reasons the other way round: if the mount could report its side going in, it can report it coming out, and anything else halts the run.
- The meridian policy could silently switch itself off. If the hour angle
could not be worked out, the whole flip-or-halt decision was skipped with no message. Starship now falls back to computing it from your site longitude, and stops the run if it genuinely cannot tell.
- **Rotators, flat covers and domes reported "done" the moment the command was
accepted** rather than when the hardware stopped moving. A flat cover takes ten to twenty seconds; the first frames were being exposed through it.
- Cooling down could never succeed. The wait for the camera to reach its
setpoint was reading a value that was never there, so it could only ever run out its five-minute limit - on a camera that had cooled perfectly.
- Your FITS headers recorded what was ASKED FOR, not what the camera used.
If a gain was rejected or a binning was clamped, the frame was still stamped with your request. Those frames then get matched against the wrong darks and flats, and it compounds quietly across every session. The header now records what the sensor actually did, and says so when the two differ.
- A single failed reading could disable power control by publishing "this
device has no switches" as though it were a healthy answer - which also disarmed the automatic recovery that exists for exactly that kind of glitch.
- A hung device could look perfectly healthy. A failed status read was
re-stamped with the current time, so nothing ever aged, and the self-healing never triggered.
- An unreadable focuser position was treated as position zero, which turned
a routine autofocus into a full-travel move to the inward hard stop.
- After a weather pause, Starship re-pointed a mount that was still parked.
The refusal was noted and the run carried on - exposing at the park position for the rest of the night, with the target's name and coordinates written into every file.
Two more, found along the way
Stopping a run did not make the rig safe. The on-end phase - park, close the roof, close the cover - is supposed to run whether a sequence finishes or is stopped. It did not: pressing Stop, reaching a dawn deadline, a meridian halt or an on-failure "go to end" all skipped it entirely. It now runs.
A plan asking for FLAT frames was taking DARKS. Plans can specify light, dark, flat or bias, but everything that was not a light was being shot with the shutter closed and labelled DARK - so plan-authored flats and bias frames were both wrong.
Also
- Platform 7 connections now cover direct ASCOM drivers too. v21.0.7 brought
the modern non-blocking connect to network (Alpaca) devices only; it now applies to locally installed drivers as well, so a slow camera is no longer reported as a failed one there either.
- ASCOM Remote servers with a username and password now work. Starship
previously could not connect to one at all. Set them under [alpaca] as auth_user and auth_password.
- Large images can transfer compressed, which makes a real difference to big
sensors over a network bridge.
---
Upgrading: install over your existing copy. Your configuration, plans and databases are left alone.
A note on this one: these changes touch the layer that talks to your mount and camera, and there are a lot of them. They follow the published specifications and are covered by tests, but they are new. If anything behaves oddly, please send a diagnostics bundle (Help -> Diagnostics).
v21.0.7 - ASCOM Platform 7 connections, and slow devices stop being errors
Two changes to how Starship talks to equipment, both aimed at the same complaint: a device that is simply SLOW was being reported as a device that had FAILED.
Connecting now uses the modern, non-blocking method
ASCOM Platform 7 added a proper asynchronous way to connect to equipment, and the specification is explicit that the old way is deprecated for applications like Starship:
> "writing to Connected to effect connection and disconnection is now explicitly > deprecated for clients but must still be implemented by drivers to ensure > backward compatibility with earlier software." > - ASCOM Master Interfaces 1.0.24, s3.2
Starship was using the old way. The problem with it is not tidiness: it BLOCKS for as long as the device takes to connect, and a camera doing USB enumeration and cooler start-up routinely takes 10-30 seconds. Past a fixed limit Starship gave up and reported a failure, on a device that was connecting perfectly well.
Starship now asks each device which interface it implements and uses the asynchronous method where it is available, waiting properly for completion instead of guessing. Older equipment keeps the previous behaviour - drivers are required to support it, so nothing is left behind.
Slow commands are given the time they need
The ASCOM Remote documentation asks clients to use three different waiting times: one to open the connection, one for quick questions like "what is your temperature", and a longer one for genuinely slow commands.
Starship used a single five-second limit for everything. Five seconds is right for a temperature reading and far too short for connecting a camera, moving a filter wheel, or homing a dome - all of which were reported as timeouts.
Slow commands now get a longer allowance that scales with your configured timeout, while status polling stays as responsive as before.
v21.0.6 - Alpaca images decoded correctly
If you captured with an Alpaca camera on v21.0.5, check your frames. They may be unusable, and this release is the fix.
Alpaca frames could come out as flat garbage
On v21.0.5, images from some Alpaca servers decoded into nonsense - a user saw every pixel come out as the same value, with the file's size and 16-bit header all perfectly correct. Nothing reported an error.
Alpaca labels its pixel format with a number, and two of those numbers were listed the wrong way round in Starship: the code that reads them believed the label for "16-bit unsigned" meant "8-bit". It read every frame one byte at a time and produced a full-size image of rubbish.
That mistake was in Starship for a long time but never reachable, because the old code looked at a different field that never carried those two values. The v21.0.5 fix - which was itself needed, and did fix the failed downloads - is what made it reachable.
Starship now also refuses a frame whose size doesn't match the format it was told to expect, instead of decoding it anyway. That is what turned this into silent corruption rather than an error message, and it should not be possible to repeat.
v21.0.5 - reliability fixes
A fix-only release. Everything here was found either by a customer report or by testing the COMPILED build rather than the source - which is where several of these could only ever have shown up.
Starship could die while the dashboard kept answering
If you use an ASCOM camera and do not have a separate filter wheel, Starship could crash outright - not raise an error, but terminate - and it needed no action from you at all. The dashboard polls the filter wheel status; with no standalone wheel to ask, it asked the camera whether it had one built in, and that question was being put to the camera from the web server's own thread rather than the thread that owns the driver. On Windows that is not an error you can catch. It is a hard stop.
What you saw: the web page still loading and responding, while nothing behind it worked. Restarting didn't help, because connecting automatically at startup put it straight back into the same state.
Reported by a user whose crash report contained fourteen of these in one session. The same unsafe pattern was also sitting behind the Abort button and the cooler controls, where nobody had hit it yet; all three now go through the driver properly.
If you have a filter wheel, you were never affected - which is why this survived so long.
Alpaca cameras: every capture failed with "pixel data short"
A camera served over Alpaca could connect, cool, and expose, and then fail every single download with ImageBytes pixel data short: N < 2N - always exactly double. Reported against three separate rigs (an ASI2600MM over an ASIAIR Alpaca bridge, and an ASI6200MM over ASCOM Remote on Windows).
Alpaca sends two type fields: the camera's logical pixel type, and the type actually used on the wire - servers routinely narrow to 16 bits to halve the bandwidth. Starship sized its buffer from the logical type, so it demanded twice the bytes the server had sent, and rejected a perfectly good image.
This is separate from the v21.0.4 fix, which addressed the JSON fallback path. These rigs negotiate the binary transfer successfully and then failed here.
Also fixed in the same place: when a server narrows into a signed 16-bit type, every pixel brighter than half-scale was being read as negative - the brightest parts of the frame coming out darkest.
Switching between saved profiles could break an install
Reported by a customer running two rigs (a native ASCOM machine and an Alpaca bridge): saving each as a profile and switching between them left Starship unusable, and reinstalling didn't help.
Loading a profile copied it straight over your config.toml with no checks. If the profile couldn't be read, the unusable file landed on disk anyway and the next start fell back to built-in defaults - which meant the dashboard stopped answering on the network, the site coordinates reverted to 0,0 (quietly breaking focus-star slews, airmass and the scheduler) and the device transports switched off. Reinstalling didn't help because the installer deliberately never touches your settings, so the broken file survived.
Now: a profile is read and checked BEFORE anything is replaced, so one that can't be used changes nothing and tells you why; a backup is written first, so Restore backup can undo a switch; and your web address and port stay put - switching rigs no longer moves the dashboard out from under you.
Your saved passwords were readable over the network
The settings page fetches your configuration from the app, and that copy included your email (SMTP) password, your Pushover keys and the dashboard password hash in plain text. With dashboard sign-in turned off - the default - anything on the same network could read them. They are no longer sent; the page is told only whether each one is set.
Saving Settings no longer wipes a stored password either. Because they are no longer sent to the page, they came back empty on save; a blank password now means "leave it alone", and that rule lives in the app rather than the page, so it holds no matter what does the saving.
After an unexpected restart, Starship could come back as a different rig
If you start Starship with a specific configuration file and it restarted after an unexpected exit, it came back without that file - falling back to whatever it found, or to built-in defaults. Same observatory, different settings. It now remembers and reuses exactly what it was started with.
Related: an automatic restart could start Starship twice (only one survived, but the extra one counted against the crash-recovery limit, so a genuine crash later could go unrecovered), and a recovering copy could pop a browser window in the middle of the night. Both fixed.
First run of a fresh install now creates your config file
Installing somewhere new started on built-in defaults instead of copying in the documented example configuration, so you never got the commented starting file that explains what each setting does.
Running your own scripts from a sequence could fail
The run_script action worked out which program to run in a way that was only sometimes correct in a compiled build, so it could fail with "file not found" depending on what else had happened first. It now resolves the running program directly.
Signing in returned a server error instead of an explanation
Access control isn't included in this edition, and the sign-in endpoint tried to use it before checking whether it was there - turning "access control is disabled" into a server error. The access-catalogue endpoint had the same fault.
Also
Starting with a configuration file that doesn't exist now says so plainly, and warns that safety monitoring is off, instead of going quiet.
v21.0.4 — big-sensor cameras over Alpaca no longer fail on download
A camera could connect fine and then fail every capture — the image download died at about 88% with a cryptic "Insufficient memory to continue the execution of the program", even on a PC with tens of gigabytes free.
The cause was a large sensor over an Alpaca / ASCOM-Remote bridge. When the Alpaca server doesn't support the compact binary ImageBytes transfer, Starship falls back to the JSON ImageArray — and for a 61 MP frame that JSON is ~250–400 MB of text. It's the server (often a small bridge box, or a 32-bit ASCOM Remote), not Starship, that runs out of memory building it.
Three fixes:
- The error now tells you what to do. Instead of the bare .NET memory message, you get:
"the Alpaca camera server ran out of memory building the JSON image … it does not support binary ImageBytes … update / 64-bit your ASCOM Remote, or connect the camera via native ASCOM."
- No double download. If the server ignores the ImageBytes request and answers with JSON,
that image is reused instead of being fetched a second time.
- Faster, lighter image handling. A pure-Python per-pixel transpose loop (brutal at 61
million pixels) is replaced with numpy on both the JSON and ImageBytes paths, ImageBytes frames are writable, and a malformed image reports a clear error.
Diagnosed from a diagnostics bundle sent in by a user in Germany. If you hit this, the real fix on your side is a 64-bit ASCOM Remote that supports ImageBytes, or connecting the camera via native ASCOM.
v21.0.3 — blockscripts can power the gear on
A gap found by asking a simple question: could a user reproduce, entirely in the GUI, an unattended "start when safe" run set up on a real rig? Almost — except for the one step that matters most on a rig whose gear is fed by a power switch: turning the power on.
A blockscript runs its own small set of actions, separate from a sequence's. That set had park, unpark, open_roof, guiding, cooling, run_sequence, retry_connect, wait, notify, run_script — but no way to switch a power outlet on, and no connect_all. So a cold-start blockscript that must power the mount/camera up before connecting to them had no way to say so: it returned "unknown action" and dropped to EMERGENCY. You couldn't build it in the GUI, and you couldn't hand-edit it in either.
Now a blockscript can do the full cold start: connect_power → power_on → connect_all → unpark → open_roof. New actions connect_power, power_on, power_off, connect_all, disconnect_all, disconnect_power. power_on/power_off take a switch channel, and in the Blockscripts editor that's a picker showing your switch names ("Quad Power"), not a raw number. connect_all matches the dashboard's Connect-all — it leaves power alone, because power is connected and switched on its own, first. The "typical startup" template now shows the right order out of the box. (connect_all connecting around power is deliberate: v20.91.)
v21.0.2 — found on a real rig
Four fixes from a night of testing against live hardware. None of these could have been caught by the test suite as it stood, and one of them was hiding inside the previous release.
Click-to-center did nothing. ASTAP doesn't write the image size the way we were reading it — it reports DIMENSIONS, not NAXIS1/NAXIS2, and we only looked for the latter. So the frame size was always missing, and v21.0.1's rewritten click-to-center (which needs it to turn your click into a sensor position) refused every click. Before v21.0.1 the same missing value made it skip its bounds check instead, which is why it slewed to the wrong place: one bug was hiding the other. The plate solver also now reports the field of view again.
The focus wizard could park the focuser in the wrong place — and then refocus never worked again. When a sweep starts far outside focus, its first frames are a flat smear where only a handful of stars are detected. Those points aren't part of the V-curve, but they were being fitted as though they were, and they dragged the computed best-focus dozens of steps away from the frame that actually had the smallest stars. The wizard trusted that answer, moved the focuser there, and saved it. Refocus searches around wherever the focuser is, so it kept searching the wrong place and rejecting its own results with "fit quality too low". The fit now refuses an answer that contradicts the sharpest frame it actually measured, and re-fits without the star-starved frames.
A failed sequence now tells you why. The run view showed a red "failed" badge and an empty journal. But a run that stops at a precondition — guiding switched on with PHD2 off, or cooling at start with no target temperature — aborts before its first step, so there's nothing in the journal to read. The reason is now shown in the run view.
The remote Operate page had the same stale "still imaging" label that the console had in v21.0.1, and is fixed too.
Also, from the same night
Clouds now stop guiding. When the weather turns and a run is put on hold, the roof is closing or closed — but PHD2 kept "guiding" on a star it could no longer see, still nudging the mount. Guiding is now stopped as part of holding the rig, and restarted on the re-pointed field when the sky comes back. (A manual pause is untouched: nothing is closing, so nothing drops your star.) A guided plan still refuses to expose unless PHD2 is actually guiding.
Dithers are recorded. They happened, but appeared in neither the log nor the run journal, so a night report couldn't tell you whether the night dithered at all.
Settings that need a restart now say which ones. Every save used to claim "needs restart", which taught everyone to ignore it — so enabling PHD2, or moving the images folder, could silently do nothing for hours. Saves now name exactly the settings that won't take effect until Starship restarts, and stay quiet when none do.
Missing optics are no longer silent. With focal length or pixel size unset, the plate solver gets no scale hint and has to guess, which makes solving slow and unreliable and quietly degrades precise pointing and click-to-center. Starship now says so, and reports it to the UI.
A whole-sky plate solve gets its own time budget. The blind fallback was inheriting the timeout meant for a solve that already knows roughly where it's pointing, so it could never succeed where the hinted attempt had just failed.
A plan that flips a mount configured not to flip now says so. Flip mode: off on the mount looks like a master switch, but a plan carrying its own meridian setting overrides it. That precedence is unchanged — a plan is an explicit choice — but it is now written to the run journal instead of happening quietly. Plans can also now say inherit to let the mount decide.
The focus wizard no longer parks on an answer it doesn't trust. If the V-curve fit comes out poor, it now moves to the frame that actually had the smallest stars rather than to the fit's computed minimum.
v21.0.1 — on-rig fixes
Four bugs found testing v21.0 on the rig. Two were in the console UI, where this project has no automated browser coverage; both fixes were verified in a real browser against a running server, and all four are now pinned by tests.
The filter dropdown on the console vanished before you could pick a filter. The console's periodic refresh rebuilt the dropdown on every tick, so an open list was destroyed mid-click. (The same re-render also wiped whatever you were typing into the free-text box when no wheel is connected.) The dropdown is now rebuilt only when the filter set genuinely changes, and never while it is open.
**The sequencer and scheduler filter dropdowns listed your Filter Library and the wheel's raw "Filter 1, Filter 2…" slot labels. They now offer exactly the library names — or the wheel's own names if you keep no library, plus OSC** for a colour camera. Filter selection is unchanged: a saved plan that still refers to a raw driver name or a slot index keeps working.
"Center here" slewed the mount the wrong way. Not a sign error — a scale one. v20.98 retired the full-res FITS viewer and moved click-to-center onto the downsampled preview, but the browser kept sending its preview pixel as though it were a sensor pixel. On a large sensor that put almost every click up and to the left of where you actually clicked. The console now sends a position that doesn't depend on the preview's size at all. Click-to-center also refuses to slew — rather than guess — when the plate solve fails, the WCS is incomplete, or the click lands outside the frame.
Stopping a sequence left the top bar saying "Sequence running", and manual captures stopped showing a preview. Those are the same bug: while a run is active the console deliberately holds full-res science frames behind Show last image, and the run never looked finished. In fact any completed sequence — even a clean success — latched it, not just a stopped one. A finished run now reports itself finished, from a single source of truth shared by the status bar, the preview, and the API. The remote Operate page carried the same stale label and is fixed too.
Also hardened while we were in there: click-to-center refuses bad input instead of guessing. A non-finite or out-of-range coordinate posted to the API used to clamp silently to the edge of the frame and slew there; it is now rejected before the plate solve, as is a solve that doesn't report the image size (without it the target pixel cannot be bounds-checked).
v21.0 — "Night Watch": the biggest reliability release yet
Three coordinated waves — Night Watch (P0 safety), Morning Report, and Quick Wins — about 40 shipped items, hardened by a multi-agent adversarial review that caught and fixed 60+ real bugs before ship. This is the release that makes an unattended night survive, keeps the equipment safe, and makes the data honest.
Night Watch — the night survives, the gear stays safe
- The roof no longer closes around a live exposure. When weather goes UNSAFE
during a supervised sequence, the sequence body now suspends first — it aborts the in-flight exposure and stops guiding — and only then does the rig park and close. On resume it re-slews to the target and restarts guiding, so you never again image the park position after a weather hold.
- Command-then-verify on every roof and park. After each open/close/park/unpark
Starship re-reads the actuator ~5 s later; an ACK'd-but-unmoved close or park now fails the action and pages you once, instead of trusting a lying driver.
- A weather hold can't hang the night. Every suspend carries an absolute
next-dawn deadline: a never-clearing hold tears down cleanly at dawn (bounded warm-up → park → close → alert) instead of waiting to the next dusk. Suspend/ resume cycles are now unlimited (bounded by that dawn deadline, not a counter), and resume waits for ≥60 s of continuously-safe readings (suspend still trips on the first unsafe one).
- Stale weather data is its own emergency. A weather source unreadable for
~30 min escalates to EMERGENCY (park + close + critical alert), not a mere hold.
- Storm-time reboots are safe. An auto-start blockscript waits for safe darkness
before opening the roof and ends cleanly if the weather never clears — no more booting into EMERGENCY → HALTED.
- Guiding failure does what the UI promised. Lost guiding now HOLDS → re-acquires
(stop/restart PHD2, fresh star) → SKIPS the target with reason → pages, instead of grinding unguided for minutes and reporting success. The ladder yields promptly to a weather suspend so a teardown never runs while guiding is active.
- Honest run outcomes. Runs end SUCCEEDED / PARTIAL / EMPTY with real frame and
target counts ("finished: 42 subs across 2 targets, 1 target skipped (guiding)").
- A failed filter change fails the event — no more FITS written under the wrong
glass and labeled as the intended filter.
- Manual mode no longer hangs. With safety disabled, wait-for-safe gates pass
immediately (you own safety) instead of waiting forever.
- New setting — Park mount before closing roof (Settings → Observatory).
Default keeps today's park-first behavior; roll-off owners whose roof clears the OTA can switch to close-first so the roof shuts fast in bad weather.
Guardian — surgical, blast-radius-aware recovery
- Map devices to their power outlet and USB port (Settings → Power). One outlet
can feed several devices. Guardian then resets the exact USB port or power relay for the device that failed, re-initializes every co-powered device after a shared cut (mount → find home, cameras → re-cool), and refuses to cut a shared output while a co-powered device is busy (slewing, exposing, downloading) or its state is unknown. Unmapped rigs behave exactly as before (soft reconnect only).
Morning Report — you find out what happened
- Sequence notify actually sends. Alerts for the silent killers: refocus
failure, guiding loss / PHD2 disconnect, a zero-frame night, and blockscript terminal states. Alert settings apply live, with a sent-history view, a test-send button, and rate limiting (safety alerts are never limited).
- Night Report v1 — a persisted per-night summary: frames per target and filter,
HFD and guiding trends, alerts sent, and skips with reasons.
- Scheduler visibility — a live now / next / why-skipped strip, plus failure
backoff so a failing target isn't re-picked every minute. Meridian-aware scheduling (flip handling is opt-in — the first live flip should be supervised).
Quick Wins
- A broken
config.tomlnow boots on defaults with a red banner and one-click
restore of the automatic backup (and a native message box on the windowless build) — never a silent death.
- Site unset (0,0) fails "wait for darkness" loudly and shows a persistent topbar
chip. RA/Dec fields accept sexagesimal ("05:35:17") everywhere or reject it loudly — no more silent slew to RA 0. "Connect all" really launches PHD2. Blockscript "Run selected" saves first. Framing → Add to Scheduler keeps the position angle. "Warm up" returns immediately and reports progress. Auto Flat reports failure when zero flats were saved. A licence expiry banner appears a week out, and park/roof stay reachable after lockout so a lapsed subscription can never strand a roof open.
v20.99 — "Night Polish": 13 on-rig fixes on top of Framing
Thirteen items straight from a night on the rig — framing, PHD2, mount, and timing.
- The preview's HFD + star-count readout works again. Since v20.98 the
focus-quality readout only appeared if per-frame grading was switched on (off by default), so most rigs just saw "HFD —". The preview now measures the frame it shows, so the HFD and star count light up on every captured frame.
- Precise pointing now shows on sequence targets too. The "Precise point"
checkbox (default on) and "PA °" input already worked from the Add-target form; now they render on every sequence target row, so you can turn precise pointing off — or set a per-target position angle — right where you build the sequence.
- Run the sequence again when it finishes. A new "Run sequence again"
end-action re-runs the plan you just finished (great for repeating a short loop). Supervised runs skip it automatically.
- Chain into another saved sequence. A new "Run another sequence"
end-action hands off to any saved plan when the current one ends. Guarded so a plan can't re-launch itself and two plans can't ping-pong.
- The preview has a toolbar. Zoom, Center here, and **Show last
image moved out of the hidden right-click menu onto a visible toolbar on the preview header. Center-here shows a crosshair cursor** and still plate-solves + precisely slews so the point you click becomes the new centre.
- An empty preview looks like the sky, not a grey box. Before the first frame
the preview shows a hand-drawn galaxy with a caption instead of a blank panel.
- Tighter tile layout. The rotator tile is smaller, and the focuser and
flat device now stack neatly beneath it in one column.
- PHD2 auto-launches on startup again. The launch step existed but was hidden
behind extra flags so it silently never ran; now the single "Auto-launch PHD2 on startup" toggle is all it takes.
- PHD2 connects your gear if it's already open. If PHD2 is running but its
equipment is disconnected, Starship now connects it for you — and a guided sequence gives PHD2 one more chance to get ready before it gives up.
- New timing defaults from real Voyager nights. After studying a set of
Voyager logs, the settle/dwell defaults were retuned for steadier results: longer settle after a slew (10 s) and after centering (10 s), tighter pointing tolerance (~10″, now editable in arcseconds on Plate-solver settings), up to 5 centering iterations, cooling held to 0.5 °C, a 5 s filter settle, a gentler 3-pixel dither with a 13 s settle, an 18 s wait after guiding starts, and a new "Refocus after meridian flip" option (off by default).
- The mount tile lost its jog arrows. The nudge pad and rate control are gone
— the console stays a monitoring surface, not a slew joystick.
- Mounts get a "Go home" button. Any mount that supports it now has a Go-home
control (iOptron calls this the zero position — it's distinct from Find Home). It appears only when your mount driver exposes the command.
v20.98 — "Framing": one image surface, smarter preview, per-target pointing
A framing-and-focus rework of the console.
- One image surface. The heavy full-res FITS viewer is gone — the
per-capture preview is now the single place images appear. For deep inspection, open the saved FITS in ASTAP / PixInsight / your tool of choice.
- A smarter preview. It shows HFD + star count at a glance;
right-click it for Zoom in or Center here (Center here plate-solves the frame and precisely slews so the point you clicked becomes the new centre); and it's frame-aware — the quick bin2 pointing/framing frames appear live even during a sequence, while full-res science subs are held behind a "Show last image" button so a run stays calm on a small PC.
- The rotator has its own tile now, with its live position (mechanical /
target / PA) always on show — no longer buried in the Camera and Mount cards.
- Tiles are colour-coded by status — a subtle tint tells you at a glance
what's idle, working, warning, or faulted (neutral when disconnected, so a cold rig isn't a wall of red).
- Precise pointing on every target, automatically. When the scheduler moves
to a new target it now plate-solves and precisely centres before imaging, so your framing is repeatable night to night. It's default on with a per-target "Precise point" checkbox to turn it off, and a per-target position angle — if you have a rotator, it rotates the field to that PA. It's fail-soft: with no plate solver or no mount (a backyard rig), or on a transient solve hiccup, it simply falls back to a normal slew and keeps imaging — it never stops your night.
v20.97 — "Steady Link": Alpaca devices no longer drop on a first blip
Some Alpaca devices — especially serial-bridged DIY controllers like a GRBL focuser — auto-reset the instant their serial port opens and need a second or two to come back. During that window they briefly report "not connected" or time out. Starship used to disconnect on the very first bad reading, so the device would connect and then drop ~3 seconds later, while other clients (e.g. Voyager) rode out the blip and stayed connected.
- Transient blips are now tolerated. Starship absorbs a few consecutive
failed liveness reads before giving up on a device — the slot shows "reconnecting (n/N)" during the grace window instead of vanishing. A real unplug is still caught within a few poll cycles, and any good reading resets the counter.
- Tunable.
[alpaca] connect_grace_polls(default 3) sets how many
consecutive blips to ride out. Set it to 0 to restore the old drop-on-first-failure behaviour.
- A longer connect window. Connecting now waits up to 10 seconds
([alpaca] connect_verify_s, was a fixed 3) for the device to report ready — a board whose reset takes 4–8 seconds used to fail the connect outright. Healthy devices connect just as fast as before.
- Slow links get a knob too.
[alpaca] http_timeout_s(default 5) sets the
per-request timeout, for bridges that are legitimately slow but alive.
- Alpaca mounts and rotators now report their state. Their property readers
existed but were never wired in — an Alpaca mount showed no RA/Dec/tracking at all. Fixed.
- Self-heal stays out of the way. While a blip is being ridden out, the
health monitor no longer sees the device as "stale" — so it can't "help" by reconnecting it mid-reset (which would just reset the board again).
- This only relaxes the connection check — a device that is genuinely gone
still drops, just not on a momentary hiccup.
v20.96 — "Calm Night": stable, unhurried sequence execution
A sequence run should be calm — one action at a time, waiting for each to finish, never fighting itself for the CPU. v20.96 makes that true, after a real night where a run raced through actions, pegged the PC at 98%, ignored Stop, and didn't park. What changed:
- The PC is no longer overloaded. Heavy image work — frame grading, the
preview render, plate solving — now runs strictly one at a time (a "load governor"), so they can't stack up and freeze the app. On a small observatory PC this is night and day.
- No preview churn during a run. The per-frame preview (a setup/framing
convenience) is skipped while a sequence runs and previews are now downsampled instead of rendered at full 24-megapixel size. Open any frame in the viewer manually whenever you want it.
- Filter changes wait for the wheel. The next exposure no longer starts
while the filter wheel is still turning — Starship waits for the wheel to report it arrived, then settles.
- A normal park no longer looks like a crash. A slow Park/Slew/Home
(which is normal — mounts take their time) no longer trips the watchdog into showing every device "disconnected".
- A run can't race anymore. If exposures ever "complete" faster than
physically possible, the run halts itself safely (parks / closes) instead of machine-gunning the gear — one log line instead of a wrecked night.
- Breathing room + honesty. A short settle between steps (so Stop is always
heard), plate solving runs at lower priority so it can't starve guiding, and an On End with no actions now says so in the log (so a "didn't park" is never silent). New Settings → performance knobs tune all of it.
v20.89d — manual-mode default, factory reset, recovery polish
- Fresh installs start in MANUAL MODE (safety monitoring off). Most setups
have no CloudWatcher — a new install no longer sits in UNKNOWN hunting for one. Observatory owners: turn monitoring on in Settings → Safety.
- Factory reset: Settings → Web server → Danger zone → "Reset to
defaults…" moves your config.toml to a timestamped backup, writes a fresh first-install config, and restarts — a clean slate in one click.
- A config.toml saved by Notepad (UTF-8 with BOM) no longer prevents startup.
- After a crash-watchdog recovery, Starship no longer opens a second dashboard
window — your existing window simply reconnects.
v20.89c — follower Choose fix + single-instance guard
- Follower device Choose now stores the driver correctly — the ProgID field
showed [object Object] after picking a follower camera/focuser/wheel (an internal result object was saved instead of the driver id). A cancelled Chooser no longer wipes an existing choice. If you saw [object Object], just Choose the device again after updating.
- Only one Starship can run at a time. Launching a second copy now detects
the running instance, opens its dashboard, and exits — previously a duplicate would fight the original for the port and devices (and with the new crash watchdog, each copy kept resurrecting itself). The installer also closes a running Starship before updating files.
v20.89b — live-test fixes + the crash guard
- Crash guard. Starship now supervises itself: if the process ever dies
unexpectedly (e.g. a native driver fault the app cannot catch), a tiny watchdog relaunches it within seconds — and a crash tracer writes the exact state of every thread to starship-crash.txt so the fault can be diagnosed and fixed for good. Clean shutdowns (the Terminate button) do NOT restart, and a broken install won't relaunch-loop (3 restarts / 30 min cap).
- The Safety "monitoring enabled" checkbox now shows the real state — it
always rendered unticked, even while monitoring was actively running.
- No more CloudWatcher log spam: a rig without a weather station logs one
warning plus a ~10-minute summary instead of two lines every poll, and the dashboard now explains what UNKNOWN means and points to manual mode.
- Skyhunter → "Frame in Atlas" works on the first click — the target used
to be lost if the sky view was still loading; it's now queued and framed the moment the atlas is ready.
v20.89 — "Trust the Night": 20+ reliability fixes from a full product audit
A deep audit of every subsystem — walked end-to-end the way a real imaging night runs — found and fixed the failures that used to bite unattended at 2am:
Weather & safety now actually interrupt the night
- The blockscript's weather response works again. A wiring bug meant the
unattended engine never received safety verdicts — its whole UNSAFE → park → close → resume ladder was dead. Fixed (and covered by tests).
- UNSAFE now pauses a running sequence. A stand-alone run pauses (running
your On Suspend actions), cancels the in-flight exposure, and auto-resumes — running On Resume — when conditions are safe again. Manual pauses still wait for you. A scheduler chunk now stops immediately on UNSAFE instead of exposing for the rest of the chunk.
- A failed sequence no longer strands the blockscript in RUNNING (roof
open, mount tracking, all next day) — a failed body now drives the engine's safe path and a stopped body ends the session cleanly.
- Critical alerts reach your phone. EMERGENCY, a failed roof close, and
health escalations now go out via your email/Pushover alert channels (they were log-only).
The meridian flip is now guided end-to-end
- The flip **stops PHD2 guiding before the slew and restarts it (with settle)
after the recenter** — previously guiding was never cycled, stranding PHD2 for the second half of the night. A failed guiding restart fails the run safely instead of silently imaging unguided.
- The mount tile's pier-side display was wrong for every value (East/West
swapped; East showed as "—"). Fixed.
Focus you can trust
- Refocus now enforces the quality gates Settings always advertised (fit
R², max HFD, minimum-in-range). A cloud-spoiled fit fails cleanly and returns the focuser to where it started — no more imaging defocused until dawn because one fit went bad.
- The Filter Library's focus-offset column now works (it was never read;
the working store was a hidden JSON field). Library values win; existing configs keep working.
- One lost guide star no longer fails every following sub — the star-lost
flag clears as soon as PHD2 guides again.
Your data is correct
- DATE-OBS is now the exposure START time (it was stamped at file-write,
minutes late — breaking comets, asteroids, photometry and stacking math).
- Sequence flats and bias frames are captured correctly — flats now shoot
with the shutter OPEN and are labelled FLAT (they were shot as DARKs), bias frames are labelled BIAS.
- The scheduler only credits ACCEPTED frames toward a campaign goal —
rejected subs no longer count as progress, so goals finish with real data.
Quality of life
- Edit scheduler goals in place (✎) — raising 30→60 frames no longer
wipes the multi-night acquired tally.
- New "End when Sun rises above X°" sequence constraint — a re-runnable
dawn cutoff that never goes stale like a fixed end date (-6 = civil twilight; needs site coordinates).
- Blind solve is now truly blind — it searches the whole sky, so
lost-mount recovery works when the mount is more than a few degrees lost.
- The On Suspend editor tab now saves what you author (it was silently
discarded); the ASCOM watchdog setting survives Settings saves; the footer safety readout updates; the Run monitor's duplicate Resume button (which triggered the wrong action) was removed — ⏸ Pause toggles to ▶ Resume.
- A piggyback follower whose camera fails now retries with backoff and
parks in a visible ERROR state instead of silently spinning until dawn.
v20.87 — Piggyback: a second imaging train
- Piggyback mode. Run a follower imaging train (its own camera, focuser and
filter wheel) on the same mount as your lead train — two scopes imaging in tandem. Turn it on in Settings → Piggyback, set up the follower + its capture plan (filter, exposure, cooling, refocus cadence) in Settings → Follower train, and start it from the dashboard's Follower panel — or arm it to run automatically with your sequence. The public edition supports one follower.
- Coordinated on the shared mount. A follower frame never straddles a slew /
flip / dither, and the lead and follower never autofocus at the same time — the two are serialised, so the PC is never running two focus sweeps at once.
- Off by default — with piggyback off, everything behaves exactly as a normal
single-rig setup. See the Piggyback help topic for the full walkthrough.
v20.86 — ASCOM on by default for new installs
- ASCOM works out of the box. A fresh install now ships with the ASCOM
transport enabled, so the Chooser opens the first time you use it. (Before, a new install started with ASCOM off and showed a red "ASCOM disabled" until you turned it on in Settings and restarted.) If ASCOM is ever off, the Chooser now tells you exactly how to turn it back on.
- The built-in diagnostics bundle now reports the correct build version.
v20.85 — Runs on more machines (Python 3.12 rebuild)
- Broader Windows support. The production build is now compiled on **Python
3.12 and ships as a self-contained runtime** — it runs on a wider range of Windows 10 machines (including older long-term-servicing builds such as 1607 LTSB) with nothing to install alongside it. No behaviour changed; this is purely a packaging/compatibility rebuild so more rigs can run the same build.
- Carries forward all the v20.84 on-rig fixes below, still built with **Nuitka
Commercial data-hiding protection and the console window hidden, and signed by the production** licence key.
v20.84 — On-rig fixes + protected production build
- Guiding is now enforced. A plan with guiding enabled will not keep
exposing if PHD2 isn't actually Guiding (looping/stopped/star-lost) — it holds, then fails the sub so your on-error policy decides. No more silent unguided subs.
- PHD2 auto-launch fixed. If the configured path is a Start-menu folder or a
shortcut, Starship now resolves it to the real phd2.exe (and falls back to the standard install location).
- You get told when a run stops. Stop/halt now raises an alert by default, with
the reason (e.g. meridian halt), and flip/abort failures include the cause. (Alerts still need to be enabled + an email/Pushover channel configured.)
- Connect-all stops on failure. If one device fails to connect, it stops and
alerts instead of quietly carrying on with half the gear. (Disconnect-all still releases everything.)
- Console power tile got a Connect button when no power device is connected.
- Autofocus progress now shows as a floating pop-up while it runs (lingers a
moment on the final curve), not inline in the run journal.
- Progress bar clears when a sequence stops (no more frozen bar needing a reset).
- Clearer duplicate-row icon in the sequence editor.
- Build: compiled with Nuitka Commercial (constants protection) + the console
window hidden, and signed by a fresh production licence key.
v20.83 — Online supporter licence (client) + rocket logo
- Supporter licence in the app. A new Settings → Licence tab shows your
edition and licence status (licensed-to, valid-through, days left), this computer's licence ID, and a field to paste the activation key you receive when you subscribe — with Activate and Refresh now.
- Daily online refresh, offline-safe. In the background Starship trades your
{activation_key, machine_id} with the licence server (api.astroworxstarship.com) for a fresh, machine-bound, signed licence about once a day. If it can't reach the server it keeps the cached licence and keeps running until that licence's own expiry — your paid period plus a 10-day grace. A flaky network never ends a night. The startup also does one best-effort refresh before the gate, so a renewed subscription unlocks immediately.
- Under the hood:
core/licensing/client.py, the/api/license/status,
/api/license/activate, /api/license/refresh endpoints, and a daily refresh thread; the licence file is cached at ~/.starship/license.key and verified offline by the existing signed-licence gate.
- Rocket brand mark (designed by Mark Iscaro) now appears on the startup
splash, the top bar, and as the favicon — and across the marketing website. The product name stays Astroworx Starship.
v20.82 — Startup splash + Acknowledgements
- A 10-second startup splash introduces Astroworx Starship: an idea built and
developed in Australia, the mission — a functional, reliable tool for the astronomy community — and a note of gratitude that Starship only builds upon the experience and learnings of the giants before it. It links to www.astroworx.com.au, shows once per browser session, and dismisses on the timer, the Enter Starship → button, or Esc.
- A new Acknowledgements page (Help → About, and linked from the splash)
credits the standards, projects, libraries and data sources Starship is built on — ASCOM/Alpaca, PHD2, ASTAP, Cartes du Ciel, OpenNGC, NumPy, psutil, the Ed25519 reference, Meeus, and the wider open-source astronomy community.
v20.81 — Two editions: Astroworx Starship + Professional
- Public vs Professional editions from one codebase. The public **Astroworx
Starship** build excludes telescope collimation, the research module (Navigator), the rental web dashboard, and user booking — these are compiled out, not just hidden, for the Astroworx Starship Professional build used on special projects. Everything else is fully available in both. The build self-identifies (title bar, About, /api/version) by which modules are present — no manual flag to keep in sync — and the UI automatically hides whatever isn't in the build. Set STARSHIP_EDITION=public to preview the public edition from source.
v20.80 — License protection for distributed builds
- A compiled Starship build now needs a license to run. If you send someone a
compiled copy, it refuses to start without a license.key that is bound to that machine and an expiry date — so a copied build won't run on another machine, and every license dies on its expiry day. You mint licenses with a private key only you hold; the build embeds only the matching public key, so they can't be forged. Source/dev runs are never gated. See docs/LICENSING.md for the workflow (generate a keypair once, then mint a license.key per recipient from the machine ID their build prints). Built on a vendored Ed25519 (RFC 8032) — no new build dependency — and hardened against a battery of forge/tamper/bypass tests.
- Navigator: fixed a missed first email digest. The auto-digest debounce used a
monotonic clock against a zero baseline, so on a freshly-booted machine the very first digest could be silently suppressed. It now always sends the first one.
v20.79 — Sequence controls that actually update
- Start / Pause / Stop now reflect the run. They were static — after a
stop→start the buttons never changed, so it wasn't clear what state the run was in. Now Start is disabled while a run is active, Stop only enables while active, and Pause flips to Resume when paused. The badge + buttons also refresh the instant you press one (instead of waiting for the next poll), so it's obvious when a sequence has actually stopped.
- PHD2 auto-start needs the path. Enabling "Auto-start PHD2" does nothing on
its own — it also needs the executable path filled in. The settings now show a clear warning when auto-start is on but the path is blank, and note that PHD2 launches on Starship startup or via Connect all (not when a sequence starts).
v20.78 — Meridian flip: stop holding a mount that's already flipped
The flip manager decided whether to flip purely from hour angle (and whether it had flipped recently) — it never checked which side of the pier the mount was actually on. So a mount that was already on the correct post-meridian side (slewed straight onto a western target, or already flipped) got wrongly held / re-flipped, stalling the sequence. Now it reads SideOfPier: if the mount is already on pierEast ("looking west", the correct side past the meridian), no flip is needed and the sequence continues. A flip is only triggered from pierWest. New [mount] flip_pier_side_check (default on) gates this; set it false only for a driver that reports SideOfPier with an inverted sense.
v20.77 — Four UI fixes from the rig
- Delete a saved sequence. The saved-sequences list now has a Delete button
on each row (it was missing entirely).
- Target/row delete works again. Deleting a target — or an exposure row inside
a target — was silently doing nothing: an internal form-sync step could throw on a plan with no title set, which aborted the delete before it happened. Fixed at the root, and hardened so a sync hiccup can never block a delete.
- Skyhunter optics fields are real now. The focal length and sensor size showed
as gray hint text (e.g. "530") that didn't actually count until you retyped them. They're now filled in from your camera config, so the field-of-view calculates immediately.
- "Connect all" tells you about PHD2. It now reports per-device results — so if
PHD2 didn't come up you'll see why (e.g. "open PHD2, or set its executable path + auto-start"). Those settings already live in Settings → PHD2 guider; set the PHD2 executable path and turn on Auto-start for the one-click launch to work.
v20.76 — Guiding guardrails, Sky Atlas, and a centred night
A batch from a real night at the rig:
- **Sky Atlas → Sequencer now adds, not replaces.** "Add to Sequencer" used to
wipe the targets already in your editor; it now keeps them and appends the new one.
- Guiding fails fast. If a sequence has guiding enabled but PHD2 isn't
connected, the run now stops immediately with a clear message ("open PHD2 and connect the guide camera + mount") instead of silently skipping guiding and spamming connection retries.
- "Connect all equipment" brings up PHD2 too — it launches PHD2 (when an
executable path is configured) and connects its guide camera + mount, so guiding is ready in one click.
- Live autofocus graph in the sequence view. When a refocus runs mid-sequence,
the V-curve now draws live in the run panel as samples come in — no need to open the console.
- Skyhunter centres the night. The visibility graph is anchored to local noon,
so tonight's dark window sits in the middle of the plot (with a "now" marker), instead of the daytime splitting it at the edges.
- Bonus fix: the guiding RMS watchdog had been silently doing nothing (it called a
property as if it were a method); it now actually holds exposures when guiding degrades.
v20.75 — Refocus actually lands on focus
A real-rig night exposed it: the Adaptive First Light wizard nailed focus at position 2361, but a later Refocus parked the focuser at 2279 — ~80 steps soft — even though it had measured a perfect V-curve with the minimum plainly at 2361.
- Cause: refocus was reusing the wizard's coarse survey recipe (big step,
wide range). At that coarseness the sharp bottom of the V got a single sample, and the noisy outer wings of the wide sweep dragged the curve-fit ~80 steps inward.
- Fix: refocus now resolves the apex instead of re-surveying — a **finer
step over a tighter range** around the current (already near-focus) position, using the same number of exposures. On the real curve this moves the fit from 2279 to ~2360. Two new knobs ([autofocus] refocus_step_frac, refocus_points_per_arm) tune it; set refocus_step_frac = 1.0 for the old behaviour.
- The Sakana Adaptive First Light wizard is unchanged — it was already
correct. Backlash handling was also checked and confirmed correct (a single outward over-travel, then an inward settle; no double compensation).
v20.74 — Sharper collimation: adaptive poke + damped solver
Two opt-in upgrades to the closed-loop collimation math, both off by default (the default path is byte-for-byte what it was). Turn them on in [collimation].
- Adaptive calibration poke (
auto_adaptive_poke). Instead of a fixed-size
poke when measuring the influence matrix, each actuator's push-pull is grown until the donut's response clearly clears the measurement noise floor — so the matrix is built on real signal, not noise. It starts at auto_cal_step and grows (bounded by the mirror's travel range); a stalled actuator is still caught by the existing zero-response / ill-conditioned guards.
- Damped least-squares solver (
auto_solver_dls). An optional Levenberg-style
solver that works in the pure-tilt (focus-held) subspace. On a healthy matrix it matches the old pseudo-inverse exactly; on an ill-conditioned one it damps the correction instead of overshooting or refusing — a gentler, safer move.
- Every existing safety guard is untouched: the arm flag, per-move clamp,
cumulative travel backstop, divergence abort, and focus-holding piston projection all still apply. The math is validated against the desk simulator; the physical mirror moves still want a supervised first run on the rig.
v20.73 — Auto-defocus: size the donut for an accurate measurement
The collimation measurement only works if the defocused-star donut is the right size. Too small and you're measuring noise (your real test frame came in at ~14 px when we need ~50); too big and the donuts overlap.
- New "Auto-defocus" button on the Collimation → Measure card. It drives the
focuser (not the mirror) until the donut hits the target diameter: probes at a test offset, scales to the target, verifies with one correction, and reports "donut 48 px — in range ✓ · +2500 steps from focus".
- It finds the right defocus for your rig — no aperture or f-ratio assumptions,
just measure-and-scale. Doubles the probe if the first try is too small; clamps to a safety limit so it can't run the focuser into the stops.
- Needs only a focuser and a bright star centred — works without the collimation
actuators connected.
- New
[collimation]settings:defocus_target_px(50),defocus_min_px/
defocus_max_px (the 35–80 acceptance band), defocus_initial_steps, defocus_max_steps, defocus_direction.
v20.72 — Tell me what Starship is thinking
- Simulate now explains the gaps — after you simulate, any target that didn't
make tonight's plan is listed with the reason ("below 30° all night", "moon-washed", "complete", or "didn't win a slot — lower priority").
- Resolve tells you if it's worth it — resolving a target now also says whether
it's up and which filters the Moon allows tonight ("up 52° · narrowband only, 28° from a 90% Moon").
- Blockscript states explain themselves — each state shows a one-line hint
(Running: "run scheduler for a multi-target campaign, or run sequence for a fixed plan").
- Sensible-value tooltips on priority, min altitude, resume dwell, and max
resumes — so you know what to pick.
v20.71 — Decision help where you actually decide
From walking the setup as a new user would:
- Scheduler — each filter row now shows its integration time (frames ×
exposure, e.g. "= 2.5 h") and a per-target total, so you can tell at a glance whether a plan is enough. Pick a narrowband filter and the exposure jumps off the 120 s broadband default to a sensible 300 s.
- Blockscript — empty state lists used to be a guessing game. Each one now shows
its typical actions ("typical: open roof, unpark, cool to") with a + add typical one-click fill, so you start from a sensible default and edit from there.
v20.70 — Scheduler readiness bar + blockscript lifecycle map
Two big clarity upgrades, one shared colour language (green = ready, amber = needs you, blue = waiting on nature, red = blocked).
Scheduler — a step-by-step readiness bar up top shows exactly what to set up, in order: location → strategy → targets → filters & frames → simulate & start. A colour-coded banner always names the single next thing to do (e.g. which target has no filters), Start is locked until you're ready, and every target card is tinted by its status.
Blockscript — a lifecycle map sits above the action lists: the seven states (startup → running → shutdown, plus the warning / unsafe / resume safety loop and emergency) as colour-coded nodes you can click to jump to. Each list now states its own trigger and exit ("in: safety UNSAFE · out: safe + dwell → Resume"), and during a real run the current state lights up live — the editor doubles as a status panel.
v20.69 — Collimation: verify the measurement by eye
The Measure view on the collimation page now overlays, on each detected donut, a green ring + centre cross, a cyan dot on the detected shadow centre, and an amber line showing that donut's decenter — plus the net-error arrow. Read it as an accuracy check: rings should hug the donuts and cyan dots sit centred in each shadow; when the amber/cyan offsets all point the same way it's a real collimation error, and when they scatter it's noise or poor detection (fix focus/exposure/ defocus before trusting it).
v20.68 — Collimation: trustworthy measurements
Groundwork for closed-loop collimation you can trust on the real mirror (all behind the existing arm flag and safety gates):
- Field-drift quality guard — the loop now measures how far the star field
shifted between iterations and stops rather than act on a reading taken after the donut population changed. (Set [collimation] auto_max_field_drift_px; default is report-only so it never surprises a working rig.)
- Measurement noise floor — calibration now reports the repeatability of the
decenter measurement, so corrections and convergence aren't chasing noise.
- Documented (and proved with a test) why the field-shift "subtraction" idea would
have hurt accuracy — the loop measures the absolute decenter, which is already robust to field shift.
Next: adaptive defocus sizing, adaptive calibration poke, and a damped-least-squares solver.
v20.67 — Pick a schedule + wait for dark
- Run scheduler → choose a saved schedule — the blockscript Run scheduler
action now has a dropdown of your saved schedules. Pick one and it loads that schedule's targets before starting (leave it blank to run the current pool). It won't swap the pool out from under an already-running campaign.
- Wait for darkness in blockscripts — the Wait action gained two modes:
until_dark (Sun below −18°, astronomical, from your site location) and until_safe_dark (waits for SAFE and dark).
v20.66 — Action qualifiers & focus-with-filter
- Console “focus with filter” — the autofocus row now has a filter dropdown next
to Refocus; pick a filter and it moves the wheel there, then focuses at that filter's exposure (or leave it on Current filter).
- Sequence actions are colour-coded — each action row is now tinted by its
category (roof, mount, dome, power, camera, utility) for easy separation.
- **Cool to a temperature *over time*** — Cool camera (sequence) and Cool to
(blockscript) take an "over X minutes" ramp, so you can cool to 0 over 5 min instead of slamming the setpoint.
- Power on/off picks a real outlet — the channel is now a dropdown of your power
device's named outlets (when connected).
- Blockscript “Run sequence” reliably lists your saved sequences now (it
lazy-loads them).
v20.65 — Auto Flat planner
- Auto Flat is now a proper planner instead of a JSON box. Under Settings → Flat
device, the per-filter plan is a table: one row per filter with shots, target ADU, tolerance %, binning and initial exposure (blank cells use the global defaults).
- Per-filter tolerance — each filter can have its own acceptable-error %.
- Save layout — keep all flats in one folder, or write a **separate folder per
filter** (flats_<filter>/).
v20.64 — Moon × filter awareness in Skyhunter
- Skyhunter tells you which filters work tonight — each target in Tonight's best
now shows BB / NB chips (broadband / narrowband), struck through when the moon washes that class out. A Hide moon-washed toggle drops targets that nothing can shoot. The object detail shows Usable filters at transit.
- This uses the same moon phase × filter-type logic the scheduler already enforces
(separation scaled by moon brightness, narrowband relaxed), now surfaced for planning. Tune the thresholds under Scheduler → moon avoidance.
v20.63 — Wait modes, FOV calculator, Skyhunter → scheduler
- Blockscript “Wait” now has modes — wait a duration, wait until safe, or
wait until a time (either HH:MM tonight or a full date/time). A timeout caps the gated waits.
- Sky Atlas FOV from optics — type a focal length and sensor width/height (mm)
and the field-of-view fills in automatically, no connected camera needed.
- Skyhunter → scheduler — objects now have a + Scheduler plan button that
prefills the Add-project form and jumps to the Schedule page.
v20.62 — See your enclosure
- Observatory tile pictogram — the observatory tile on the Equipment dashboard
now draws your enclosure (roll-off roof or dome) and colours it by shutter state: green when open, muted when closed, amber while moving, red on error.
- Dome geometry, visual + editable — the Dome page's Geometry & offsets card is
now an editable form with a live top-down diagram showing what each offset means (dome circle, North, the open slit, the mount offset and GEM arm). Save writes straight to your config.
v20.61 — Save & recall schedules
- Saved schedules — on the Schedule page, save your whole project pool as a
named schedule, then Recall it (adds the targets) or Replace the current pool with it later. Recalling starts a fresh campaign at 0 frames.
- Blockscript “Run sequence” now has a dropdown of your saved sequences
instead of a free-text box — no more typing the exact name.
- “Scheduler Target” magic name — name a target Scheduler Target in a sequence
and save it as a scheduler template; the scheduler fills that slot with each chosen target. Any other targets in the template stay fixed.
v20.60 — Sequence actions & smarter refocus
Sequence editor
- The action menu is now grouped and colour-coded by category (Roof & cover,
Mount & pointing, Dome, Power & equipment, Camera & focus, Utility) — much easier to find the action you want.
- New actions: Connect / Disconnect power device (just the power switch, not all
gear), Home mount, Plate-solve & sync, and Blind-solve & sync (recover) — a true whole-sky solve that re-syncs a mount that has lost its place.
- New On-start gate: Wait for darkness — holds until the Sun is below your chosen
altitude (default −18° astronomical), with an optional extra delay. Uses your site location.
Console
- The focuser tile has a refocus filter dropdown — refocus on the current filter
or pick another; it moves the wheel there first and uses that filter's focus exposure.
v20.59 — Enclosure type is now pickable
- Settings → Observatory → Enclosure type can now be changed. It was
accidentally locked (disabled) even though dome support has been built since v20.38 — so picking Dome to enable dome slaving was impossible. Now it's a normal dropdown with friendly labels (Roll-off roof / Dome).
v20.58 — Console & sequencer polish (batch 1)
- Fixed the sequence "empty plan" bug — after entering a target and its filters
then switching tabs, validating/saving/starting no longer falsely says the plan is empty. It now always reads your live edits.
- Plate-solve & sync — a one-click button on the console plate-solve panel: solves
the frame and immediately syncs the mount's pointing model (no scope movement).
- Topbar activity — a live line showing Sequence running / Scheduler active /
Blockscript in operation so you can see at a glance what the rig is doing.
More of the requested console/sequencer improvements (focuser refocus-filter dropdown, new sequence actions with category grouping, schedule save/recall) are coming next.
v20.57 — Scheduler page redesign (the glass box)
The Schedule page now explains itself, so programming a campaign is intuitive:
- A "now imaging" panel at the top tells you what's being shot and a status strip
shows the campaign state, roof, darkness, Moon, and how much of tonight's goals are done.
- Every target says, in plain words, why it is or isn't being shot — *imaging
now, ready — waiting its turn, Moon too close — 18° from a 64% Moon, still rising — 24° (needs 30°), sets in 22 min, complete, paused*.
- Target cards are readable: per-filter progress bars (Ha 12/20, OIII 20/20) and
plain meta ("priority high · won't shoot below 30° (now 41°)") instead of cryptic shorthand.
- The behaviour knobs are explained inline (dispatch vs finish-target, spread vs
finish-first), with a short "how it works" intro on adding a target.
- "Simulate tonight" is now a visual timeline — a coloured bar per target across
the night with a legend — instead of a text list.
This completes the filters + scheduler rework (filter library, per-filter refocus exposure, OSC, multi-filter plans, and this glass-box redesign).
v20.56 — Multi-filter scheduler plans
Adding a target to the scheduler is now a real plan builder. Type the name → Resolve (coordinates fill in) → add one row per filter (each with its own exposure and frame count), hit + filter for more, and one Add project saves the whole plan. The filter dropdown pulls from your filter library, including OSC. You build a broadband (L/R/G/B) or narrowband (Ha/OIII/SII) plan simply by which filters you add — no more one-filter-at-a-time.
v20.55 — Filter library + per-filter refocus exposure
Settings → Filter wheel → Filter library lets you name the filters in your wheel and give each one a refocus exposure — the autofocus exposure used when it refocuses on that filter. Dim narrowband needs longer than broadband (e.g. L 4 s, RGB 6 s, narrowband 12 s); blank uses the global autofocus exposure. Every refocus path now uses the right per-filter exposure — both the manual Refocus and the in-sequence refocus on a filter change.
For a one-shot-colour camera, add a filter named OSC — and because an OSC camera can still sit behind a wheel of dual-band filters, you add those too. An empty library falls back to the wheel driver's reported names.
This is the foundation for the multi-filter scheduler plans and the scheduler-page redesign, which land next.
v20.54 — Theme selector
System → Appearance now offers four themes, saved per browser and applied instantly (no restart):
- Original — the dark blue default.
- Light — bright, for daytime setup.
- Red — night — shades of red on near-black to preserve your dark adaptation at
the eyepiece.
- High contrast — pure black and white with vivid status colours for maximum
legibility.
The choice is remembered and applied before the page paints, so there's no flash on reload.
v20.53 — Manual mode (backyard / portable / no weather sensor)
You can now run Starship without an observatory or a weather sensor. Settings → Safety → "Safety monitoring enabled" (or [safety].enabled = false) turns the weather supervisor off: the status reads MANUAL MODE instead of a red UNSAFE, nothing is gated (you're the weather sensor), and the weather poll / loss-escalation / response engine don't run — so there's no spurious "no contact" fault. Leave it on for a real observatory, where the roof gate matters.
Also clarified: flat panels / cover calibrators and Auto Flat work without an observatory — a flat device is its own connected accessory, not tied to the roof. A backyard imager with a flat panel gets full flat-cover and auto-flat functionality. The only things a no-enclosure setup skips are the roof/dome actions and the fully-unattended roof-cycling campaign.
v20.52 — Smarter target picking (ideas from APSCHED)
After studying a friend's Voyager scheduler (APSCHED), three of its ideas made the scheduler pick better:
- Meridian preference. Targets are now nudged toward the meridian, where airmass
is lowest — a score boost that peaks at transit and fades over ± transit_window_min (default ±2 h). You can also make it a hard cut (transit_gate) to image only near the meridian. On by default as a gentle preference.
- Phase-aware Moon avoidance. The required Moon separation now scales with
illumination, so a thin crescent needs far less clearance than a full Moon (continuous, not a fixed step). A 20°-from-Moon target that the old rule always skipped is now shot under a slim crescent and only skipped as the Moon brightens.
- "Don't waste a clear night." Optional (
allow_relax): when nothing meets every
constraint, the scheduler relaxes the soft ones — transit window first, then Moon — and shoots the best of what's left, instead of idling.
All three are config-driven, the existing scheduler behaviour is unchanged unless you opt in to the gate/relaxation, and the test suite is at 684 passing. Run scripts/_meridian_demo.py to see the meridian and Moon-scaling effects side by side.
v20.51 — Sakana Edition: smarter collimation + focus calibration
Integrated the "Sakana Edition" work and hardened it after an independent review. All of it is additive — the existing focus and collimation methods are untouched, and (verified) every primary-mirror actuator move still flows through the unchanged, tested auto-collimation core, with its clamps, damping, and divergence-abort intact.
- The Sakana Way — an experimental collimation mode that takes a stable
measurement, makes guarded automatic actuator moves, takes a second stable measurement, and gives you an explicit verdict (PASS / IMPROVED / ALREADY_GOOD / FAILED) with a confidence and reasons. It stays default-off and disarmed (needs GRBL connected, a calibrated influence matrix, and the auto-collimate arm flag) and can only measure and classify — it has no direct actuator authority of its own.
- Adaptive First Light — a self-finding focus wizard. Instead of asking you to
guess a step and range, it measures the focus noise, probes for the smallest focuser move that actually changes focus, derives the sweep, and builds the V-curve with the existing wizard.
- Measured backlash (review, don't auto-apply) — it measures focuser lost motion
and writes a characterization report you approve in Settings → Focuser before it's used; the values then travel with your equipment profile.
Fixes applied during integration: backlash measurement now skips a sample if a focuser pre-move is refused by the travel limit (instead of trusting a corrupt number) and won't claim high confidence from only two samples; equipment profiles carry only the focuser characterization fields, never the operational ones like direction-reverse or travel limits; the first-run auto-calibrate default is off so the mirror never moves to calibrate without an explicit step; and two corrupted UI glyphs were restored. Full test suite: 678 passing.
v20.50 — Review fixes for the restart + simulation work
A review of the v20.49 changes found and fixed three real issues:
- A dead safety gate, revived. The scheduler (and the new boot auto-resume)
were reading the safety verdict the wrong way, which silently failed and got swallowed. The roof still always closed on unsafe — CloudWatcher is hardwired and the roof-open command independently refuses an unsafe sky — but the scheduler's own "close on unsafe" backup was doing nothing. Now fixed, so the safety net has all its layers again.
- The roof is checked against the hardware on restart. After a reboot,
Starship now reads the roof's actual position instead of assuming it's closed — so if a power cut left it open, the next dawn/unsafe close actually fires.
- The week simulation matches the live system more exactly at the edges (an
empty target list now reports "not complete," the way the real run behaves).
v20.49 — Watch a week run + survive a restart
- Multi-night campaign simulation. A new dry-run plays the whole campaign
forward night by night — same target-picking, moon avoidance, set/dawn-aware chunking, roof open/close and completion as the live system, with the real sun and moon, and optional clouded nights. python scripts/_campaign_sim.py prints a 7-night run you can read; nothing moves. (In the example week the narrowband targets finished while the broadband one waited for the moon to set — the gate doing its job.)
- Auto-resume after a restart. Set Auto-start on boot on the blockscript
list ([blockscript].auto_start). After a power cut or reboot, Starship relaunches that blockscript, which runs the scheduler again — and because the progress ledger is saved in scheduler.db, the campaign continues exactly where it left off (at most the one in-progress chunk is re-shot; its frames are already on disk). It only opens the roof once it's dark and safe, so resuming at any hour is safe.
v20.48 — Scheduler manages the roof + campaign hardening
The last piece of hands-off nights: Starship now opens and closes the roof itself. The scheduler opens (and unparks) when it goes dark and safe, and closes (and parks) at dawn, on an unsafe verdict, or when the whole campaign is complete — Schedule → Campaign settings → Manage roof (on by default). CloudWatcher stays hardwired to the roof as the hardware guarantee (it always closes on unsafe and won't open against one), so these are command-and-report on top of that. Voyager is no longer in the loop for the roof.
Also: an adversarial review of the campaign found and fixed six issues — the most important being that a post-emergency auto-recovery now correctly relaunches the scheduler (it was leaving the night without a dispatcher), and a graceful restart now stops the campaign cleanly. Plus smaller fixes: a template's altitude floor can no longer fight the scheduler's, chunks never overrun a target's set time by a frame, and same-named templates can't silently clobber each other.
v20.47 — Unattended campaign complete: set targets, let it run for nights
The whole thing is now wired end to end. Build a target pool (each target its filters + subs), an observing template whose target is a variable, and a blockscript that runs the scheduler unattended until every goal is acquired.
- Scheduler finished: a strategy toggle —
dispatch(best target now,
re-score each chunk) or finish_target (stay on a target until it's done or sets); per-filter moon avoidance (separation + illumination, narrowband relaxed — a target with no shootable filter right now simply isn't picked); and chunks that never overrun a target's set time or dawn.
- Unattended blockscript: a new Run scheduler action makes the scheduler
the campaign body. It images night after night; when all goals are complete the session shuts down. Weather pauses and device-loss recovery work throughout.
- Templates in the UI: "Save as template" in the sequence editor turns a
sequence into a recipe (its first target becomes the variable); pick a template per project on the Schedule page; set strategy / moon avoidance / default recipe in the new Campaign-settings card.
Roof actuation between nights (closing during the day) remains tied to the verified roof-control layer and is the next step before fully hands-off nights.
v20.46 — Unattended campaign (1/4): scheduler observing templates
First piece of the "set a target list, let it run unattended for nights" system. The scheduler now images each target through an observing template whose target is a variable — you design the recipe once (slew/focus/guide/dither), and the scheduler swaps in a target from the pool and injects that target's filters/subs. The built-in default template reproduces the previous behaviour exactly, so nothing changes until you start using custom templates. Each project can carry its own template (per-target recipes). Settings: [scheduler] default_template and strategy. (Next pieces: target strategies + moon/dawn gating, then the unattended blockscript that loops the scheduler across nights.)
v20.45 — Resume + collimation review fixes
An adversarial review of the checkpoint/resume and collimation work found five real issues, all fixed:
- Resume now replays one-time setup. It only skips completed targets, so a
hand-authored guiding/arm step placed once before the first target is no longer dropped on resume.
- Resume is frame-accurate even for grouped exposures. A
30×300sstep
interrupted at sub 20 now re-shoots only the remaining 10, not all 30 (the default editor path was already per-sub; this fixes raw/count=N plans).
- Resume points at the right field first. On resume, the start-up
precise-point / autofocus now uses the resume target's coordinates, not the first target's.
- Collimation now refuses calibrate / auto-collimate immediately if GRBL
isn't connected (instead of a job that fails a moment later), and the progress log shows clean messages instead of raw dicts.
v20.44 — Frame acceptance: per-filter thresholds + live tally
Frame acceptance (rejecting bad subs as they're captured) was already live; two refinements:
- Per-filter thresholds. Narrowband subs have far fewer stars and larger HFR
than broadband, so a single global "min star count" wrongly rejects them. You can now set per-filter overrides as JSON in Settings → Frame acceptance, e.g. {"Ha":{"min_star_count":4,"max_hfr_px":5.0}} — anything not overridden falls back to the global gate.
- Live tally. The run monitor now shows how many frames were accepted,
rejected, and re-shot this run.
(The out-of-process ASCOM driver host — isolating a wedged driver in its own process — is deliberately deferred: it's a large refactor that only pays off if the existing fail-closed watchdog proves insufficient in the field.)
v20.43 — Sequencer checkpoint / resume (survive a crash mid-night)
A crash, power blip, or restart in the middle of a sequence used to lose the whole session. Now a standalone run checkpoints itself at every step, and if it's interrupted you can pick up where it left off:
- A Resume button appears on the sequence editor when a saved plan has an
interrupted run (it shows how far it got, e.g. Resume (42/120)).
- Resume re-runs the on-start phase first — re-point, re-focus, re-guide,
because conditions drift during downtime — then continues from the target it was on, skipping the frames already captured (it only re-shoots the interrupted sub onward, not the whole target).
- It refuses to resume if you've edited the plan since (the structure no
longer matches), and a clean finish clears the checkpoint so the next run starts fresh.
Supervised runs (blockscript / scheduler) are unchanged — they own their own night-level recovery. The normal start path is byte-for-byte identical.
v20.42 — Collimation: the closed loop (calibrate + auto-collimate)
The Collimation page could already measure the error and let you nudge the mirror by hand. Now it can close the loop:
- Run calibration pokes each actuator, measures the donut response, and builds
the influence matrix (rejecting an ill-conditioned result), with a live progress log and the condition number reported.
- Auto-collimate then solves the live error against that matrix and drives the
mirror to minimum — gain-damped and travel-limited. It stays disarmed until you set [collimation] auto_collimate_enabled = true (the button is disabled until calibrated, and the loop refuses to run while disarmed).
Both run as cancellable background jobs. As always with collimation, the capture and motor moves are real hardware — watch the first moves at the mirror.
v20.41 — Flat cover for real + Skyhunter staleness nudge
- Close flat cover actually closes the cover. The
close_flat_cover
end-of-run action used to be a journaled placeholder; it now drives the CoverCalibrator (OpenCover/CloseCover) for real — and the same fix makes warm-up camera at end real too. There are also new Open flat cover / Close flat cover phase actions you can drop into any sequence phase.
- Skyhunter warns when moving-body data is stale. Asteroid/comet elements
drift over weeks and satellite TLEs over days, so the Skyhunter page now shows a banner when the asteroids/comets catalogs are >30 days old (or the TLEs are >14 days old, or not fetched yet), pointing you at the refresh script to run.
v20.40 — Skyhunter: moving small bodies (asteroids, comets, satellites)
Skyhunter can now find and plan things that move — all computed offline from elements you fetch when online, the same way the planets work.
- Asteroids — the brightest ~525 minor planets are bundled (Vesta, Ceres,
Pallas, Juno…). Search "Vesta", and they show up in Tonight's Best and the finder chart with their current position and brightness (IAU H,G model). Refresh with scripts/build_skyhunter_asteroids.py (JPL SBDB, or a local MPCORB.DAT for an all-MPC pipeline).
- Comets —
scripts/fetch_skyhunter_comets.pypulls the current bright
comets from the MPC and keeps the ones worth chasing. A new solver handles the near-parabolic and hyperbolic orbits comets actually have (the planet engine only did ellipses). Predicted comet magnitudes are rough MPC estimates — the detail view says so.
- Paste your own elements — a box that takes an MPC one-line comet/asteroid
record, or a plain labelled set (a, e, i, node, peri, M, epoch…), for anything not in the bundle. Computed with the same engine.
- Satellite passes — a heads-up predictor, not a sequence target: when
the ISS (or any bright satellite) crosses your sky, how high, which way ("NW→SE, peak 72°"), how bright, and whether it'll be lit. A real near-Earth SGP4 propagator (validated to nanometres against the standard test vectors), with Sun/Moon transit-candidate flags. Refresh TLEs with scripts/fetch_skyhunter_tle.py. Geostationary/GPS-type orbits are flagged and skipped (they need a different model and don't make fast visible passes).
The astronomy was cross-checked against JPL Horizons (asteroid + three comets spanning all orbit types) and the canonical SGP4 verification vectors; the planet ephemeris was left untouched.
v20.39 — Dome control + live slaving (deployable for hardware test)
The full dome subsystem, so you can put it on a real dome in the next few weeks:
- A dedicated Dome page (left nav) — live status (azimuth, shutter, home,
park, slewing, slaved), a big Slave / Unslave button, motion controls (rotate-to-azimuth, find home, park, open/close shutter, abort), and the sync calibration workflow (capture a sync at the current pointing, list per pier side, delete, clear).
- Live slaving — when slaved, a fail-safe background loop reads the mount's
HA/Dec/pier-side, runs ASDM, and rotates the dome to keep the slit on the optical axis. Dead-banded (no motor hunting), frozen near the zenith, and any error stops commanding rather than driving to a wrong azimuth.
- Sequencer actions —
slave_dome/unslave_dome, plusdome_park,
dome_find_home, and dome_goto (azimuth), so a night can slave on start and unslave/park on end.
- Sync points persist (per pier side) and the geometry is sanity-checked
(refuses to slave if the mount + GEM offset reach outside the dome).
Still hardware-pending: this is logic-complete and capability-guarded, the ASDM engine is unit-tested (16 tests), but the actual dome motion can only be proven on the real dome. The calibration solver remains a later, gated stage.
v20.38 — Domes: enclosure type + the ASDM slit model (engine)
Starship now knows whether your observatory is a roll-off roof or a dome. Set it in Settings → Observatory → Enclosure type. A roll-off roof needs no azimuth tracking; a dome unlocks ASDM (Adaptive Sync Dome Modelling), which keeps the slit aligned with the telescope's optical axis.
This release builds the ASDM engine (geometry + the adaptive correction layer) as a pure, unit-tested module — deliberately not wired to hardware yet, exactly as the spec demands ("don't slave hardware until the model passes its tests"):
- Geometry — a ray-from-aperture / dome-sphere intersection, with the GEM
pier-side offset handled correctly and the sign conventions pinned for both hemispheres (the southern-hemisphere azimuth sign is the classic silent failure — it's now a test).
- Adaptive correction — operator "sync" points store the residual on the
model (per pier side), interpolated without overshoot; where there's no nearby sync it falls back to pure geometry and says so.
- 16 unit tests, including the core invariant: mount at dome centre + no GEM
offset ⇒ slit azimuth equals pointing azimuth.
The dome parameters live in the Observatory settings. Next stages (not yet built): the operator sync-capture workflow, the calibration solver, and live slaving.
v20.37 — Skyhunter: planets, a finder chart, moon-aware ranking
Four follow-ups to Skyhunter:
- Planets + the Moon are now live targets — search "Jupiter", "Mars", etc., and
they appear in Tonight's Best with their current position and brightness (computed locally, no internet). The astronomy was validated: the internal Sun position matches to 0.4 arcmin, and every planet's distance/magnitude checks out.
- Offline finder chart — every object detail now draws a star chart from the
bundled catalogs (bright stars + nearby objects + planets) with your camera's FOV box overlaid, N-up / E-left. Framing now works with no internet — exactly what Aladin couldn't guarantee.
- Moon-aware "Tonight's Best" — the ranking now docks targets that sit close to
a bright, high moon, so a 76%-lit moon pushes nearby targets down and favours the dark side of the sky. It shows the moon's altitude and the adjusted score.
- "+ Sequence target" got cleaner names and a confirmation, and warns that a
planet's coordinates are a snapshot (they move) if you'll run the plan later.
v20.36 — Skyhunter: offline targets + tonight's visibility
A new module for choosing and planning targets without the internet — because a remote rig shouldn't depend on an online name-resolver or a second planetarium app (Sky Atlas's Aladin just proved that point).
- Offline catalog — a bundled, magnitude-filtered slice of OpenNGC: **3,919
objects** (106 Messier), 242 KB, with type / magnitude / size. Search by name, Messier number, type or constellation.
- Tonight's visibility — for any object: the altitude curve over the night,
transit, the observable window (above your altitude floor and in darkness), and moon separation + illumination. All computed locally from your site.
- Tonight's best — ranks the catalog by peak altitude during tonight's dark
window, so you can see what's well-placed at a glance.
- One click to act — "+ Sequence target" drops the object straight into the
sequence editor; "Frame in Sky Atlas" hands it to the framing view.
Find it in the left nav as Skyhunter. (The astronomy was validated end-to-end: M42's transit altitude, the Sun at solstice, the Moon's motion and phase all check out.) It's renameable — Object Hunter / Plan Commander — in one place.
v20.35 — Sequence shot-row column alignment
The per-shot table's number columns (Exposure / Count / Bin / Gain) had right-aligned headers but their input cells weren't aligned, so in the full-width table the inputs sat at the left of their stretched columns and drifted out from under their labels — the "field/label mismatch." The data was always correct; this is purely the visual alignment. Each value now sits under its header.
v20.34 — Binning fix, sequence recall fix, wizard redesign
Binning. A saved plan's expose step had binning = 10 — the binning box is max=4, but browsers don't enforce that on a typed value, so "10" got saved and the sequencer shot heavily-binned subs (the camera clamped 10→4). Now the binning is clamped to 1–4 both when the form is read and at compile time, the camera is restored to full-frame/bin 1 after an in-sequence refocus, and the m8 m20 plan is corrected to bin 1. Re-run it for full 26 MP subs.
Sequence recall. Recalling a saved plan showed an empty form (targets gone) whenever the plan used guiding — the form rebuilder bailed on the start_guiding / stop_guiding steps. Those are now skipped during reconstruction, so a normal guided plan round-trips and keeps its targets and shots.
Focus Wizard — single adaptive pass. The wizard no longer traces a full coarse V then a separate fine V. It now starts from where you are, racks out, samples inward, and stops a few points past the minimum (no need to climb the whole far arm). If it can't bracket a clean V it halves the step and retries, and only gives up (with a "re-center near focus" message) after a couple of tries. Leave the step blank to reuse what worked last time. Faster, and it keeps a usable spread of samples either side of focus.
v20.33 — Sky Atlas fix (Aladin pinned)
The Aladin sky view loaded from the CDN's /v3/latest/ path, which drifts as Aladin ships new builds — latest no longer matches the last release (3.6.1), and the newer build broke the embed. Now pinned to the stable 3.6.1 release.
Also fixed a no-retry bug: a single (even transient) load failure set a "tried" flag that was never reset, so Sky Atlas stayed permanently disabled until a full page reload. It now retries the next time you open the tab.
> Milestone this session: the first successful unattended sequence — m8 m20 > opened the roof, plate-solved, refocused in-sequence (pos 2362, HFD ~2), guided > at RMS ~0.1, shot 10×60 s, and parked — zero errors. The v20.32 sequencer > refocus and filter no-op both held live.
v20.32 — Sequencer + focus fixes from a live test
Five issues surfaced running a real sequence:
- Sequencer refocus wrecked focus and couldn't be stopped. The
autofocus
step fired the old run_autofocus() as a detached background job — it walked the focuser ~1000 steps off the new calibration, and because it ran outside the sequence, Stop / Halt-all couldn't reach it. Now the autofocus action and the auto-refocus policy both run the validated run_refocus synchronously and cancelably (a watchdog wires the sequence's Stop to the focus run). Stop now actually aborts an in-progress refocus.
- Colour camera, no filter wheel — no filter needed. The event filter
dropdown gains a "— none —" option (a blank filter was silently defaulting to "L" and then failing the run), and a filter step is a harmless no-op when no wheel is connected instead of erroring "filter not found in wheel".
- The focus result is recallable. The calibration readout now shows
focus <pos> · step <n>, and a new "Go to focus" button drives the focuser straight back to the last saved focus position — handy after anything moves it off.
- "Use current" on a sequence target. Frame the target how you want, then one
click captures the mount's live RA/Dec (+ rotator PA) into that target.
v20.31 — Focus Wizard auto-derives its own step + sweep range
The Wizard used to sweep a fixed step (50) for the fine V — too coarse on a sensitive focuser, so the V looked blocky and the sweep ran wider than it needed to. Now the coarse step only finds the V; the fine step and range are derived from the V's slope for your rig.
- Step targets a fixed HFD change between samples (~0.4 px, since HFD = 2×HFR
and an arm moves 2×slope per step). A steeper, more sensitive focuser gets a smaller step automatically; every arm ends up sampled at the same resolution. Floored/ceilinged at the focuser's usable step (8…150).
- Range ends where HFD has climbed to ~2–3× the minimum — the steep, roughly
linear part of each arm — with a small margin, never past the V-curve edge. Points per arm stay in the 4–6 band for a robust fit.
On the rig this matches by itself the step 25 / ±125 / 5-points-per-arm that gave the cleanest, fastest V by hand (vs the old fixed 50 / ±250).
The derived step and range are written into the Refocus recipe (so Refocus inherits the tighter sweep), and refined per-rig across runs — each Wizard run blends its result with the last one so the recipe settles instead of chasing the seeing. The recipe records why it chose what it did (slope, HFD-per-step, how far out the outer sample sits). The sequencer's run_autofocus is unaffected.
v20.30 — HFD measurement fix (the real reason focus was off)
A well-focused frame that Voyager measured at HFD 3.1 read ~11.5 in Starship — the half-flux measurement itself was inflated ~7×, so every V-curve, the "baseline HFR too high" warnings, and per-frame acceptance were all built on a broken number.
Cause: compute_hfr summed flux out to an aperture with a 20px floor. For a compact, well-focused star (~2px), a 20px aperture covers ~1250 pixels, and with only a global sky background subtracted the residual pedestal dominates the half-flux sum — pushing the measured radius out toward the aperture's geometry (~R/√2 ≈ 14) instead of the star.
Fix: aperture floor 20 → 6 px (the adaptive est_radius × 1.6 still grows it for real defocus donuts), plus a local annulus background (median of the 0.7R–R ring) instead of the global frame background. Verified on a real on-rig frame: 399 stars now read HFR 1.68 / HFD 3.37 (was 11.86), matching Voyager's 3.1.
The focus dashboard and ops log now display HFD (= 2× the internal HFR) so the numbers read the same as Voyager. The internal metric, thresholds and saved calibration stay in HFR, so the sequencer's run_autofocus is unaffected.
v20.29 — Autofocus redesign: two buttons (Wizard + Refocus)
Autofocus is now two buttons with one consistent method. Global rules for every focusing action: no slew-to-star, bin 3 + 5 s fixed, full-frame multi-star median HFD, and every sample reached by a final inward move so backlash is always taken up the same way (focuser OUT = increasing on this rig).
Wizard (assumes you start near focus):
- Racks OUT by the coarse offset (550), moves IN to clear backlash, then
samples every step (50) inward until HFD drops to a minimum and climbs back up to the starting HFD — a full V.
- From that coarse V it derives a tight range (~4–6 samples per arm).
- Runs a fine V-curve over that range, moves to best focus, and saves both a
calibration and a Refocus recipe (range / step / focus).
Refocus: a tight, always-inward sweep around the current position using the saved recipe → fits the V → moves to the mathematical minimum and reports "focus point found at position X", with the two convergence lines and the focus position drawn on the graph.
New endpoints POST /api/console/autofocus/{focus-wizard,refocus}. The Console now shows just Wizard… / Refocus (+ Clear cal); the Settings shortcuts were repointed. The old First Light Wizard / Linear sweep / Run-autofocus paths remain in the backend (the sequencer's auto-refocus still uses them) but are no longer shown in the UI. The sweep's capture + focuser loop is written-until-rig.
v20.28 — Linear sweep autofocus (full-frame, multi-star)
A second autofocus mode alongside the powers-of-2 First Light Wizard, built to replace a real on-rig failure: the wizard picked a single faint anchor star and its auto-exposure ramped to 30 s. The linear sweep is simpler and more robust on a wide field:
- Full frame, multi-star. It measures the **median HFD of every detected
star** — no single anchor to land on, no slew to a bright star needed.
- Fixed exposure / binning. No auto-exposure ramp; it uses your Autofocus
settings (now full-frame, bin 3, 5 s by default).
- Uniform step you control. From the current (manually-focused) position it
moves OUT by the start offset (default 200), then steps IN by the step (default 50), sampling until the V is bracketed (HFD drops then rises). It fits the V with the same fitter as the wizard, moves to best focus, and saves a calibration in the same file — so a linear-derived calibration and a wizard-derived one are interchangeable.
Run it from Console → "Linear sweep…" (a modal with start-offset / step inputs and the live V-curve plot), or the Linear sweep shortcut in Settings → Autofocus. New endpoint POST /api/console/autofocus/linear. The sweep's capture + focuser loop is written-until-rig.
v20.27 — Site / Location settings tab
The observatory's coordinates were tucked away as a sub-group inside the Mount tab — easy to miss, and leaving them at 0,0 silently breaks the focus-star slew (that's what stopped "slew to a suitable star" on the rig), AIRMASS headers, the scheduler's observability check, and the sky charts. They now have their own discoverable tab: Settings → Site / Location. Set latitude / longitude / elevation by hand, or Get from mount to pull SiteLatitude / SiteLongitude / SiteElevation from a connected mount. The Mount tab points you there.
v20.26 — collimation reset, made safe
The collimation motors already store their position (persisted to disk after every move, reloaded on connect, so it survives a restart) and Reset to start already returns them to the marked origin. Two safety guards added for confident on-rig testing (the hardware-validated GRBL motor code itself is untouched):
- Mark start now asks for confirmation — so you can't accidentally re-zero the
origin mid-session and lose your known-good reference.
- Reset to start shows the exact reverse move it will make (Rho/Sigma/Tau
steps) before it runs, and reports what it moved afterwards.
v20.25 — running version shown at the top
The build Starship is running now appears at the top of the app, next to the brand (e.g. v20.25). It's fetched from the server (GET /api/version), so it reflects the actual running build — and if the page you're looking at is stale relative to the server, the badge turns amber and tells you to hard-refresh.
v20.24 — the AstroWorx client experience (skin + guts, slice 1)
Your AstroWorx Voyager-broker client dashboard, ported onto Starship — the same branded experience, now powered by Starship and gated by your roles + bookings:
/welcome— a branded landing with three tiles: Live Dashboard · Book the
Telescope · Operator Admin.
/operaterebuilt in the AstroWorx skin (Fraunces serif, starfield, dark
astro theme): sign-in, role badge, live booking countdown, SAFE/UNSAFE banner + weather chips, live frame (latest capture + fullscreen viewer + exposure progress), Camera/Mount/Focus/Guiding sparkline, point→slew, capture, cooler, focuser, track/park, Sequence runner, Observatory roof, AllSky + CCTV feeds, a Tonight altitude + moon graph, and a Captures gallery — every control shown only if your role allows it, and enforced on the server.
/account— sign in, request an imaging window, see your bookings + recent
photos.
Next slice: self-signup, a booking calendar with busy-view + self-cancel + auto-approve, per-session photo tagging, Framing/Aladin FOV, and the mini sequence editor.
v20.23 — "Preview client dashboard" button
Settings → Web server now has a Preview client dashboard button that opens the /operate page (your remote client/student console) in a new tab, plus a Copy link button for the URL to point your Cloudflare tunnel at.
v20.22 — Equipment tiles show configured devices (even when offline)
The Devices grid said "no devices configured" whenever nothing was connected — because it only listened to live device events. It now also reads the configured slots the server already knows about, so every assigned device gets a tile (shown offline with its ProgID) and fills with live data the moment it connects. Each tile has its own Connect / Disconnect button. So your mount, camera, focuser, guide cam, roof and power show up as tiles right away — click Connect (or Connect all) and they go live.
v20.21 — richer Equipment tiles (trends, exposure, sky position)
Three additions to the device tiles:
- Trend sparklines — a rolling mini-graph of sensor temperature and HFR
(from graded frames) on the camera tile, and guiding RMS on the guiding card. They build up live from the WebSocket feed.
- Live exposure progress — while a capture is running, the camera tile shows a
progress bar with the exposure message and percentage.
- Alt/az sky position — a little polar indicator on the mount tile (N up, E
right, zenith at centre, horizon at the rim) showing where the scope is pointing.
Presentation only — reload to see it.
v20.20 — the student / client /operate dashboard
A separate, hardened page for renters and schools to operate the rig over your Cloudflare tunnel — dark-theme and touch-friendly. Open it at /operate.
- Sign in (when access control is on) → a header with your role, a live
booking countdown ("session ends in 42m 10s"), and Log out.
- The same live mission-control view — safety, weather, device tiles, guiding,
subsystems, activity — over the live feed.
- Operate controls that match your permissions: a Target panel (type a
name → Resolve → Slew) and a Capture panel for those allowed, plus per-device tile actions (track, cool/warm, focus, filter, rotate). A control only appears if your role grants the capability — and it's still enforced on the server, so hiding a button is never the only thing stopping it. Viewers get a read-only dashboard.
- The page carries no admin/settings code at all — minimal attack surface.
Set up types + users in Admin → Users & Access, enable [access], and point clients at /operate.
v20.19 — Equipment dashboard: device control tiles
Following the Voyager dashboard model, every device is now a proper tile with status and actions, not just a readout:
- a connection header (online / offline / fault), a big primary readout
(mount altitude, camera sensor temp, focuser steps, current filter, rotator PA), live values and status chips, and
- an action row wired to the real Console endpoints: mount **Track / Stop /
Park / Unpark / Abort; camera Cool (type a target °C) / Off / Warm; focuser Go / −100 / +100 / Halt; a filter dropdown; rotator Rotate / Halt; observatory Open / Close roof; and Connect all / Disconnect all**.
Every action is enforced server-side by capability (a role without the permission gets "not allowed"), and the live re-render won't wipe a value you're mid-typing.
v20.18 — the Equipment page is now a live status dashboard
The Equipment section was a thin read-only ASCOM probe; it's now a "mission control" view of the whole rig, updating live. No new backend — it presents the telemetry already streaming over the WebSocket:
- Safety banner — big SAFE / WARNING / UNSAFE / UNKNOWN with the reasons.
- Sky & weather — Solo/CloudWatcher: cloud, ambient, dew, humidity, wind, sky
brightness, with per-sensor safe/warn/unsafe chips.
- Activity — what it's doing now: the blockscript supervisor state, the
scheduler's current target, a sequence progress bar, the active job.
- Device vitals — rich cards: mount RA·Dec / Alt·Az / pier / tracking·slewing·
parked, camera cooler temp→setpoint + a power bar + gain, focuser position·temp· moving, current filter, rotator angle.
- Guiding — PHD2 state + RMS, and a Subsystems health grid for every module.
Opening the page hydrates from the REST snapshots; after that it's driven live by the WS feed. CSS/JS only — reload to see it.
v20.17 — configurable user types + the Users & Access page
You can now declare your own user types and tick exactly what access each one gets. Roles became editable records instead of hardcoded sets:
- Role editor (Admin → Users & Access): the five built-ins (owner / admin /
operator / student / viewer) are seeded and editable, and you can add custom types with their own capability mix and a "needs a booking to operate" flag. Capabilities are the fixed, server-enforced gates — a type can only bundle existing powers, never invent new ones. The owner type stays a locked superuser so you can never strip your own access.
- User management on the same page: add accounts, assign a type, activate/
deactivate, reset passwords.
- Works before you enable access control, so you can set up types and users on
localhost first, then flip [access] on for the tunnel.
Security review fixes (an independent adversarial pass — the core authorization came back clean): capability grants are now clamped so an admin can't hand out a power it doesn't hold; an error reading the access flag now fails closed; the last owner can't be demoted/deactivated/deleted; brute-force lockout keys on the real connection (not a spoofable header); and the role registry/build paths were hardened. 12 new tests.
v20.16 — access control spine (multi-user / rent-the-rig, default OFF)
The foundation for renting imaging time and giving schools/test-drive access over a Cloudflare tunnel. Default off — nothing changes for the single-operator localhost setup until you enable [access].
- Roles × capabilities — owner / admin / operator (paying client) / student
(test-drive) / viewer, each mapped to a capability set. Clients and students never get settings, device assignment, the roof, parking, or the safety supervisor — those stay with your supervising blockscript.
- App login + sessions — PBKDF2 passwords → role-scoped, expiring session
cookies (HttpOnly, SameSite, optional Secure). Brute-force lockout throttles failed logins. Sessions live in memory and clear on restart.
- Rental bookings — a non-privileged role may only operate during an
approved booking window; view-only and self-service booking requests are allowed any time. Owners/admins approve, deny, and schedule.
- Server-side, fail-closed enforcement — every request is checked against a
route→capability table. Any endpoint not explicitly opened to clients defaults to an owner/admin capability, so a new or forgotten route is locked by construction. The UI hiding controls is only cosmetic; this is the real gate.
- Endpoints:
/api/auth/{login,logout,whoami}and
/api/access/{catalog,users,bookings,audit,sessions,…}. Enabling seeds an owner from your [web] credential so you can never lock yourself out.
- 23 new tests including a real end-to-end HTTP enforcement test.
Still to come (next slices): the hardened /operate client page (the live status dashboard, restricted to what the role allows), the admin Users & Access UI + booking calendar, and an independent security review.
v20.15 — finished the buttons too (defined .btn)
Your screenshots showed the inputs were fixed (v20.14) but lots of buttons were still white — Start, Stop, Resolve, Add project, remove plan, "+ Add action", the scheduler's Ask Pete. Root cause: the .btn class was never actually defined, so every class="btn" button without its own inline background fell back to the browser's white default. Defined it — a dark secondary-button look with hover / active / disabled states. Buttons that set their own background (the blue "Save & restart", the accent primaries) are unchanged. With v20.14, the text fields and buttons are now consistently themed across the whole app. CSS-only — reload to see it.
v20.14 — finished the text fields (global field theming)
Some text inputs across the app were rendering with the browser's default white background — unfinished against the dark theme. The codebase had only patched two modules by hand (Response engine, Schedule). Replaced that whack-a-mole with one global rule: every text-like input / textarea / select (text, number, password, search, email, url, tel, time, date, datetime-local, month, and untyped) now gets the dark field look by default, plus a consistent accent focus ring. No control ever falls back to white again, and any input added in future is themed automatically. Inline + per-module styles still win where they're set, so nothing that was already styled changes. CSS-only — reload to see it.
v20.13 — "Ask Pete" in the editors
The pre-flight checker is now a button, not just an API. An "Ask Pete" button sits in the sequence, blockscript, and scheduler editors; clicking it dry-runs the current config (the sequence you're editing, the selected blockscript even unsaved, your live project list) and pops a report modal — Pete's avatar, a verdict pill (errors / warnings / all clear), the findings with ✓ / ⚠ / ✗ icons and where each one is, and his one-line take ("I wouldn't run this yet — 3 things are broken" / "Looks clean, I'd hit start"). Reload to see it.
v20.12 — "Ask Pete" pre-flight checker (slice 1)
Point Pete at a sequence, blockscript, or scheduler config and he tells you what would break — before the night does. Two layers:
- Static lint (fully reliable for "undefined") — every dangling reference is
caught: a filter that isn't in your wheel, a slew with no coordinates or target, a run_sequence naming a plan that doesn't exist, a blockscript goto to a state that isn't there, an unknown action type, a retry_connect for a device you don't have.
- Light dry-run — a sequence is walked (compiled, so always finite) for a **time
estimate + value sanity; the scheduler is scanned against the sky to flag a target that never clears your horizon** tonight (it would sit idle forever); a blockscript is checked for goto self-loops.
- Honest about "endless loops": truly proving a script halts is the halting
problem — no tool can. Pete instead flags the structural mistakes (a self-loop, a goal that can never complete) and is upfront that he can't certify termination.
- API:
POST /api/pete/check {kind: "sequence" | "blockscript" | "scheduler", ...}
returns Pete's report (findings + a friendly summary). 14 new tests (517 total).
- Next slices: the "Ask Pete" buttons + report panel in the editors, blockscript
reachability, and scheduler nights-to-complete.
v20.11 — the simulation gauntlet ("Pete's week")
Verification infrastructure (no runtime change) — a repeatable, deterministic way to exercise the whole unattended stack without hardware, narrated through the operator persona "Pete":
tests/test_week_simulation.py— a 7-night observatory-lifecycle campaign. Each
night the REAL blockscript action runner opens up (unpark → open roof → cool), the REAL scheduler dispatches targets onto the REAL sequencer run loop, the REAL engine handlers react to trouble, and the runner shuts down (warm → park → close roof). Across the week it drives + asserts recovery from a dropped sub (sequencer retry), a focuser USB drop (retry_connect reconnects), a clouded-out night (never opens), mid-night cloud (close + reopen), and a mount lost past the retry cap (engine escalates to EMERGENCY → HALTED and the rig is parked + shut). The invariant it guarantees: roof closed + mount parked every dawn, and the goals complete across the week despite the lost nights.
tests/test_night_simulation.py— the single-night version. Both run with-s
to print the night narrative.
- These join the existing pieces (no new product surface). 503 tests total.
v20.10 — scheduler ↔ blockscript integration + night simulation
The dispatch scheduler (target scoring, chunked dispatch, frame ledger — already built) is now joined to the blockscript supervisor, and a full night is simulated end-to-end.
- Scheduler runs under blockscript supervision. When a blockscript is active,
the dispatcher runs each chunk as an injected run, so the blockscript owns the observatory (roof/mount/safety) and the scheduler just chooses + images targets.
- A real conflict the simulation surfaced + fixed. My v20.9
body_donefired on
every injected run — which would have shut the blockscript down after the first scheduler chunk. Separated injected (skip observatory actions) from signals_body_done (RUNNING→SHUTDOWN): only the run_sequence action's own body signals done; scheduler chunks don't, so a many-target night runs to completion.
- End-to-end night simulation (
tests/test_night_simulation.py) — drives the
REAL scoring, REAL dispatcher chunk, REAL sequencer run loop (on a simulated rig), and REAL ledger, plus a REAL blockscript engine. It demonstrates: the scheduler picking M81/M51 by score, a transient camera glitch caught + recovered by the sequencer's retry handler, goals completing in the ledger, and the blockscript handling a device-loss (WARNING + retry) and a weather UNSAFE→resume. Run it with pytest tests/test_night_simulation.py -s to read the night narrative.
- 502 tests. The on-sky hardware paths remain WRITTEN-until-rig.
v20.9 — sequence + blockscript test-readiness
Wired the three gaps (found by an end-to-end audit + adversarial review) that would have silently broken a supervised test. The blockscript engine logic was already built and tested — only the connections were missing.
- Blockscript can actually run a sequence now. Added
console.run_sequence(plan),
which launches a sequence as an injected (supervised) run — the sequencer skips observatory-domain phase actions (roof/mount/park) so it doesn't fight the blockscript that owns them. The blockscript run_sequence action used to silently no-op (it returned OK but started nothing); now it really runs the plan.
- Device loss drives the blockscript.
BlockscriptManagernow bridges
equipment.device events into on_device_lost / on_device_recovered, so a device dropping mid-night triggers the engine's WARNING → retry → EMERGENCY path. A loss (unexpected drop with an error) escalates; a deliberate disconnect (no error) is ignored; healthy polls are de-duplicated so they don't spam recovery.
- Supervised sessions auto-close. On a successful injected run the sequencer
signals body_done, moving the blockscript RUNNING → SHUTDOWN — so the night packs up (per your shutdown actions) when imaging finishes. Stand-alone runs are unaffected (they never touch a blockscript).
- 13 new tests (501 total). One test-plan note: don't use the
close_flat_coveron-end
action in tomorrow's plan — it's still a journaled placeholder (real CoverCalibrator dispatch is a separate item). Hardware paths are WRITTEN-until-rig.
v20.8 — Auto Flat acquisition
Automatic flat-frame capture, modeled on Voyager's Auto Flat. It has its own Operations → Auto Flat panel (plan summary + Run/Cancel + live progress); the plan and settings are edited in Settings → Flat device. Also POST /api/console/flat/auto. It runs as a background job.
- Per-filter, target-ADU exposure — for each configured filter the runner meters
a test frame, scales the exposure linearly toward the target mean ADU (flats are linear above the bias), and once it's within max_err_pct saves count flats. Bounded by min/max exposure and a metering-iteration cap; if the panel can't reach target within bounds it saves the best effort and flags it.
- Flat panel driven automatically —
panelmode turns the CoverCalibrator light
on (per-filter brightness) for metering + capture and off at the end; manual mode leaves the light to you. Optional park + cover-close on completion.
- Per-filter plan — a list of `{filter, count, binning, gain, offset, exposure
bounds, target_adu, brightness}`; blank fields fall back to the global defaults. Editable as JSON in the Flat settings tab.
- Correct frame headers — flats are written with
IMAGETYP=FLAT(and the writer
now tags every frame LIGHT/FLAT/DARK), so stacking software classifies them right.
- 7 new tests (488 total). The on-sky/panel capture path is WRITTEN-until-rig; the
metering, exposure-scaling and per-filter loop are validated against a simulator. Sky dawn/dusk flats are structured in the config but not yet driven.
v20.7 — Solo driver-death heartbeat (closes the v20.5 known limit)
- Solo poll-thread liveness watchdog (
[health] solo_heartbeat_*) — closes the
follow-up flagged in v20.5. The safety supervisor is event-driven, so the weather-loss → EMERGENCY timer only advances while the Solo keeps publishing solo.reading / solo.error. In the normal comms-loss case the driver keeps erroring and publishing, so the timer runs — but if the poll thread itself dies silently, no events fire, the supervisor stops ticking, and the timer freezes, so a dead driver would never escalate. The health monitor now taps the raw Solo event stream directly and, on silence past solo_heartbeat_poll_multiple × the Solo poll interval (default 3×, floored at solo_heartbeat_min_age_s), drives the same force_emergency safe-state — the event-stream twin of the wedged-ASCOM-worker detector. It arms only after at least one Solo event has been seen (a never-started driver is a startup fault, caught by the supervisor heartbeat), and stands down if the supervisor's own loss escalation already fired, so the two never double-fire. Default on (solo_heartbeat_enabled = true): it never fires spuriously — only on multi-cycle silence — and closing the gap is fail-closed.
- 15 new tests (481 total). Pure software liveness + the existing (already-on-rig)
force_emergency escalation path; no new hardware-touching code.
v20.6 — PHD2 auto-start + per-filter focuser offsets
Two more from the Voyager gap roadmap, both gated default-off:
- PHD2 auto-start (
[phd2] auto_start+executable_path/profile/
connect_equipment) — when on, Starship launches PHD2 if it isn't already running, selects the equipment profile (by name or id), and connects its equipment, so an unattended rig needs no human to start the guider. It's self-healing — a crashed PHD2 is relaunched on the next connect retry (a reachability check prevents a second instance). Recalibrate-on-flip is deliberately left to PHD2, which already flips the calibration on a pier-side change. Off (default) = connect to a PHD2 you started yourself; Starship never launches or reconfigures it.
- Per-filter focuser offsets (
[autofocus] filter_offsets) — a map of filter →
focuser steps relative to a reference filter. On a filter change the focuser shifts by the chromatic delta instead of running a full autofocus; if a filter isn't in the map it falls back to the existing refocus-on-filter-change. The Navigator already computes suggested values from your real focus runs — copy them into the map (editable as JSON in Settings → Autofocus).
- 14 new tests (466 total). The PHD2 launch/profile path is WRITTEN-until-rig.
v20.5 — closing the loop on an unattended night
The three highest-value gaps vs Voyager for a remote rig, from the gap analysis — all gated default-off (a run at defaults is byte-identical to v20.4) and adversarially reviewed before shipping:
- Per-frame quality acceptance (
[frame_acceptance]) — grades each LIGHT sub as
it is captured (HFR / star-count / eccentricity / background, via the same metrics engine autofocus uses) and rejects bad frames (cloud, dew, wind, satellites). A reject is journaled and moved to a rejected/ subfolder so the stack stays clean; it can be re-shot, and abort_after_consecutive stops a clouded-out run. Fails open — a grading glitch never drops a frame.
- Guiding watchdog (
[guiding_watchdog]) — holds the next sub until PHD2 recovers
from a lost or high-RMS guide star, abort-aware, with a recover-timeout that fails the step into your on-error policy. Pairs with frame acceptance (which catches a sub ruined mid-exposure).
- Weather-loss → EMERGENCY (
[safety].weather_loss_emergency_s) — sustained Solo
silence/loss for this long latches EMERGENCY (park + close, manual reset) instead of flapping the roof under unknown skies. Fires once, resets when contact returns, and only on loss — reported rain/cloud still auto-recovers on dwell. Known limit: the timer relies on the Solo driver still publishing (a driver-death heartbeat is a tracked follow-up — closed in v20.7).
- 16 new tests (452 total). The on-sky capture/guiding/safety actions are
WRITTEN-until-rig; the control flow is validated.
v20.4 — autofocus watchdog + adaptive exposure
The two deferred autofocus pieces, both gated default-off so a run at default settings is byte-identical to before:
- Star-less frame watchdog (
hfd_watchdog_window) — abort the autofocus run
after this many consecutive frames detect no stars (clouds rolled in, the star drifted out of the ROI) instead of grinding through the whole sweep and then failing at the fit. 0 = off (default). The sweep keeps its existing coarse-approach retry and R²-fit gate; this just stops a hopeless run sooner.
- Run-time adaptive exposure (
af_adaptive_exposure) — when a focus point
detects no stars, retry it at a longer exposure (doubling from the AF exposure, floored at Min, up to Max) before giving up — recovers a faint star at large defocus. Off (default) = always one exposure. Min/Max exposure are now live in Settings.
- The existing
config.tomlwatchdog value was normalised5 → 0(it had a
default of 5 but was never actually read until now), so nothing changes on the rig unless you opt in.
- 8 new tests drive the real sweep through a simulated cloud-out (watchdog
aborts at the Nth frame) and a faint-star-at-defocus (exposure ramps up to find it). 436 tests total. The on-sky capture path stays WRITTEN-until-rig.
v20.3 — pending settings wired
- Rotator travel limits —
min/max positionnow enforced (out-of-range moves
refused), the same as the focuser limits.
- Fresh-solve overrides —
[camera] plate_solve_gain_override/
plate_solve_binning_override are applied to frames captured only to plate-solve (precise pointing, polar, rotate-to-PA); science frames are unaffected.
- Refocus triggers — auto-refocus during a sequence on temperature delta, every
N frames / minutes, or on a filter change, plus a post-focus offset. Opt-in via refocus_enabled (default off, so nothing changes unless you turn it on); the temp trigger pairs with the Navigator's learned temperature coefficient.
- Surfaced already-wired controls — cooling
no_cooldown_for_delta_cand the
autofocus hardware ROI (af_hw_roi_pct) are now editable in Settings (the old redundant pixel-width / legacy ramp fields are retired).
- The autofocus star-less watchdog + run-time adaptive exposure landed in v20.4.
v20.2 — camera FITS headers
- AIRMASS + OBJCTALT now computed (Kasten-Young) from the mount pointing, your
[site] coordinates and the time, and written into every capture's FITS header — stacking software weights frames by it.
- SITELAT / SITELONG / OBSERVER cards written, with a **Hide personal info in
FITS** toggle that strips your location for sharing frames publicly (AIRMASS stays).
- Download-save timeout is now read from
[camera] download_save_timeout_s
(both Alpaca and ASCOM capture paths) instead of a hardcoded 60 s.
- Four camera "pending" settings resolved; plate-solve gain/binning overrides remain
honestly pending (they need the dedicated fresh-solve capture worker).
v20.1 — Sky Atlas, plate-solve robustness, focuser prefs
- Sky Atlas — interactive Aladin sky view with camera-FOV framing, **mosaic
planning (rows × cols × overlap × rotation), and one-click sequence generation**.
- Plate-solve / pointing robustness — retry-with-longer-exposure ladder, a
configurable [platesolve] downsample, and pointing exposure raised 4 → 6 s (from a real ASTAP-failure log).
- Focuser manual-move preferences — reverse, backlash compensation (honors
IN/OUT), position-check tolerance, post-move settle, don't-halt-on-HALT-ALL — all isolated from autofocus, applied only to manual/sequence moves.
- Backlash measurement engine — optical two-approach apex method (for the
First Light Wizard to auto-measure + persist backlash).
- Navigator got its dashboard panel + API; competitor pain-point analysis
written (docs/competitor_pain_points.md) as the reliability roadmap.
v20 — reliability, auto-collimation, Navigator, scripts & plugins
The first major milestone after the v19 build line (v19.74–v19.82), consolidated into one release. The reliability + features push:
- Reliability audit — fixed a critical pier-crash hole (a failed meridian flip
could expose on the wrong side of the pier); the flip now runs on_error, marks the run FAILED, and stops. Plus 14 more sequencer / blockscript / scheduler fixes (blockscript emergency-recursion guard, force-override abort, scheduler frame counting, plan kind validation, guarded DB writes).
- Auto-collimation (Astroworx telescopes) — closed-loop, 3-actuator collimation
over GRBL steppers: influence-matrix calibration, gain-damped correction with project-out-piston, per-move + cumulative-travel clamps, divergence abort, and an arm flag. Disarmed by default.
- Autofocus — single-anchor apex verification fixes the saturation walkout
(bright stars saturating at focus no longer invert the multi-star median).
- Health monitor — two-tier memory safe-state (dismissible soft countdown vs a
non-dismissible hard floor), disk fill-rate forecast, CPU load/temp check, and a wedged-ASCOM-worker detector.
- Navigator (observe + learn, read-only) — captures focus / guiding /
collimation telemetry, builds per-rig models (focus temperature coefficient, filter offsets, bad-run detector, collimation drift, guiding baseline), and can email you a digest of suggestions / progress / ideas. Opt-in, default off.
- External scripts (
run_script) — run your own script/command at any point
in a sequence (phase action OR body step) or a blockscript state action, through one sandboxed executor (allowed-dir, no shell, hard timeout, abort-aware). Opt-in via [scripts].
- Plugins — drop a
.pyinplugins/; Starship calls itsregister(service)
at startup. Failures are isolated. Opt-in via [plugins].
- Dashboard/polar — memory-dismiss endpoint +
dismissibleexposed in health
status; polar alignment now forces tracking ON before the measurement sweep.
- New help topics: Auto-collimation, Field rotator, Scheduler, Blockscript,
Health monitor, External scripts & plugins.
v18 — help docs audit
- Reorganised help groups: Concepts → Modules → Reference → About
- New Getting started topic — first-night walkthrough from install to running a sequence
- New Roadmap topic — built / coming / deferred / won't-do consolidated in one place
- New Recent changes topic (this one)
- Updated all topics to reflect what's actually built post-v17
- Removed stale "autofocus is future" notes from Sequencer, Plans, Console topics
- Topic order within groups now follows typical user workflow
v17 — autofocus
- VCurve First Light Wizard with logarithmic powers-of-2 sweep, three resolution presets
- Calibrated autofocus run with coarse-approach retry and confirmation frame
- Star detection + HFR computation (pure NumPy, no astronomy-specific deps)
- Calibration persisted to
autofocus_calibration.jsonnext to config - New Autofocus section in Settings with all 14 parameters
- Three new buttons on the Console focuser card: Run autofocus, First Light Wizard, Clear cal
- AF modal with live SVG V-curve plot, progress bar, and result panel
- Sequencer focus policy now invokes real autofocus when calibrated; falls back to focuser_position otherwise
- AF failures during sequences are journaled but don't abort the run (fail-soft)
- HTTP endpoints for wizard / run / clear / calibration get
- Autofocus help topic added
v16 — in-app help
- Help button (
?) in topbar, opens modal anywhere - Keyboard shortcuts:
?andF1to open, ESC to close - 20 initial topics across Concepts / Modules / Reference groups
- Per-page help anchors on Console, Sequencer, Settings
- Built-in Markdown renderer (no external dependency)
- Search box filters topics by title and summary
- HTTP endpoints
GET /api/help(manifest) andGET /api/help/<id>(rendered)
v15 — camera cooling
CoolingConfigdataclass with eleven Voyager-style parameters- Five Console commands: cooler_status, cooler_set_power, cooler_set_target, cooler_wait_for_setpoint (interruptible), cooler_warm_up
- Worker functions for ASCOM (CoolerOn, SetCCDTemperature) and Alpaca (/cooleron, /setccdtemperature)
- On-connect cooling action via equipment.device bus events (edge-triggered)
- HTTP API: GET /api/console/cooler + POSTs for set_power / set_target / warm_up
- Sequencer Cooling tab with enable_at_start, setpoint_c, wait_for_settle, warm_up_at_end
- Sequencer hooks: cooling-on-start (with abort if wait_for_settle and timeout), warm-up-on-end
- Settings UI Cooling section
- Console live cooling widget with sensor temp / setpoint / Δ / power %, state pill, manual buttons
v14 — Voyager-style policy tabs
- Sequencer plan editor wrapped with policy tabs: Constraints, Focus, Cooling (added v15), Meridian, On End
- Plan-wide policy encoded as inline TOML table on a hidden notes step
- Constraints: altitude min/max, hour-angle min/max, end-by UTC; trip skips current target
- Focus triggers: every N frames, every X minutes, on Δ°C, on filter change (focuser_position fallback until v17)
- Meridian flip with re-slew, max minutes past, plate-solve recenter (stubbed)
- On End: park (real), warm-up camera and close flat cover (placeholders)
- All policy fields round-trip cleanly through save/load/restore
v13 — structured editor
- Multi-target plan model with rotate-per-frame and finish-each-filter execution modes
- Targets editor with per-target events table (filter, frame, exposure, count, binning)
- Two-tab layout: Targets / TOML
- Per-target time estimate
- RA/Dec from CdC, planetarium target name resolution
- 5.6h LRGB plan (M42, 243 compiled steps) verified end-to-end
v12 — sequencer foundation
- Plan / CompiledPlan / Executor / PlanLibrary
- Eleven instruction kinds (notes, set, wait, wait_until, slew, filter, expose, dither, focus, park/unpark/track, repeat, loop_targets)
- Run states (idle / running / paused / stopping / succeeded / failed / stopped)
- Per-step error policy (abort / skip / retry with retry_count)
- Pause/resume/stop tested end-to-end
v11 — visual identity
- Brand mark and lockup SVGs
Earlier
- v1–v10: core safety kernel, Solo driver, ASCOM/Alpaca transports, FITS viewer with STF, Cartes du Ciel TCP, ASTAP plate solver, FITS read/write/render, basic Console (capture/mount/focuser), web dashboard with WebSocket.