Conversation
The park picker bottom sheet was emitted as a trailing sibling of the fillMaxSize Activate content inside PotaScreen's Column. In a Column the greedy fillMaxSize child consumes all the height, so the sheet (itself a fillMaxSize scrim + sheet) laid out into zero leftover height: visible flipped true on tap but nothing appeared on screen. Host the Activate content and ParkPickerSheet together in a Box(fillMaxSize) so the sheet overlays the content, mirroring how FrequencyPickerSheet is hosted as a sibling overlay in FT8AFApp. Add BottomSheetOverlayHostTest, a Compose/Robolectric regression test pinning the geometry: an FT8AFBottomSheet placed after a fillMaxSize sibling in a Column is not displayed, while the same wrapped in a Box is. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#317) * Fix POTA Nearby parks showing far-away regions (bad upstream centers) POTA's /locations API ships wrong center coordinates for a chunk of foreign regions: ZA-FS, RU-VL, RO-OT (and others) all report centers sitting in Kansas. nearbyParks picked only the 3 nearest region centers and trusted them, so a US user got those mislocated regions and their parks rendered their true ~5000-mile distance at the top of Nearby. Broaden the candidate net from 3 to 12 nearest region codes so the user's real region(s) are always included despite the bad rows, fetch those park lists concurrently, then rank purely by each park's own (correct) coordinates. Add a NEARBY_MAX_DISTANCE_KM cap (1000 km) so a mislocated region's genuinely-distant parks can never surface as nearby. Tests: a regression case using the real ZA-FS/RU-VL/RO-OT-in-Kansas data showing the wider candidate count rescues US-KS/US-MO, plus max-distance cap coverage on sortParksByDistance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address review: pin nearby-candidate test to production value (12) The wide candidate-count assertion used 5; use 12 to mirror the production NEARBY_LOCATION_CANDIDATES constant so the test documents the intended value. With 5 test rows the result is unchanged (all returned). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Found while auditing the on-device debug.log: nothing functionally broken, but two log defects made a real failure mode hard to spot. 1. Dropped TX is invisible. When the libusb native write fails (seen ~10x in a session as rc=5 / rc=-1), the UsbRequest fallback also fails for the same stale-handle reason and writeAudio returns false in ~2ms. The rig is already keyed, so it transmits dead air for the whole 15s slot. The log only said "writeAudio returned FAILED", which reads as benign. The QSO sequencer is RX-driven and self-corrects next cycle, so this isn't fatal -- but the operator can't tell from the rig, so the log now spells out "TX DROPPED, no audio sent this cycle (rig keyed but silent)" and names the libusb error code instead of a bare rc. 2. sinceLastReinit printed the epoch. lastReinitMs starts at 0, so a capture session that stopped before any reinit logged now-0, i.e. the wall-clock epoch (~1.78e12 ms, "56,000 years"). Now shows "n/a (no reinit yet)". No behavior change beyond logging. Decision logic extracted into pure, package-visible helpers (formatSinceReinit, describeLibusbWriteError, buildWriteAudioResultLog) with unit tests. Claude-Session: https://claude.ai/code/session_01CDu3dtuN89JsRqxFsq1ow9 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…downsample (#319) * Improve RX decode sensitivity: deep decode default, level meter, FIR downsample A user reported the app decoding far fewer signals than WSJT-X from the same radio (TX500). Three independent sensitivity fixes: 1. Deep decode on by default. The subtract-and-redecode loop (the thing that pulls weak signals out from under strong ones, like WSJT-X's default depth) was gated behind a setting that defaulted off, so out of the box the app did a single decode pass. A persisted "deepMode" config still overrides; only installs that never touched the setting pick up the new default. Also replace the flat 7s deep-decode loop timeout with ModeProfile #deepDecodeBudgetMillis (0.75x the slot). The old constant was ~2x FT2's entire 3.75s slot (so it never bounded fast modes) yet left FT8 well short of its 15s cycle, cutting weak-signal passes WSJT-X would still run. 2. RX input-level meter. The radio's USB audio is often set too quiet (weak signals fall below threshold) or hot enough to clip (intermod kills decodes), and the app gave no feedback. Add AudioInputLevel (pure peak/RMS -> dBFS + coarse status) + AudioLevelDisplay (label/colour), metered off the same 160ms waterfall buffer and shown on the spectrum screen. 3. Anti-aliasing FIR downsample. The 48k->12k USB capit path used a 4-tap box filter (~10 dB stopband), folding out-of-band hiss into the FT8 passband and raising the noise floor. Replace with a header-only windowed-sinc polyphase decimator (fir_decimator.h, ~80 dB rejection at the alias frequencies). Host test demonstrates the 9kHz alias tone drops from 0.32 (box) to 0.0001 (FIR). Tests: ModeProfileTest (timeout scaling), AudioInputLevelTest + AudioLevelDisplayTest (level math/mapping), test_fir_decimator.cpp wired into run_host_tests. JVM tests + native host tests pass; debug build installs on the Pixel 8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address Copilot review on FIR decimator (#319) Two review comments on the anti-aliasing downsample: 1. Guard the decimation-ratio division. nativeStart computed inputSampleRate / targetSampleRate directly, which divides by zero (UB) if the device streams a 0 target rate and silently produces the wrong output rate when the input isn't an exact multiple. Extracted the computation into a pure, tested helper decimationRatioFor() that clamps to >= 1 (pass-through on a bad pair), and the caller now logs the two degenerate cases instead of feeding the decoder mis-rated audio. 2. Fix misleading "polyphase" wording. The implementation is a straight FIR convolution followed by decimation, not a polyphase structure. Renamed the tapsPerPhase parameter to tapsPerRatio and reworded the comments to describe what the code actually does. Adds host-test coverage for decimationRatioFor (exact multiple, zero/ negative target, input-below-target, non-integer multiple, equal rates). Host FIR suite passes; assembleDebug builds all ABIs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDu3dtuN89JsRqxFsq1ow9 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ocation Two review comments on the merged #319 code: 1. FT8SignalListener deep-decode budget used `timeSec` (total elapsed since the whole decode began), so on a slow device the initial fast + first deep pass could spend the budget and abort the subtract-and-redecode loop before it ran once. Budget the loop by its own elapsed time, measured from the loop start. Extracted DeepDecodeBudget.loopExhausted so the decision is unit-tested without loading the native decoder (DeepDecodeBudgetTest). 2. usb_audio_capture onTransferComplete heap-allocated a fresh std::vector<float> for the decimated output on every iso callback (~8ms). Reuse a persistent `outScratch` member (clear() keeps capacity), reserved once in nativeStart, so the hot capture path allocates nothing per transfer. JVM tests + native host tests pass; debug build installs on the Pixel 8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t run numbers (#322) The per-project CI split replaced build-release.yml with android.yml, a new workflow file. GITHUB_RUN_NUMBER is per-workflow, so the counter reset from ~680 (under the old workflow, versionCode ~780 on the Play internal track) back to a low number. With the old "+ 100" offset the staging->internal publish produced versionCode 167, below the 780 already on the track, so Google Play refused the rollout: "You cannot rollout this release because it does not allow any existing users to upgrade to the newly added APKs." Bump the offset to + 1000 so the next staging push clears the historical max with margin. The publish step is already continue-on-error, so the run was green and only the Play upload was skipped; this restores the upload. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## staging #323 +/- ##
==========================================
Coverage 11.51% 11.51%
Complexity 113 113
==========================================
Files 88 88
Lines 12466 12466
Branches 2230 2230
==========================================
Hits 1435 1435
Misses 10900 10900
Partials 131 131
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Promotes the latest dev branch to staging, producing an android-dev.<run#> prerelease and attempting a Play internal upload, while pulling in the most recent functional changes (decoder sensitivity, POTA picker/nearby logic, USB logging, and USB capture DSP).
Changes:
- Fix Play internal-track rollout rejection by increasing the CI-derived
versionCodeoffset (GITHUB_RUN_NUMBER + 1000). - Improve decode sensitivity/observability: deep-decode defaults + slot-scaled loop budget, RX input-level meter, and clearer USB TX/capture logging.
- Fix POTA park picker UX and nearby-park correctness/perf (overlay hosting, broader candidate set, concurrent fetch, distance cap).
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| ft8af/app/build.gradle | Bumps CI versionCode offset to avoid Play “no upgrade path” rejection after workflow run-number reset. |
| ft8af/app/src/main/java/com/k1af/ft8af/FT8Common.java | Removes the flat deep-decode timeout constant in favor of slot-scaled budgeting. |
| ft8af/app/src/main/java/com/k1af/ft8af/GeneralVariables.java | Enables deep decode by default (with config override) and documents the behavior. |
| ft8af/app/src/main/java/com/k1af/ft8af/ModeProfile.java | Adds deepDecodeBudgetMillis() scaling deep-decode loop time with mode slot length. |
| ft8af/app/src/main/java/com/k1af/ft8af/ft8listener/DeepDecodeBudget.java | Extracts deep-decode loop budget decision into a unit-testable helper. |
| ft8af/app/src/main/java/com/k1af/ft8af/ft8listener/FT8SignalListener.java | Budgets subtract-and-redecode loop by its own elapsed time using the new helper/budget. |
| ft8af/app/src/main/java/com/k1af/ft8af/ft8transmit/FT8TransmitSignal.java | Makes USB-direct TX failures explicit (“TX DROPPED”) via a testable log builder. |
| ft8af/app/src/main/java/com/k1af/ft8af/spectrum/AudioInputLevel.java | Adds pure-logic peak/RMS→dBFS metering + status classification for RX level display. |
| ft8af/app/src/main/java/com/k1af/ft8af/spectrum/AudioLevelDisplay.java | Maps meter reading to label + ARGB color (unit-testable, no Android deps). |
| ft8af/app/src/main/java/com/k1af/ft8af/ui/SpectrumFragment.java | Updates spectrum screen to display the RX input-level meter each buffer. |
| ft8af/app/src/main/java/com/k1af/ft8af/wave/MicRecorder.java | Fixes “sinceLastReinit” logging (shows sentinel instead of epoch) via helper. |
| ft8af/app/src/main/java/com/k1af/ft8af/wave/UsbAudioDevice.java | Improves libusb write failure logs by naming libusb error codes (test-covered). |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/pota/PotaParkRepository.kt | Fixes nearby parks by broadening region candidates, fetching concurrently, and adding a max-distance cap. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/pota/PotaScreen.kt | Hosts ParkPickerSheet in a Box overlay so the picker is visible when toggled. |
| ft8af/app/src/main/res/layout/fragment_spectrum.xml | Adds a TextView to render the RX input-level meter on the spectrum screen. |
| ft8af/app/src/main/cpp/fir_decimator.h | Introduces a header-only FIR decimator to replace box-filter downsampling. |
| ft8af/app/src/main/cpp/usb_audio_capture.cpp | Switches USB capture downsample to FIR decimation; reuses buffers to avoid per-transfer allocations. |
| ft8af/app/src/main/cpp/ft8af_glue/test_fir_decimator.cpp | Adds host tests for FIR decimator ratio, unity gain, and alias rejection. |
| ft8af/app/src/main/cpp/ft8af_glue/run_host_tests.sh | Builds/runs the new FIR decimator host test. |
| ft8af/app/src/main/cpp/ft8af_glue/run_host_tests.ps1 | Builds/runs the new FIR decimator host test (and propagates its exit code). |
| ft8af/app/src/test/java/com/k1af/ft8af/ModeProfileTest.java | Adds unit coverage for slot-scaled deep-decode budget and constraints. |
| ft8af/app/src/test/java/com/k1af/ft8af/ft8listener/DeepDecodeBudgetTest.java | Adds unit coverage for the deep-decode subtraction-loop budgeting helper. |
| ft8af/app/src/test/java/com/k1af/ft8af/ft8transmit/TxResultLogTest.java | Adds unit coverage for “TX DROPPED” vs OK logging. |
| ft8af/app/src/test/java/com/k1af/ft8af/spectrum/AudioInputLevelTest.java | Adds unit coverage for RX meter math and status thresholds. |
| ft8af/app/src/test/java/com/k1af/ft8af/spectrum/AudioLevelDisplayTest.java | Adds unit coverage for RX meter label/color mapping. |
| ft8af/app/src/test/java/com/k1af/ft8af/wave/MicRecorderSinceReinitTest.java | Adds unit coverage for “since last reinit” log formatting sentinel vs duration. |
| ft8af/app/src/test/java/com/k1af/ft8af/wave/UsbAudioWriteErrorTest.java | Adds unit coverage for libusb error code labeling. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/pota/PotaParkPickerLogicTest.kt | Adds regression coverage for widened region candidates + max-distance capping. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/ui/components/BottomSheetOverlayHostTest.kt | Adds Compose/Robolectric regression test for bottom-sheet overlay hosting bug. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The #316 fix wrapped PotaScreen's content in a Box(fillMaxSize) overlay host but left the if/else and ParkPickerSheet blocks at their original indentation, so they sat at the same column as `Box(...) {` instead of inside its lambda. ktlint flags this (Copilot review on #323) and it reads as if the blocks aren't part of the Box. Indent the whole Box body one level. Whitespace-only; no behavior change (BottomSheetOverlayHostTest still covers the overlay-hosting geometry). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/pota/PotaScreen.kt
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.
Promote latest
devtostaging(cuts anandroid-dev.<run#>prerelease + Play internal upload).Included since the last promotion (#321)
GITHUB_RUN_NUMBER(780 → 167); the+ 1000offset now clears the historical max, so this promotion's internal upload should complete instead of being rejected with "does not allow any existing users to upgrade."🤖 Generated with Claude Code