Auto-sync the clock by default (one-slot acquisition), drop callers who gave up, swap the TX over on a late reply - #758
Conversation
…TX over on a late reply Three field complaints from the same activation, all in the QSO timing loop. 1. "Auto-sync clock from decodes" still left me applying the suggestion by hand. ClockSelfSync stopped acting once |median DT| <= 0.30 s and needed >= 4 decodes in a slot, so on a quiet band it never fired and on a busy one it parked the clock ~0.25 s off -- exactly the residual the operator kept tapping "Apply suggested correction" for. Deadband is now 0.15 s (two DT measurement steps), a slot with 1-3 surviving decodes counts but needs three agreeing slots instead of two, and the Suggestion card stops offering the manual apply while auto-sync (or GPS discipline) owns the clock -- it shows the last auto-correction instead. 2. Queued callers who had given up were still called back after the QSO. Nothing ever removed a station from the caller queue, and the queue was only fed on cycles where the partner went silent. Every pass now records who is calling me (other than the partner) and refreshes their last-heard slot; a caller not heard in the most recent receive slot (one full cycle) is pruned before the queue is worked, so we go back to CQ -- or answer whoever is calling right now -- instead of calling someone who left. Strongest-first picks among live callers only. A caller the operator taps is never pruned. 3. After sending a report we sometimes re-sent it instead of RR73 when the partner's R-report decoded just after key-up. PR #704's mid-cycle TX swap handles precisely this, but it could never fire: both decode paths stashed anything that landed while transmitting and the drain refused to run while transmitting, so the reply was replayed a whole cycle later. Decodes that land after key-up are now parsed immediately (evidence-only, as the replay already was), which advances the order at once and lets the swap replace the over inside the audio slack. The TX worker also picks up a swap requested during the PTT settle before the first sample plays, instead of playing the old over in full and replaying the new one too late to fit. The stash (PendingSequencerDecodes) is gone; FastPassDisposition's second outcome is EVIDENCE_ONLY. Tests: ClockSelfSync sparse/deadband cases, CallerQueueOrdering pruning, CallerQueueLivenessTest (sequencer-level queue liveness), FastPassDisposition, TimeCorrection showSuggestionApply. Full unit suite: 3396 tests, 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## dev #758 +/- ##
============================================
- Coverage 41.69% 41.65% -0.05%
- Complexity 284 290 +6
============================================
Files 278 278
Lines 32960 33166 +206
Branches 3940 3994 +54
============================================
+ Hits 13742 13814 +72
- Misses 18868 18996 +128
- Partials 350 356 +6
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
The self-syncing clock is now the default (autoSyncClockFromDecodes=true; hydration only acts on rows that exist, so fresh installs and pre-feature backups come up with it on). To earn that, it has to feel instant: - Acquire fast, track gently. A well-populated slot (>= 4 survivors) whose median DT is more than 0.5 s out acts on its own -- no second slot to confirm -- and removes the whole error in one step (ACQUIRE_GAIN 1.0). Inside 0.5 s the loop tracks as before: two agreeing slots, half the residual per step. Sparse slots still need three agreeing slots, but take the full step when plainly off, so a quiet band 1 s out is fixed in three slots instead of nine. - The DT pill in the slot bar shows an AUTO badge (GPS when GPS discipline owns the clock), so the operator can see the offset is being handled. - Settings copy says it is on by default and what to expect. Convergence from a cold start, busy band: 1.5 s -> in deadband after ONE slot (was 8). Quiet band, one station: 1.0 s -> three slots (was never). Tests: ClockSelfSyncTest rewritten around the acquire/track regimes (41 tests), ClockSyncTest badge cases, hydration default flipped. Full unit suite: 3410 tests, 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Second commit: auto-sync is now on by default and acquires in one slot. A well-populated slot (≥4 decodes) whose median DT is >0.5 s out is corrected in full immediately; inside 0.5 s it tracks with two-slot confirmation and half-steps. Sparse slots (1–3 decodes) need three agreeing slots but take the full step when plainly off. The DT pill in the slot bar shows an AUTO badge (or GPS) so you can see it's being handled. Cold start on a busy band: 1.5 s off → in deadband after one slot. Full suite: 3410 tests, 0 failures. |
There was a problem hiding this comment.
🔵 Needs a closer look
It changes timing-sensitive TX/decoder interactions and default clock-discipline behavior in core operating paths, which warrants final human review and on-air validation.
Pull request overview
This PR tightens the QSO timing loop by (1) making decode-based clock self-sync effective on sparse bands and enabled by default, (2) keeping the caller queue “live” by pruning stations that stopped calling, and (3) ensuring late decodes are acted on immediately as evidence so the sequencer can swap the on-air message within the audio slack.
Changes:
- Enable and surface decode-based auto clock sync by default, with a tighter deadband and acquisition/track behavior for sparse vs busy slots.
- Add caller-queue liveness tracking (
lastHeardUtc) plus pruning of callers who gave up, and refresh the queue on every sequencer pass. - Remove decode stashing and parse late decodes immediately as evidence-only to allow mid-cycle TX restarts/swaps.
File summaries
| File | Description |
|---|---|
| ft8af/app/src/main/java/com/k1af/ft8af/timer/ClockSelfSync.java | Implements acquisition vs tracking, tighter deadband, and sparse-slot confirmation rules. |
| ft8af/app/src/test/java/com/k1af/ft8af/timer/ClockSelfSyncTest.java | Expands estimator unit tests for sparse slots, acquisition, tracking, and convergence. |
| ft8af/app/src/main/java/com/k1af/ft8af/GeneralVariables.java | Turns auto-sync on by default and adds runtime status fields for UI (“last step”, “last applied”). |
| ft8af/app/src/test/java/com/k1af/ft8af/database/DatabaseOprConfigHydrationTest.java | Updates hydration expectations to reflect the new default-on auto-sync behavior. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/settings/TimeCorrection.kt | Adds showSuggestionApply() gate so manual apply is hidden when an automatic source owns the clock. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/ui/settings/TimeCorrectionTest.kt | Tests the manual-apply visibility logic for manual vs auto-sync vs GPS discipline. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/settings/TimeSyncSettings.kt | Updates the Suggestion card to show auto-sync status and last auto-correction instead of a manual action. |
| ft8af/app/src/main/res/values/strings_compose.xml | Adds strings for auto-sync status messaging and clock-sync accessibility text/badges. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/components/ClockSync.kt | Adds AUTO/GPS badge selection + accessibility string composition for the DT pill. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/ui/components/ClockSyncTest.kt | Tests AUTO/GPS badge selection behavior. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/components/SlotTimerBar.kt | Wires auto-sync/GPS flags into the DT indicator so the badge reflects current clock ownership. |
| ft8af/app/src/main/java/com/k1af/ft8af/ft8transmit/QueuedCaller.java | Adds lastHeardUtc to track caller freshness for pruning. |
| ft8af/app/src/main/java/com/k1af/ft8af/ft8transmit/CallerQueueOrdering.java | Adds “given up” detection and in-place pruning helpers. |
| ft8af/app/src/test/java/com/k1af/ft8af/ft8transmit/CallerQueueOrderingTest.java | Tests given-up detection, pruning, FT4/FT8 scaling, and strongest-first behavior post-prune. |
| ft8af/app/src/main/java/com/k1af/ft8af/ft8transmit/FT8TransmitSignal.java | Refreshes caller queue every parse pass, prunes given-up callers, and supports pre-audio TX message updates on restart. |
| ft8af/app/src/test/java/com/k1af/ft8af/ft8transmit/CallerQueueLivenessTest.java | Adds sequencer-level Robolectric coverage for queue refresh + pruning across QSO completion/CQ. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/ui/components/CallerQueueDisplayTest.kt | Updates QueuedCaller construction to include lastHeardUtc. |
| ft8af/app/src/main/java/com/k1af/ft8af/ft8listener/FastPassDisposition.java | Changes fast-pass handling from PARSE/STASH to PARSE/EVIDENCE_ONLY. |
| ft8af/app/src/test/java/com/k1af/ft8af/ft8listener/FastPassDispositionTest.java | Updates tests to reflect “never dropped, never deferred” fast-pass behavior. |
| ft8af/app/src/main/java/com/k1af/ft8af/MainViewModel.java | Removes pending-decode stashing and parses late decodes immediately as evidence; posts auto-sync UI status. |
| ft8af/app/src/main/java/com/k1af/ft8af/PendingSequencerDecodes.java | Deleted: no longer needed after switching late decodes to immediate evidence-only parsing. |
| ft8af/app/src/test/java/com/k1af/ft8af/PendingSequencerDecodesTest.java | Deleted along with the stashing mechanism. |
Review details
- Files reviewed: 22/22 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
enqueueCaller() posted a fresh queue snapshot every time an already-queued station was seen again, and refreshCallerQueue() now feeds it every pass, so a deep pass re-delivering the same decode republished an identical list. QueuedCaller.refresh() updates SNR / last-heard (forward only) and reports whether anything visible changed; the snapshot is posted only then. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three field complaints from the same activation, all in the QSO timing loop.
1. Auto-sync still needed the manual "Apply suggested correction"
ClockSelfSyncstopped acting once |median DT| ≤ 0.30 s and required ≥ 4 decodes per slot — so on a quiet POTA band it never fired, and on a busy one it parked the clock ~0.25 s off, which is exactly the residual the operator kept applying by hand.GeneralVariables.mutableSelfSyncApplied).2. Queued callers who gave up were still called back
Nothing ever pruned the caller queue, and it was only fed on cycles where the partner went silent (the scan lived behind the no-reply branch).
QueuedCaller.lastHeardUtctracks the slot of their latest call; every pass (fast and evidence-only) refreshes it viarefreshCallerQueue().CallerQueueOrdering.pruneGivenUp()drops anyone not heard in the most recent receive slot (one full cycle) before the queue is worked — on QSO completion, on give-up, and while idling on CQ. Nobody fresh → back to CQ, or answer whoever is calling right now. Strongest-first picks among live callers only.dequeueSpecificCaller) are never pruned.3. Re-sending the report instead of RR73
When the partner's R-report decoded just after key-up, #704's mid-cycle TX swap was supposed to fix the over — but it could never fire: both decode paths in
MainViewModel.afterDecodestashed anything that landed while transmitting, and the drain refused to run while transmitting, so the reply was replayed a full cycle later (our own-slot fast pass ~14 s after key-up).shouldRestartForNewOrdercan swap the over inside the audio slack. Past the slack the advanced order keys the next cycle — no stash needed.playFT8Signal()resets the cancel flags, plays the old over in full, and replays the new one ~13 s in where the clip math strips it.PendingSequencerDecodesremoved;FastPassDisposition.Action.STASH→EVIDENCE_ONLY.Tests
New/updated:
ClockSelfSyncTest(sparse slots, tighter deadband, convergence),CallerQueueOrderingTest(pruning),CallerQueueLivenessTest(sequencer-level: queue fed on smooth QSOs, stale callers pruned, completion goes to CQ / works the live caller / strongest-first ignores a loud caller who left),FastPassDispositionTest,TimeCorrectionTest.showSuggestionApply.Full unit suite: 3396 tests, 0 failures.
On-air check (please)
debug.log: look forQSO: TX restart …/TX message updated before audio startafter afast decode landed after key-upline, anddropped queued caller(s) no longer calling.🤖 Generated with Claude Code