Skip to content

Keep guiding on offsets; cap deliberate offsets at 300", guiding at 60"#449

Open
cfremling wants to merge 3 commits into
mainfrom
fix/guiding-offset-stay-guiding
Open

Keep guiding on offsets; cap deliberate offsets at 300", guiding at 60"#449
cfremling wants to merge 3 commits into
mainfrom
fix/guiding-offset-stay-guiding

Conversation

@cfremling
Copy link
Copy Markdown
Collaborator

@cfremling cfremling commented Jun 8, 2026

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 kicked TARGET_GUIDE → TARGET_ACQUIRE whenever an offset arrived without a fineguiding flag. Both the GUI "put on slit" path and the sequencer's post-fineacquire target_offset() send ACAMD_OFFSETGOAL without 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 the fineguiding flag is removed entirely. This matches the proven CF/mac-sim implementation of offset_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_OFFSET gate 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 if median_filter deferred 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_goal sets allow_large_offset while guiding, and do_acquire permits that one correction up to PUTONSLIT_TCS_MAX_OFFSET (300″), consumed only when the offset is actually sent to the TCS (so a median_filter deferral can't waste it).

  • Ordinary guiding corrections stay capped at 60″ so a bad solution can't slew the telescope.
  • The ACQUIRE path is unchanged — it uses tcs_max_offset, which the acquire wrapper already raises to 300″ during acquisition.

Scope / safety

  • Contained to acamd's offset_goal and the do_acquire gate; allow_large_offset is atomic (set in the command thread, read/cleared in the framegrab thread).
  • Not compiled locally (no local build available).

Re: Codex review on the first commit

  • P1 (large offset never applied when offsetperiod>1) — resolved: the new allowance is consumed only on an actual send and uses a flat 300″ cap (no delta math), so a median_filter deferral no longer wastes it.
  • P2 (data race in reset_offset_params) — dormant in practice: offsetperiod=1 in the deployed config, where median_filter returns before touching the offset vectors, so there is no concurrent access. Left as-is.

🤖 Generated with Claude Code

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>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread acamd/acam_interface.cpp
Comment on lines 5524 to +5525
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();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread acamd/acam_interface.cpp
this->target.timeout_time = std::chrono::steady_clock::now()
+ std::chrono::duration<double>(this->target.timeout);
}
this->target.reset_offset_params();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
@cfremling cfremling changed the title Never drop out of guiding when applying a goal offset Keep guiding on offsets; cap deliberate offsets at 300", guiding at 60" Jun 8, 2026
@cfremling
Copy link
Copy Markdown
Collaborator Author

Thanks Codex — both findings evaluated against the real code; both are valid. Update in commit fcc0c3eb:

P1 — large put-on-slit/target offset never applied (offsetperiod>1). Confirmed: with the median filter active, the one-shot putonslit_offset allowance was consumed on the first (deferred) frame, then later frames rejected the still-pending offset against the 60″ cap → max_attempts → guiding dropped. Fixed by replacing the delta-allowance with an explicit one-shot allow_large_offset that grants a flat 300″ (PUTONSLIT_TCS_MAX_OFFSET) cap and is consumed only when the offset is actually sent, so a median_filter deferral can no longer waste it.

P2 — data race on ra_offs/dec_offs/time_offs. Confirmed real when offsetperiod>1 (reset_offset_params() in the command thread vs median_filter() in the framegrab thread). It is dormant in the deployed configuration: offsetperiod=1, where median_filter() returns at the top (if (tcs_offset_period==1) return true;) before touching the vectors, so there is no concurrent access. Left unchanged for now; can add a lock/defer if we ever run offsetperiod>1 (which currently we don't — the guide-offset median filtering isn't used).

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.
@cfremling
Copy link
Copy Markdown
Collaborator Author

Follow-up (14a141a6): the 300" deliberate-offset cap is now a config parameter rather than a hard-coded constant, mirroring how the 60" guiding cap works. Added ACQUIRE_TCS_MAX_PUTONSLIT_OFFSET=300 to acamd.cfg.in, loaded the same way as ACQUIRE_TCS_MAX_OFFSET into Target::tcs_max_putonslit_offset (setter/getter mirror the existing pair). The member defaults to 300" so configs predating the new line still behave correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant