Skip to content

Feed deep/late-pass decodes to the QSO auto-sequencer#406

Open
patrickrb wants to merge 2 commits into
devfrom
fix/sequencer-deep-decodes
Open

Feed deep/late-pass decodes to the QSO auto-sequencer#406
patrickrb wants to merge 2 commits into
devfrom
fix/sequencer-deep-decodes

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

Problem

Field report from the 2026-07-03 POTA activation (K1AF): QSOs stalled repeating the previous message while the partner's reply was visible on screen, and some stations calling K1AF were never answered.

The pulled debug.log pins it down: the auto-sequencer only ever saw the fast decode pass. MainViewModel.afterDecode gated parseMessageToFunction on !isDeep, so every deep, subtraction-loop, and late/cross-slot pass (routine since the decode-gap work, #367#370) reached the UI and QSO panel but never the sequencer.

Evidence from the log (local = UTC−5):

  • N2JFD: K1AF N2JFD R-18 received 20:25:15/:45/20:26:15; sequencer logged newOrder=-1 (no-reply 1, 2) for two cycles and only advanced on the third.
  • K3VAT: identical pattern for R-23, with the smoking gun 15:35:28 DECODE: kept=24 ... replyToMe=false slot=1 — the fast pass contained no message addressed to us while the QSO panel displayed the R-23 (deep-pass decode).
  • 60 no-reply events at orders 2–3 across the day.

Fix

Deep passes now drive the sequencer through an evidence-only parse variant (parseMessageToFunction(list, true)):

  • May do: advance the QSO on the partner's reply, trigger the RR73→73 reply, complete on a received 73 (or the partner moving on at RR73), enqueue/answer callers.
  • May not do: no-reply counting, give-up, or silence-based completions (order 5 + newOrder −1, RR73 no-reply caps) — those absence-of-evidence calls stay fast-pass-only, so a deep pass that finds nothing new is not a second no-reply. The completion decision is extracted into shouldCompleteQso(...) for testability.

Deep results arriving before TX keys (~:29.0–:29.8 vs key-up ~:29.9) are parsed immediately and can upgrade the very next transmission (send RR73 instead of repeating the report in the same cycle). Results arriving mid-TX are stashed in the new PendingSequencerDecodes (age-capped at 60 s) and replayed at the first delivery after key-up.

Evidence parses log as QSO: cycle(deep) ... so the effect is auditable from a pulled debug.log on the next activation.

Tests

  • EvidenceOnlyParseTest (Robolectric, full sequencer instance): deep R-report advances 2→4 and resets the no-reply counter; deep RR73 → 73; deep 73 completes to CQ; deep caller lands in the queue; deep pass with nothing new does not increment no-reply (with the fast-pass contrast case) and does not complete an order-5 QSO; own-slot decodes are rejected; Hound mode ignores deep passes.
  • FT8TransmitSignalTest: shouldCompleteQso arms — positive evidence completes on any pass, silence-based arms fast-pass-only, thresholds honoured.
  • PendingSequencerDecodesTest: stash/drain/clear, ordering, age eviction (boundary included).
  • Full testDebugUnitTest suite green.

Field verification

Next activation: watch for QSOs advancing on the first received R-report and QSO: cycle(deep) lines in debug.log. (Note: two of this outing's stalls were compounded by a separate USB TX-drop bug — fix in a follow-up PR.)

🤖 Generated with Claude Code

…dence

Field report (POTA activation 2026-07-03): QSOs stalled repeating the
previous message while the partner's reply was visible on screen, and
some stations calling K1AF were never answered. debug.log shows the
cause: MainViewModel only handed fast-pass decodes to
parseMessageToFunction, so any reply found only by a deep, subtraction
or late/cross-slot pass (routine since the decode-gap work, PRs
#367-#370) never reached the sequencer. Both stalled QSOs in the report
show newOrder=-1 for two cycles while the partner's R-report was
deep-pass-only (one cycle even logged replyToMe=false while the R-report
was displayed in the QSO panel).

Fix: deep passes now drive the sequencer via an evidence-only parse
variant - they can advance the QSO, trigger the RR73->73 reply,
complete on a received 73, and enqueue callers, but never make
absence-of-evidence decisions (no-reply counting, give-up, "partner
went silent" completions), which stay fast-pass-only so a deep pass
finding nothing new is not double-counted as a second no-reply.

Deep results that land while TX is playing are stashed in
PendingSequencerDecodes (age-capped) and replayed at the first delivery
after key-up, so evidence arriving mid-transmission still advances the
next cycle instead of being dropped. Results landing before TX keys
(~0.9s window) upgrade the very next transmission.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 20.80%. Comparing base (68398ec) to head (eb7293f).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##                dev     #406   +/-   ##
=========================================
  Coverage     20.80%   20.80%           
  Complexity      133      133           
=========================================
  Files           148      148           
  Lines         19297    19297           
  Branches       2876     2876           
=========================================
  Hits           4015     4015           
  Misses        15115    15115           
  Partials        167      167           
Flag Coverage Δ
android 12.19% <ø> (ø)
native 9.93% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the FT8 QSO auto-sequencer so that deep/subtraction/late-pass decodes can advance the QSO state machine as positive evidence, fixing stalls where replies were visible in the UI but ignored by sequencing. It also adds a mid-TX “stash and replay after key-up” mechanism and extracts QSO-completion logic for better testability.

Changes:

  • Route deep/late-pass decodes into an evidence-only sequencer parse path, and replay mid-TX deep results after key-up.
  • Add PendingSequencerDecodes (age-capped) to stash deep decodes while transmitting.
  • Extract shouldCompleteQso(...) and add targeted unit/Robolectric coverage for the new sequencing behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ft8af/app/src/main/java/com/k1af/ft8af/MainViewModel.java Feeds deep/late-pass decodes to the sequencer (evidence-only), stashes mid-TX deep decodes, and replays them after TX ends.
ft8af/app/src/main/java/com/k1af/ft8af/PendingSequencerDecodes.java New synchronized stash/drain helper with MAX_AGE eviction for mid-TX deep-pass decodes.
ft8af/app/src/main/java/com/k1af/ft8af/ft8transmit/FT8TransmitSignal.java Adds evidence-only parse variant, extracts shouldCompleteQso, and exposes package-visible hooks for testing.
ft8af/app/src/test/java/com/k1af/ft8af/PendingSequencerDecodesTest.java Unit coverage for stash/drain ordering and age-based eviction.
ft8af/app/src/test/java/com/k1af/ft8af/ft8transmit/FT8TransmitSignalTest.java Adds unit tests for shouldCompleteQso behavior across evidence-only vs fast-pass paths.
ft8af/app/src/test/java/com/k1af/ft8af/ft8transmit/EvidenceOnlyParseTest.java Robolectric coverage ensuring deep-pass evidence advances sequencing without triggering absence-of-evidence behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ft8af/app/src/main/java/com/k1af/ft8af/MainViewModel.java Outdated
Comment thread ft8af/app/src/main/java/com/k1af/ft8af/MainViewModel.java
Copilot review on PR #406: Ft8Message.utcTime is in the NTP/GPS-corrected
time base (UtcTimer.getSystemTime() = delay + currentTimeMillis), while
the stash/drain calls passed the raw system clock. On a phone with a
badly-set clock (the reason the app has clock sync at all) the mixed
bases would skew age eviction by the correction offset - keeping stale
evidence or evicting fresh evidence. Pass the corrected time and
document the expected base on PendingSequencerDecodes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
patrickrb added a commit that referenced this pull request Jul 4, 2026
Copilot review findings, both valid:
- writeElapsedMs was measured with System.currentTimeMillis(); a wall
  clock step (NTP set, user change) could fake a fast failure and
  wrongly allow the restart-from-zero UsbRequest fallback after audio
  already went to air. Use SystemClock.elapsedRealtime().
- The tx_audio_dropped toast hard-coded "USB audio device dropped" as
  the cause, but writeAudio can fail for other reasons (libusb setup
  errors, UsbRequest path failures). Genericized to "USB audio error"
  in all 16 locales; the precise cause stays in debug.log via
  describeLibusbWriteError.

Also move the shouldWarnTxDropped tests into their own TxDropWarningTest
file so this PR no longer appends to the same FT8TransmitSignalTest
region as PR #406 (eliminates the guaranteed merge conflict between the
two open PRs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants