iOS: retransmit courtesy 73 if it missed its TX slot (don't clobber it)#581
Open
patrickrb wants to merge 1 commit into
Open
iOS: retransmit courtesy 73 if it missed its TX slot (don't clobber it)#581patrickrb wants to merge 1 commit into
patrickrb wants to merge 1 commit into
Conversation
iOS QsoEngine.processRx wrapped the QSO up (returned to CQ / stopped) whenever stage == .bye73, on the assumption that the intervening TX slot always sent the queued courtesy "73". But scheduleTx can bail — an encode failure, or the slot's parity/timing not lining up — leaving the 73 queued but unsent. The next processRx then clobbered it by returning to CQ, so the answerer's courtesy 73 (and a manual Tx5 73) could be skipped in exactly those missed-slot cases, and the app prematurely started calling CQ over the top. The desktop reference port already fixed this (qso.rs, PR #570 commit 8843c39): finish_qso() is gated on a bye73_sent flag that the scheduler latches via notify_transmitted() only when the TX actually keys up. The iOS port dropped that safeguard. Mirror the desktop fix on iOS: add a bye73Sent flag + notifyTransmitted() to QsoEngine, gate finishQso() on it, and reset the flag everywhere a fresh 73 is queued (setStage(.bye73), the .rReport -> RR73 completion path, resetQso). Until the 73 is actually transmitted, processRx keeps it queued so the scheduler retransmits it next eligible slot — matching how every other stage retries. notifyTransmitted() is a no-op off .bye73, so in-progress sequences are unaffected. LiveEngine.scheduleTx now returns whether it actually started TX and the scheduler calls notifyTransmitted() on success (mirrors the desktop engine's maybe_transmit). Tests (FT8AFKit, runs on Linux via swift test): the existing answerer sequence now signals the transmit; added testBye73NotFinishedUntilActuallyTransmitted (missed-slot stays armed, no double-log, finishes only after notify) and testNotifyTransmittedIsNoOpOffBye73. Both fail pre-fix, verified by reverting only the gate. Full Kit suite 131/131 green. The LiveEngine app-target wiring is Xcode-only and was not Linux-build-verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #581 +/- ##
============================================
+ Coverage 29.36% 29.53% +0.17%
Complexity 197 197
============================================
Files 179 179
Lines 24068 24128 +60
Branches 3263 3263
============================================
+ Hits 7067 7127 +60
Misses 16780 16780
Partials 221 221
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Fixes an iOS QSO auto-sequencer edge case where a queued courtesy “73” (Bye73 stage) could be incorrectly clobbered if it missed its TX slot, by gating QSO wrap-up on an explicit “actually transmitted” signal from the scheduler.
Changes:
- Add
bye73Sentstate andnotifyTransmitted()toQsoEngine, and gate Bye73 wrap-up on that flag. - Update
LiveEngine.scheduleTxto return whether TX started, and only then notify the sequencer. - Extend/adjust unit tests to model missed-slot behavior and verify Bye73 completion semantics.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| ios/FT8AFKit/Tests/FT8EngineTests/QsoEngineTests.swift | Adds regression tests ensuring Bye73 isn’t finalized until TX is actually reported, and that notify is a no-op off Bye73. |
| ios/FT8AFKit/Sources/FT8Engine/QsoEngine.swift | Introduces bye73Sent + notifyTransmitted() and prevents premature wrap-up when Bye73 hasn’t actually been transmitted. |
| ios/FT8AF/FT8AF/Engine/LiveEngine.swift | Makes TX scheduling report “did TX start?” and uses that to trigger notifyTransmitted() only on success. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+323
to
+329
| /// Generate FT8 audio and play it through the speaker. Returns `true` if a | ||
| /// transmission was actually started, `false` if it bailed (encode failure) — | ||
| /// the caller uses this to tell the sequencer the message really went out. | ||
| @discardableResult | ||
| private func scheduleTx(message: String, freqHz: Float) -> Bool { | ||
| guard let appState else { return false } | ||
| guard let samples = FT8Encoder.generateFT8(message, baseFreqHz: freqHz) else { return false } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a cross-port state-machine divergence in the iOS QSO auto-sequencer: the answerer's courtesy 73 (and a manual Tx5 73) could be silently skipped when it missed its TX slot, and the app would prematurely return to calling CQ.
Root cause
QsoEngine.processRx(ios/FT8AFKit/Sources/FT8Engine/QsoEngine.swift) wrapped the QSO up (returned to CQ / stopped) wheneverstage == .bye73, assuming the intervening TX slot always sent the queued 73. ButLiveEngine.scheduleTxcan bail — an FT8 encode failure, or the slot's parity/timing not lining up — leaving the 73 queued but unsent. The nextprocessRxthen clobbered it by returning to CQ.The desktop reference port already fixed exactly this in PR #570 / commit
8843c396(desktop/src-tauri/src/qso.rs):finish_qso()is gated on abye73_sentflag that the scheduler latches vianotify_transmitted()only when the transmitter actually keys up (maybe_transmit→start_transmitreturnsbool). The iOS port dropped that safeguard. This is the follow-up catalogued in the PR #580 sweep notes.Fix
Faithful port of the desktop structure:
QsoEngine— add abye73Sentflag and anotifyTransmitted()method; gatefinishQso()on the flag; reset it everywhere a fresh 73 is queued (setStage(.bye73), the.rReport → RR73completion path,resetQso). Until the 73 is actually transmitted,processRxkeeps it queued so the scheduler retransmits it next eligible slot — matching how every other stage retries.notifyTransmitted()is a no-op off.bye73, so in-progress sequences are unaffected.LiveEngine.scheduleTx— now returns whether it actually started TX; the scheduler callsqso.notifyTransmitted()on success (mirrors the desktop engine'smaybe_transmit). Happy path byte-identical.Testing
ios/FT8AFKitbuilt and tested on Linux viaswift test(the Kit imports only Foundation + CFT8): 131/131 green.testAnswererFullSequenceLogsQsoto signal the transmit (mirrors the desktop test change).testBye73NotFinishedUntilActuallyTransmitted(missed-slot stays armed, no double-log, finishes only afternotifyTransmitted) andtestNotifyTransmittedIsNoOpOffBye73. Both fail pre-fix, verified by reverting only theprocessRxgate.Risk
Low. Pure state-machine change scoped to the courtesy-73 wrap-up; every other stage and the happy-path TX flow are unchanged. The
LiveEngineapp-target wiring is a minimal@discardableResultsignature change (one call site) — it is Xcode-only and was not Linux-build-verified (the FT8AF app target imports AVFoundation and cannot compile on Linux); the QSO-engine logic + its tests were fully verified.🤖 Generated with Claude Code