Repository navigation
fix(rade): renormalize V1 phase accumulators. Principle XI. - #6399
Conversation
There was a problem hiding this comment.
Issue fit
Yes, on the mechanism. #6398 names two drift sites; the diff addresses both and nothing else. I traced the mechanism in the head checkout rather than taking the body for it, and it holds:
third_party/radae/src/rade_bpf.c:91-107mixes down byphaseand back up byrade_cconj(phase), so the output carries|phase|².phaseonly ever advances byphase = rade_cmul(phase, phase_inc)(:91) and before this PR was stored back unmodified. The attenuation claim is structurally right.- The exposure window is the whole RADE-enabled session, not uninterrupted sync.
rade_rx_reset()(third_party/radae/src/rade_rx.c:100) is the only thing that would re-seedrx_phaseand callrade_bpf_reset()— and it has no callers anywhere inthird_party/radae/src/orsrc/.RADEEngineholds oneradeobject fromstart()tostop()(src/core/RADEEngine.cpp:89,:164), so a sync loss, band change or re-acquire does not bound the drift. That makes the "day of continuous receive" framing conservative rather than optimistic. - The issue's "publishes confident wrong numbers" claim is also real, not rhetoric:
rade_snrdB_3k_est→RADEEngine::snrChanged(src/core/RADEEngine.cpp:516) is connected tom_freedvClientatsrc/gui/MainWindow_DigitalModes.cpp:1088.
The one thing unaddressed is the one the body already declines to claim: the symptom has not been reproduced through the app. That is disclosed, and Principle XI is cited correctly — the body names the squash-merge re-run and maintainer reproduction as the gate rather than grading itself.
Scope
| File | What it changes | Claimed by title/body | Verdict |
|---|---|---|---|
third_party/radae/src/rade_bpf.c:111-112 |
Renormalize the mixer phase at the end of rade_bpf_process() |
Yes | In scope |
third_party/radae/src/rade_rx.c:236-237 |
Enable the already-written, commented-out rx_phase renormalization |
Yes | In scope |
Everything in the diff is explained by the issue. No new public surface, no protocol verb, no settings or capability key, no default changed, no guard or symptom-naming comment removed (the /* Normalize phase to prevent drift */ comment at rade_rx.c:235 is retained, not replaced). No CHANGELOG.md entry — correct. One signed commit, no cherry-picked build workaround riding along.
Two scope claims I checked and can confirm:
- "The BPF is unconditionally enabled for V1." True for RX:
rade_api_nopy.c:84passes a literal1torade_rx_init. The TX side passesbpf_en = 0(rade_api_nopy.c:76), sorade_tx.c:165/:228never reachrade_bpf_process— the change is RX-only in effect, which is narrower than the body implies but not broader. - "Two sites." I went looking for a third. Every other phase-like vector in the vendored tree is recomputed from scratch rather than accumulated:
rade_acq.c:77,:184,:186,:267all buildw_vecasrade_cexp(w * n)per index, andrade_ofdm.c:407derivesrx_phasefromrade_cangle()of a measured pilot.rade_bpf.c:91is the onlyx = rade_cmul(x, inc)accumulator in the tree. No sibling left unfixed.
Blockers
1. third_party/radae/src/rade_bpf.c:111-112 — no registered test, and the sibling site is proof that a code-only fix does not stick.
float phase_mag = rade_cabs(phase);
phase = rade_cscale(phase, 1.0/phase_mag);docs/agents/tests-ci.md:58 is explicit about the layer: "Wire encoding, parser bounds, model tables, scheduling, DSP, capability/safety policy | a socket-free CTest in tests/". This is DSP with a fully deterministic seam and no Qt, no socket, no model — and the author has already written the harness that exercises it, it is just not registered.
The argument for making this merge-gating rather than a nit is in the diff itself: the rade_rx.c half of this PR is re-enabling a renormalization that was already written and then commented out. That is precisely the regression a registered test prevents, and nothing stops it happening again to either site. Today tests/tests.cmake registers 708 tests and zero of them reference radae or RADE_DIR, and the tree ships third_party/radae/src/rade_bpf_test.c which no CMake target compiles (CMakeLists.txt:522-545 lists rade_bpf.c but no test). So this would be the first RADE test — worth the maintainer knowing, since it is a new precedent rather than an extension.
It does not need the 23-hour harness. A mutation-killing test is microseconds: seed the accumulator off the unit circle, run one sample, assert it came back:
rade_bpf bpf;
rade_bpf_init(&bpf, 101, 8000.0f, 3000.0f, 1500.0f, 960);
bpf.phase = (RADE_COMP){0.5f, 0.0f}; /* stand in for hours of drift */
RADE_COMP x = rade_cone(), y;
rade_bpf_process(&bpf, &y, &x, 1);
assert(fabsf(rade_cabs(bpf.phase) - 1.0f) < 1e-5f);Delete the two added lines and rade_cabs(bpf.phase) stays at ~0.5, so the test fails against the unfixed code — which the current evidence cannot demonstrate in CI.
I would scope this to the BPF site only. The rade_rx.c site needs a populated rade_rx_state in RADE_STATE_SYNC to reach :236, there is no cheap seam for that, and "the same one-line renormalization, upstream's own change" is a fair characterisation of it.
Needs maintainer decision
2. third_party/radae/COMMIT — the vendored tree now diverges from its recorded pin with nothing recording it.
COMMIT reads 4da110afd6de3bb2de8800d67c6d82a292a42e77. After this PR the tree is that pin plus two unrecorded upstream cherry-picks (5cb24369, a2d77e85). The next person who re-vendors from the pinned commit silently reverts both fixes, and nothing in the tree would tell them.
This tree already has a convention for exactly this, at third_party/radae/src/rade_text.c:5-11:
// Source: FreeDV-GUI src/pipeline/rade_text.c (BSD-2-Clause)
//
// AetherSDR changes from upstream:
// - #include "ofdm_internal.h" replaced with "mpdecode_core.h"So the ask is either to move COMMIT forward (if the two commits are HEAD-adjacent and nothing else changed between), or to add the same AetherSDR changes from upstream: block naming these two hunks. I am calling this a maintainer decision rather than a blocker because I found no vendoring rule in canon — rade_text.c is a convention in the same directory, not a cited sentence, and §5 says a convention is a nit at most. But it is the kind of gap the next upstream sync trips over, and it belongs in the PR body either way.
Nits
third_party/radae/src/rade_bpf.c:112—rade_cscale(phase, 1.0/phase_mag)uses adoubleliteral where the sibling atrade_rx.c:237uses1.0f / phase_mag. I checked the "no new warnings" claim before raising this and it holds:-Wconversionis applied only toaether_crdv(CMakeLists.txt:2250), not to the RADE sources, so the implicitdouble→floatnarrowing at therade_cscale(RADE_COMP, float)call is silent, and one double division per 960-sample chunk is free. I am not asking for a change — the body claims both hunks are verbatim upstream, and "fixing" this would manufacture a local divergence if upstream has1.0. If upstream has1.0f, match it; otherwise leave it as vendored and the within-tree inconsistency is upstream's.- The evidence table's own after-column is unexplained: output RMS is
1.0000at 0.0 h and1.0276from 3.3 h onward. A 2.76% (0.24 dB) step appearing once renormalization engages sits slightly oddly next to "no behaviour change on short sessions". Most likely an artifact of the harness's reference point, but it is a number in the proof that the proof does not account for — worth a sentence. rade_bpf.c:111-112divides byphase_magwith no guard, as doesrade_rx.c:237. I convinced myself this is unreachable rather than latent:phasestarts atrade_cone()(rade_bpf.c:74) and only ever multiplies byrade_cexp(-alpha)(:66), which is(cosf, sinf)and therefore unit-magnitude, sophase_magcannot approach zero. Non-blocking, and matching upstream here is the right call.
Security audit
CodeGuard did not look at the changed files. The 180 findings are on src/gui/MainWindow.cpp, MainWindow_Wiring.cpp, RadioModel.{cpp,h}, tests/audio_device_preference_policy_test.cpp, tools/check_command_plane.py and docs/architecture/aetherd-touchpoint-tags.json — none of which this PR touches. The CMDI-AMP cluster at MainWindow.cpp:527-609 lands on isTransientAudioDeviceId/containsAudioDeviceId, which together with the audio-device test file is the surface of main's tip commit aa0dd0e4c (#6387), not of this branch. So please do not read a clean scan of this PR into it.
Every lead I could check against source is a false positive (credit: security-audit (CodeGuard)): the CMDI-AMP hits are C++ reference parameters and lambda captures ([&id](const QByteArray& candidate)), the CMDI-BACKTICK hits are Markdown-quoted Flex/TCI verbs inside // comments, CS-INJ-role_override at :11822 is the word "override" in a QRM-classifier comment, CS-VLN-PATH-PROC at :6373 is a fixed "/proc/self/status" literal, CS-PII-IP at :6986 is the WSJT-X multicast default 224.0.0.1, and CS-VLN-SSRF-HEX is a hex client handle in a comment. No hardcoded credential exists in the flagged files. Independently of the scan: this PR's change is float arithmetic with no I/O, no parsing, no allocation and no attacker-reachable input, so there is no security surface to audit in it.
What I tried to break
- A third drift site left behind. Grepped every
rade_cmulin the vendored tree for a self-accumulating phase.rade_acq.c:77,:184,:186,:267andrade_ofdm.c:407all recompute fromrade_cexp/rade_cangleper index;rade_bpf.c:91is the only accumulator. The "two sites" claim survives. - Renormalizing once per call rather than per sample. Within one 960-sample chunk the drift is bounded by ~960·ε ≈ 1e-4 in magnitude, so ~2e-4 in
|phase|²— three orders below the symptom. Correct placement. - Division by zero and NaN poisoning of
bpf->phase. Unreachable from arade_cone()start times unit-magnituderade_cexpincrements; see the nit. Also walkedn == 0and negativen: the loop is skipped,phase == bpf->phaseis already unit, and the renormalization is a no-op. - An uninitialized read at
rade_rx.c:228-233.RADE_COMP rx_phase;is declared uninitialized and stored torx->rx_phaseafter the loop, which would now be renormalized. Bounded by compile-time constants (Nmf + M + Ncp> 0), so the loop always runs at least once. Not a defect, but it is why the new1.0f / phase_magcannot be fed garbage. - Whether the rx site explains the day-scale symptom. It does not, and the body does not claim it does.
rx->rx_phaseis not updated inside the loop at:230— only the finalrx_phaseis stored at:233— so that accumulator advances once per frame while the BPF advances once per sample, roughly three orders of magnitude slower drift. Fixing it is right; the −45 dB collapse is the BPF's alone. - Whether PR CI actually compiled this. It did, unlike the ORT-gated paths:
ENABLE_RADEdefaultsON(CMakeLists.txt:41) andci.ymldoes not override it, sobuild,check-macosandcheck-windowsall built both files. All five checks are green on60083b8. That proves compilation on three platforms and nothing about the behaviour — which is blocker 1. - Could not check: whether
5cb24369anda2d77e85are the real upstream commits, whether the two hunks are byte-verbatim, and the upstream literal (1.0vs1.0f). No network here. Settled by a maintainer opening the two upstream commits.
Everything above is reasoned from the head checkout at /tmp/aetherclaude/pr-6399. I ran no build and no test and drove no radio; none of these findings were reproduced at runtime.
Recommendation
Request changes — narrowly, and only for the test. The diagnosis is correct, I verified it independently of the body, the fix is minimal and in the right place, and the two scope claims I could check both held up. Nothing here needs rethinking.
What is missing is the one artifact that keeps it fixed. Register a socket-free CTest on the BPF site using the shape above — seeded phase, one sample, assert the magnitude came back — and this is ready. The vendoring-provenance item (#2) is the maintainer's call on whether to move COMMIT or add the rade_text.c-style upstream-changes block; either is a two-line change to the same PR. Thanks for the clear mechanism write-up and for naming the unreproduced symptom instead of papering over it — that honesty is what made the rest of the review cheap.
🤖 aethersdr-agent · cost: $12.8997 · model: claude-opus-5
|
Cisco CodeGuard — static analysis of this PR (185 finding(s))
Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them. 🤖 aethersdr-agent · cost: $14.4306 · model: claude-opus-5 |
rfoust
left a comment
There was a problem hiding this comment.
Issue fit
The patch addresses both phase-normalization sites in #6398 and appears correct. Both hunks exactly match upstream freedv/rade_c commits 5cb24369 and a2d77e85, which I retrieved and compared independently. I incorporated the additional AetherClaude review and CodeGuard comment before posting this review.
Scope
| File | Change | Claimed by title/body? | Verdict |
|---|---|---|---|
| third_party/radae/src/rade_bpf.c | Normalize saved mixer phase | Yes | In scope |
| third_party/radae/src/rade_rx.c | Enable existing RX phase normalization | Yes | In scope |
Everything in the diff is explained by the issue. No unrelated files, API additions, defaults, UI preferences, or CHANGELOG entry.
Blocker
- Deliver registered, socket-free regression coverage for both sites — see the inline comments. The PR provides numerical results but no runnable harness or registered test. This has a deterministic seam: I compiled a socket-free harness against the actual RADE implementation and bundled Opus, and it passes with the patch and fails with the fixes reverted. docs/agents/tests-ci.md routes DSP assertions to a socket-free CTest. The RX site is also reachable without a radio or synthetic peer: initialize with the production bottleneck/auxdata settings, inject a synchronized state and an off-unit phase, and invoke rade_rx_process(). Neither site requires a many-hour CI run to detect removal of normalization.
Verification and attacks that held up
- Local ARM64 aethercore build passed with RADE enabled, using the prescribed ARM64 toolchain and build-codex-arm64 directory. Both changed objects are ARM64, and the Ninja graph contains no RNNoise x86 sources. This was a core build, not a full GUI build or broad CTest run.
- A 100,001-block BPF comparison at 8 kHz, 960 samples/block, 1475 Hz centre and 1740 Hz bandwidth produced the following. The RMS reference is a settled block after 12 seconds, avoiding startup filter settling:
Unpatched, 3.333333 simulated hours:
phase=0.485761017 RMS relative to baseline=0.236303557 exit=1
Patched, 3.333333 simulated hours:
phase=1.000000000 RMS relative to baseline=0.999999522 exit=0
- Actual RX processing with injected phase magnitude 0.5, BPF disabled to isolate the second site, returned:
Unpatched: RX phase after one real process call: 0.500000000 exit=1
Patched: RX phase after one real process call: 1.000000000 exit=0
This proves the normalization behavior, not successful RF acquisition or SNR recovery. It uses injected internal state, with no socket, device, or firmware peer.
- Reset, empty BPF blocks, and varying valid block sizes retained finite unit phase on the patched version. I also examined initialization and call-size bounds; I found no reachable zero-magnitude division regression for valid production state.
- Reviewed head: 60083b8. GitHub merge ref: a35c948, based on f6cd464. Local build used a constructed snapshot of main 1b77f49 plus this patch. At posting, main is 8207725; the intervening change touches GUI availability and associated docs/tests, with no RADE, CMakeLists.txt, or tests/tests.cmake changes. The merge ref is stale, so the green PR CI is not evidence for latest-main integration. All reported checks are green and GitHub verifies the commit signature.
- I did not drive the GUI: demo mode does not provide the Flex DAX path RADE requires. No live radio was accessed and no TX was performed. Full-app SNR recovery remains unverified, as the PR discloses. A model-invocable automated review pass was unavailable; I performed the independent failure-path pass locally. The CodeGuard comment names files outside this two-file diff and supplies no demonstrated defect introduced by these arithmetic edits.
Non-blocking notes
- Record the two upstream patches alongside the vendored pin. third_party/radae/COMMIT still names 4da110afd6de3bb2de8800d67c6d82a292a42e77; a subsequent re-vendor should have an explicit record that these fixes must be retained. This is a provenance convention, not a cited governance blocker. Do not simply change the pin unless the full tree matches it.
- Correct the attenuation units: 0.003% output RMS is approximately -90 dB (20 log10), not -45 dB. The latter treats the RMS ratio as a power ratio. Also explain the initial-reference transient behind the after-column's 1.0276 value.
- Reconcile the stated docs exemption with AGENTS.md's explicit user-visible-change documentation requirement. A concise current-behavior note in the RADE page can satisfy it without adding incident history.
Recommendation
Request changes, narrowly for regression coverage. Independent before/after checks support the implementation. Add registered tests that fail when each normalization is removed; the full-app SNR claim should remain explicitly unverified until the disclosed maintainer reproduction is performed.
Both complex phase accumulators in the vendored RADE V1 receiver are
advanced by repeated complex multiplication and never renormalized, so
float32 rounding error shrinks their magnitude without bound. The BPF
mixes down to baseband and back up with the same drifting phase, so its
output is attenuated by roughly |phase|^2. Over a long receive session
the BPF output collapses toward zero while pilot correlation and the SNR
estimator keep running on the near-zero signal, producing large negative
SNR readings while rade_sync() still reports sync.
Two sites, both taken verbatim from upstream freedv/rade_c:
- third_party/radae/src/rade_bpf.c - renormalize the mixer phase at the
end of rade_bpf_process() (upstream 5cb24369).
- third_party/radae/src/rade_rx.c - enable the renormalization that was
already present but commented out (upstream a2d77e85).
The BPF is unconditionally enabled for V1 (rade_api_nopy.c passes
bpf_en=1), and both files are compiled into aethercore, so every build
with ENABLE_RADE=ON is affected.
Measured with a standalone harness driving rade_bpf_process() with a
constant on-centre tone, comparing this tree before and after the patch
(simulated hours of continuous receive, 8 kHz, 960-sample chunks):
sim hours | before: |phase|, out RMS vs start | after: |phase|, RMS
0.0 | 0.999993 1.0000 | 1.000000 1.0000
3.3 | 0.474835 0.2317 | 1.000000 1.0276
10.0 | 0.107226 0.0118 | 1.000000 1.0276
23.3 | 0.005479 0.0000 | 1.000000 1.0276
Before the patch the filter output falls to 0.003% of its starting level
(about -45 dB) after a day of receive; after it is flat. Upstream reached
the same result from a user report of freedv-gui reporting -18 to -20 dB
SNR while still in sync after 24-48 hours of runtime
(freedv/rade_c#14, freedv/freedv-gui#1355).
No API change and no behaviour change on short sessions. No docs/user
update: the page documents current behaviour, and the style guide's
"state current behavior, never history" rule means a defect being
removed here leaves nothing for the operator to do.
Per Principle XI this harness is the implementing agent's evidence, not
independent verification - the squash-merge CI re-run and maintainer
reproduction on pre- and post-merge builds are the gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60083b8 to
d58b72c
Compare
…es. Principle XI. Review of aethersdr#6399 asked for registered, socket-free regression coverage of both renormalizations, the provenance of the two cherry-picks, and a docs note. - tests/rade_phase_renorm_test.cpp (rade_phase_renorm_test): seeds the BPF mixer phase and the receiver's rx_phase off the unit circle, as hours of float32 drift would, and asserts one call returns each to unit magnitude. It also asserts the symptom on the BPF - output level two blocks after a 0.5 seed matches an undrifted filter - and that a healthy phase stays unit over 50 modem frames. The RX case injects RADE_STATE_SYNC into a receiver built with the production bottleneck/auxdata and the BPF off; it proves the normalization, not acquisition or SNR. No socket, device or peer. - tests/tests.cmake: declared under RADE_FOUND, no CI gate change. - third_party/radae/LOCAL_PATCHES.md: records the two cherry-picks next to the COMMIT pin so a re-vendor from the pin does not silently drop them. - docs/user: one current-behavior sentence on the RADE page. Mutation-checked against the patched tree: removing the BPF lines fails 5 of 9 checks, commenting out the RX lines fails 1 (the drifted-phase case; the unit-phase control correctly still passes), restoring both passes 9 of 9. Correction to the previous commit message and PR body: 0.003% output RMS is about -90 dB, not -45 dB. -45 dB treated the amplitude ratio as a power ratio. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Pushed
Still outstanding, as before: the SNR symptom has not been reproduced through the full app on air. The maintainer reproduction on pre- and post-merge builds remains the gate. CI on the new commit has not run to completion yet. 73, Ryan NF0T & Claude (AI dev partner) 👨🏼💻 Co-authored by Claude Sonnet 5.5 |
rfoust
left a comment
There was a problem hiding this comment.
Issue fit
Approved after re-review of 5dcee79. The two normalization fixes address #6398, and the registered socket-free test now covers both sites. The previous coverage blocker is resolved.
Scope
| File/group | Claimed in the body? | Verdict |
|---|---|---|
| rade_bpf.c / rade_rx.c | Yes: upstream normalization fixes | In scope |
| rade_phase_renorm_test.cpp / tests.cmake | Yes: registered regression coverage | In scope |
| LOCAL_PATCHES.md | Yes: upstream-patch provenance | In scope |
| RADE user page | Yes: current-behavior documentation | In scope |
No unrelated changes. The attenuation figure is corrected to -90 dB and the initial-reference transient is explained.
Verification
Built the prescribed ARM64 CMake target with RADE enabled, then ran ctest -R '^rade_phase_renorm_test$' --no-tests=error --output-on-failure: 1/1 passed, 9 checks, 0 failures. The test executable and host/system processors are ARM64; no RNNoise x86 sources appear in the Ninja graph. Test-registration and frozen-gate checks passed.
Independent mutation runs using the exact new test source and actual vendored DSP code reproduced the claimed results: patched 9/9 pass; removing BPF normalization causes 5 failures; removing RX normalization causes 1 failure. This tests seeded off-unit phase, healthy-phase behavior, empty BPF calls, and downstream BPF output recovery. No socket, radio, device peer, or TX was used. The standalone mutation binaries use no Qt; the registered local run passed despite an existing mixed-Qt installation warning.
Reviewed merge result 19deb82 against main bbded44. Main has subsequently advanced to 8b39af8 with workflow-only changes, leaving RADE, CMakeLists.txt, the registration file, and the regression test unchanged. Current PR CI and the docs build are green; both commits have verified signatures. Full-app SNR recovery remains unverified and requires the disclosed maintainer reproduction; demo mode cannot exercise Flex DAX receive. A separate model-invocable automated pass was unavailable.
Non-blocking nit
The inline documentation suggestion qualifies the SNR claim so it describes phase-rounding drift rather than implying actual channel conditions cannot lower SNR. This does not block merging.
No remaining blockers. Approval is based on the independent build, registered CTest, and mutation results above; the post-merge CI re-run and the disclosed full-app reproduction remain independent follow-up verification.
Summary
Fixes #6398
Two complex phase accumulators in the vendored RADE V1 receiver are advanced by repeated complex multiplication and never renormalized, so float32 rounding error shrinks their magnitude without bound. The BPF mixes down to baseband and back up with the same drifting phase, so its output is attenuated by roughly
|phase|². After a day of continuous receive the BPF output falls to about 0.003% of its starting level (≈ −90 dB). Pilot correlation and the SNR estimator keep running on the near-zero signal, so SNR reads far too low whilerade_sync()still reports sync.Both fixes are taken verbatim from upstream
freedv/rade_c:third_party/radae/src/rade_bpf.c: renormalize the mixer phase at the end ofrade_bpf_process()(upstream5cb24369).third_party/radae/src/rade_rx.c: enable the renormalization that was already present but commented out (upstreama2d77e85).+4 / −2 across two files. No API change, and no behaviour change on short sessions. The BPF is unconditionally enabled for V1 (
rade_api_nopy.cpassesbpf_en = 1) and both files are compiled intoaethercore, so everyENABLE_RADE=ONbuild is affected.Alongside the fix: a registered socket-free regression test for both sites (
rade_phase_renorm_test), a record of the two cherry-picks next to the vendored pin (third_party/radae/LOCAL_PATCHES.md), and one current-behavior sentence on the RADE user page. Everything else isthird_party/radaeV1 code, independent of the RADE V2 work.Constitution principle honored
Principle XI, Fixes Are Demonstrated. The harness below is my evidence, not verification. The gate is CI on the squash-merge commit and maintainer reproduction on pre- and post-merge builds (see Test plan).
Test plan
rade_phase_renorm_test(tests/rade_phase_renorm_test.cpp, declared intests/tests.cmakeunderRADE_FOUND) seeds each accumulator off the unit circle, standing in for hours of float32 drift, and asserts one call returns it to unit magnitude. No socket, device or peer. It runs in the full suite on every push tomainand in the weekly sanitizer lane; the frozen per-PR gate is unchanged.RADE_STATE_SYNCinjected,rx_phaseseeded at 0.5, productionbottleneck=3/auxdata=1, BPF offrade_rx_process()call saves magnitude 1rx_phaseat 1.0The RX case proves the normalization from injected internal state. It does not claim acquisition or SNR convergence.
Mutation check (MinGW build, patched tree as control):
Long-run harness (BPF only, constant on-centre tone, 8 kHz, 960-sample chunks), control is this tree unpatched. Output RMS is relative to the first block, which still carries the 101-tap filter's startup transient. That is why the patched column settles at 1.0276 rather than 1.0; rfoust's independent run against a settled reference reads 0.9999995.
| Simulated hours | Before:
|phase|| Before: out RMS vs start | After:|phase|| After: out RMS ||---|---|---|---|---|
| 0.0 | 0.999993 | 1.0000 | 1.000000 | 1.0000 |
| 3.3 | 0.474835 | 0.2317 | 1.000000 | 1.0276 |
| 10.0 | 0.107226 | 0.0118 | 1.000000 | 1.0276 |
| 23.3 | 0.005479 | 0.0000 | 1.000000 | 1.0276 |
aethercoreand the new test build clean on MinGW; the test target adds-Wno-cppfor Opus'snnet.hSSE#warningtools/check_test_registration.py --strictandtools/check_ci_test_gate.pypass locallyFor the maintainer: on a pre-merge build, run RADE V1 receive continuously (or loop a RADE recording) for many hours and watch SNR and BPF level. On the post-merge build both should stay flat.
Checklist
AppSettingscallsfreedv/rade_c, not decompiled or reverse-engineereddocs/user/docs/rade-digital-voice.mdgets one current-behavior sentence (the SNR readout does not fall away over a long session), no incident history. NoCHANGELOG.mdentry, per AGENTS.md.npm run buildfor the docs site was not run locally.third_party/radae/LOCAL_PATCHES.mdrecords the two cherry-picks beside theCOMMITpin, which is left unchanged🤖 Generated with Claude Code