From 607141350f4fdc1dffc953741af4a5af59b10a98 Mon Sep 17 00:00:00 2001 From: Christoffer Fremling Date: Thu, 29 Oct 2026 16:22:43 +0200 Subject: [PATCH] acamd: do not acquire on a frame exposed before the acquisition started The framegrab runs through the slew and do_acquire() is called with the first frame to finish after the acquire command, which can have been exposed entirely while the telescope was still settling. On 2026-10-29, 4 of 18 acquisitions used such a frame (its solve landed 1.2-2.6 s after ontarget: a 2 s exposure started 1.5-3 s before it) and overshot the first correction by 46-107%, costing 1-3 extra frames each; every acquisition whose first frame began at or after ontarget corrected to within 35% (median 5%) in one move. Target::acquire() stamps the start; do_acquire() skips a frame whose readout time (mjd0) is less than one frame cycle after it. Costs one frame cycle only when the in-flight frame straddles the command. Co-Authored-By: Claude Opus 5.5 --- acamd/acam_interface.cpp | 13 +++++++++++++ acamd/acam_interface.h | 1 + 2 files changed, 14 insertions(+) diff --git a/acamd/acam_interface.cpp b/acamd/acam_interface.cpp index f3a0daf1..82e3126e 100644 --- a/acamd/acam_interface.cpp +++ b/acamd/acam_interface.cpp @@ -3394,6 +3394,7 @@ logwrite( function, message.str() ); this->sequential_failures = 0; this->allow_large_offset.store(false); // no stale deliberate-offset allowance this->is_acquired.store( false, std::memory_order_release ); + this->acquire_start_mjd.store( mjd_now() ); // Start the timeout clock, initialized as the time now plus the // configured timeout in seconds (duration defaults to seconds). @@ -3451,6 +3452,18 @@ logwrite( function, message.str() ); return NO_ERROR; } + // Skip a frame that began exposing before the acquisition started + // (mjd0 is the readout time, so allow one frame cycle). + // + if ( this->acquire_mode == Acam::TARGET_ACQUIRE || this->acquire_mode == Acam::TARGET_ACQUIRE_HERE ) { + const double cycle = ( iface->camera.andor.camera_info.kintime > 0 ? iface->camera.andor.camera_info.kintime + : iface->camera.andor.camera_info.exptime ); + if ( iface->camera.andor.camera_info.mjd0 < this->acquire_start_mjd.load() + cycle / 86400.0 ) { + logwrite( function, "frame exposed before the acquisition started: not used" ); + return NO_ERROR; + } + } + // Here is the acquisition sequence, called after every frame grab. // // Using that frame, call the solver and send offsets to the TCS. After this diff --git a/acamd/acam_interface.h b/acamd/acam_interface.h index 4c2f59fd..e82a9b8c 100644 --- a/acamd/acam_interface.h +++ b/acamd/acam_interface.h @@ -346,6 +346,7 @@ namespace Acam { int nacquired; int attempts; int sequential_failures; + std::atomic acquire_start_mjd{0}; ///< when the current acquisition was started std::atomic is_acquired; ///< set if target acquired successfully std::atomic stop_acquisition; ///< set if the acquisition sequence should stop