Keep guiding on offsets; cap deliberate offsets at 300", guiding at 60"#449
Keep guiding on offsets; cap deliberate offsets at 300", guiding at 60"#449cfremling wants to merge 3 commits into
Conversation
acamd's offset_goal() previously kicked the system from TARGET_GUIDE back into TARGET_ACQUIRE whenever an offset was applied without the "fineguiding" flag. Because the slicecam "put on slit" path and the sequencer's target_offset() (run right after fine acquisition) both send ACAMD_OFFSETGOAL without that flag, a guided "put on slit" and every post-fineacquire science offset would drop guiding and restart acquisition. There is no use case for re-acquiring on an offset while already acquired and guiding. Make offset_goal() always stay in TARGET_GUIDE while guiding, resetting the offset median-filter buffers so the new goal takes effect quickly (the same reset the fineguiding path already did). The "fineguiding" flag is no longer needed and is removed from acamd's offset_goal() and from slicecam's offset_acam_goal(). The fix is contained entirely to the ACAMD_OFFSETGOAL command handler. The acquisition loop is untouched, and behavior in non-guiding states (ACQUIRE, ACQUIRE_HERE, NOP) is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 05b27d5c14
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ( this->target.acquire_mode == Acam::TARGET_GUIDE ) { | ||
| // for slicecam fine aquisition/guiding, stay in TARGET_GUIDE but | ||
| // reset the filtering so the goal takes effect quickly | ||
| if ( is_fineguiding ) { | ||
| this->target.reset_offset_params(); | ||
| } | ||
| else { | ||
| this->target.acquire_mode = Acam::TARGET_ACQUIRE; | ||
| this->target.nacquired = 0; | ||
| this->target.attempts = 0; | ||
| this->target.sequential_failures = 0; | ||
| this->target.timeout_time = std::chrono::steady_clock::now() | ||
| + std::chrono::duration<double>(this->target.timeout); | ||
| } | ||
| this->target.reset_offset_params(); |
There was a problem hiding this comment.
Preserve the enlarged offset limit until the correction is sent
When offsetperiod is configured above 1 second and a put-on-slit or target offset exceeds ACQUIRE_TCS_MAX_OFFSET, keeping TARGET_GUIDE causes the offset to never be applied. On the first frame, do_acquire() permits the large offset using the one-shot putonslit_offset - last_putonslit_offset allowance, but median_filter() returns false while filling its window; last_putonslit_offset is nevertheless updated, so every subsequent frame rejects the still-pending correction against the normal maximum at acamd/acam_interface.cpp:3640-3648. The previous transition to TARGET_ACQUIRE bypassed the median filter and sent the correction during that first allowed frame.
Useful? React with 👍 / 👎.
| this->target.timeout_time = std::chrono::steady_clock::now() | ||
| + std::chrono::duration<double>(this->target.timeout); | ||
| } | ||
| this->target.reset_offset_params(); |
There was a problem hiding this comment.
Synchronize the guide-filter reset with median filtering
For ordinary guided offset requests, this newly added reset can run in the server command thread while the frame-grab thread is executing do_acquire() and mutating the same ra_offs, dec_offs, and time_offs vectors in median_filter() (acamd/acam_interface.cpp:3807-3820). Clearing those vectors concurrently with a push or sort is undefined behavior and can corrupt memory or crash acamd; use a shared lock or defer the reset to the acquisition thread.
Useful? React with 👍 / 👎.
A deliberate goal offset applied while guiding -- put-on-slit, offset-star acquisition, the end-of-fineacquire target offset, and the pyGUI 'Offset' button (all routed through offset_goal/ACAMD_OFFSETGOAL) -- can legitimately be much larger than an ordinary guiding correction. The 60" ACQUIRE_TCS_MAX_OFFSET gate would reject these. The previous mechanism enlarged the gate by the frame-to-frame change in a putonslit_offset (maxoffset = tcs_max_offset + |putonslit_offset - last_putonslit_offset|). That was fragile: it only widened the cap when the offset *changed*, the one-shot widening was consumed on the first frame even if median_filter deferred the send, and it was never reset between targets, so a second similar-magnitude offset was rejected at 60". Replace it with an explicit one-shot allowance: offset_goal sets allow_large_offset while guiding, and do_acquire permits that one correction up to PUTONSLIT_TCS_MAX_OFFSET (300"). The allowance is consumed only when the offset is actually sent to the TCS, so a median_filter deferral can no longer waste it. Ordinary guiding corrections keep the 60" cap so a bad solution can't slew the telescope, and the ACQUIRE path (which uses tcs_max_offset, raised to 300" by the acquire wrapper during acquisition) is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks Codex — both findings evaluated against the real code; both are valid. Update in commit P1 — large put-on-slit/target offset never applied ( P2 — data race on |
The 300" deliberate-offset cap was a hard-coded constant; the 60" guiding cap is the config parameter ACQUIRE_TCS_MAX_OFFSET. Make them consistent: add ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET (default 300") to acamd.cfg, loaded the same way as ACQUIRE_TCS_MAX_OFFSET, stored in Target::tcs_max_putonslit_offset with a matching setter/getter. The member defaults to 300" so existing configs that predate the new line still behave correctly.
|
Follow-up ( |
What this does
Two related fixes to how ACAM handles offsets applied while guiding.
1. Never drop out of guiding on an offset
offset_goal()previously kickedTARGET_GUIDE → TARGET_ACQUIREwhenever an offset arrived without afineguidingflag. Both the GUI "put on slit" path and the sequencer's post-fineacquiretarget_offset()sendACAMD_OFFSETGOALwithout that flag, so a guided put-on-slit and every post-fineacquire science offset dropped guiding and restarted acquisition. Dropping out of guiding on an offset is detrimental and has no practical use case; it is removed. No flag is needed to gate it, so thefineguidingflag is removed entirely. This matches the proven CF/mac-sim implementation ofoffset_goal.2. Cap deliberate offsets at 300″, ordinary guiding at 60″
A deliberate goal offset applied while guiding — put-on-slit, offset-star acquisition, the end-of-fineacquire target offset, and the pyGUI 'Offset' button (all routed through
offset_goal/ACAMD_OFFSETGOAL) — can legitimately be far larger than a guiding correction. The 60″ACQUIRE_TCS_MAX_OFFSETgate would reject them.The old mechanism widened the gate by the frame-to-frame change in
putonslit_offset— fragile: it only widened when the offset changed, the one-shot widening was consumed on the first frame even ifmedian_filterdeferred the send, and it was never reset between targets (so a second similar offset was rejected at 60″).Replaced with an explicit one-shot allowance:
offset_goalsetsallow_large_offsetwhile guiding, anddo_acquirepermits that one correction up toPUTONSLIT_TCS_MAX_OFFSET(300″), consumed only when the offset is actually sent to the TCS (so amedian_filterdeferral can't waste it).tcs_max_offset, which the acquire wrapper already raises to 300″ during acquisition.Scope / safety
offset_goaland thedo_acquiregate;allow_large_offsetis atomic (set in the command thread, read/cleared in the framegrab thread).Re: Codex review on the first commit
offsetperiod>1) — resolved: the new allowance is consumed only on an actual send and uses a flat 300″ cap (no delta math), so amedian_filterdeferral no longer wastes it.reset_offset_params) — dormant in practice:offsetperiod=1in the deployed config, wheremedian_filterreturns before touching the offset vectors, so there is no concurrent access. Left as-is.🤖 Generated with Claude Code