Skip to content

fix(model): platform-based capability gating from FlexLib ModelInfo (#2177)#3954

Merged
ten9876 merged 3 commits into
mainfrom
fix/model-platform-capabilities
Jul 2, 2026
Merged

fix(model): platform-based capability gating from FlexLib ModelInfo (#2177)#3954
ten9876 merged 3 commits into
mainfrom
fix/model-platform-capabilities

Conversation

@ten9876

@ten9876 ten9876 commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Problem

A user with an AU-510 reported that the NRS / RNN / NRF extended-DSP toggles never appear in the VFO DSP menu (NRL, which isn't extended-gated, does show). Investigating surfaced a whole class of model-capability bugs from hand-maintained model.contains(...) sniffing that had drifted out of sync with FlexLib.

Root cause (two-fold for AU-510)

  • ClassificationhasExtendedDspFilters() used substring checks (contains("ML-")/"CL-"/"AU-") that silently missed the "S" server variants (MLS-9601 has no "ML-"; CLS-9301 has no "CL-") and was case-sensitive.
  • TimingsetHasExtendedDsp() was pushed once at onSliceAdded, so if the model status arrived after the slice (GUIClientID session restore), the flag stayed at its false default and never refreshed.

Fix

Migrate extended-DSP and diversity gating to the FlexLib-sourced ModelCapabilities table — Constitution Principle I (FlexLib is the model authority) — and close the gaps:

  • ModelCapabilities gains a RadioPlatform enum (Microburst / DeepEddy / BigBend / DragonFire), a platform field, an isDiversityAllowed flag, and a hasExtendedDsp() helper. The table is corrected (ML-380/CL-200/RT-100 placeholders → real ML-9600/CL-9300/RT-2122) and completed (MLS-9601, CLS-9301, AU-510) — all 23 current FlexLib models resolve.
  • Extended DSP now derives from platform (BigBend | DragonFire), fixing MLS-9601 / CLS-9301 and case-sensitivity.
  • Timing — extended-DSP is now also refreshed on RadioModel::infoChanged (mirroring the SmartSDR+/diversity refresh), so a late model status corrects the flag.
  • Diversity was the same anti-pattern, triplicated across 3 call sites and wrong both directions vs FlexLib IsDiversityAllowed:
    • FLEX-6500 (single-SCU) was wrongly granted diversity → now false.
    • ML-/MLS-/CL-/CLS- (dual-SCU) were wrongly denied it → now true.
    • All three sites now call RadioModel::isDiversityAllowed().
  • RT-2122 (DragonFire, SliceList {A,B}) now reports 2 slices instead of defaulting to 4.

Verification

  • model_capabilities_test asserts platform + 4m/2m/loop + diversity + extended-DSP for all 23 models, with explicit regressions for MLS/CLS extended-DSP, au-510 (lowercase), FLEX-6500 diversity-false, ML/CL diversity-true, RT-2122 diversity-false. Passes locally (ctest -R model_capabilities_test).
  • Full AetherSDR build links clean.

Note

The reported AU-510 symptom is most likely the timing half (FlexLib does exact-match uppercase, so the wire string is AU-510 and the old contains("AU-") would have matched once the model was known). Both halves are fixed here so the class of bug is closed regardless.

🤖 Generated with Claude Code

…2177)

Migrate the ad-hoc model-string sniffing for extended DSP and diversity to
the FlexLib-sourced ModelCapabilities table (Constitution Principle I —
FlexLib is the model authority), and fix the gaps that sniffing left behind.

Root of the reported AU-510 bug (extended DSP filters NRL/NRS/RNN/NRF not
appearing) is two-fold:

- Classification: the old hasExtendedDspFilters() used substring checks
  (contains("ML-")/"CL-"/"AU-") that silently missed the "S" server
  variants (MLS-9601 has no "ML-"; CLS-9301 has no "CL-") and was
  case-sensitive. Now derived from RadioPlatform (BigBend|DragonFire) via
  the table, which enumerates all 23 current FlexLib models.
- Timing: setHasExtendedDsp() was only pushed once at onSliceAdded, so if
  the `model` status arrived after the slice (GUIClientID session restore)
  the flag stayed stale. It is now also refreshed on RadioModel::infoChanged
  alongside SmartSDR+/diversity.

Diversity was the same anti-pattern, triplicated across three call sites and
wrong in both directions vs FlexLib IsDiversityAllowed:
- FLEX-6500 (single-SCU) was wrongly granted diversity.
- ML-/MLS-/CL-/CLS- (dual-SCU) were wrongly denied it.
All three sites now use RadioModel::isDiversityAllowed(), table-backed.

Also: RT-2122 (DragonFire, SliceList {A,B}) now reports 2 slices instead of
falling through to the default 4.

ModelCapabilities gains a RadioPlatform enum, a platform field, an
isDiversityAllowed flag, and a hasExtendedDsp() helper; the model table is
corrected (ML-380/CL-200/RT-100 placeholders -> real ML-9600/CL-9300/RT-2122)
and completed (MLS-9601, CLS-9301, AU-510). model_capabilities_test asserts
platform + 4m/2m/loop + diversity + extended-DSP for all 23 models, with
explicit regressions for the MLS/CLS/AU-510/6500 cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ten9876 ten9876 requested review from a team as code owners July 1, 2026 20:28
@ten9876 ten9876 enabled auto-merge (squash) July 1, 2026 20:28
@aethersdr-agent

Copy link
Copy Markdown
Contributor

Hi @ten9876 — thanks for taking this on. The AU-510 timing-vs-classification split in the writeup is a genuinely nice piece of root-causing, and migrating the sniffing onto the FlexLib ModelCapabilities table (Principle I) is the right call. Here's what I can see about the CI failure and how to close it out.

What failed

Two checks are red on 583a456, and both died at the compile step, not at test-run:

  • build (Linux) → failed at Build (log)
  • analyze (cpp) (CodeQL, also Linux) → failed at Build, so Perform CodeQL Analysis was skipped (log)

check-macos and check-windows were still in progress when I looked, so this reads as our usual Linux-first-to-finish pattern rather than a confirmed Linux-only break. Configure succeeded in both, so CMake is happy — this is a translation unit failing to compile.

What I checked (and ruled out)

I want to be straight about tooling: the CI log API I have surfaces only per-step pass/fail, not raw compiler text, so I can't quote the failing error: line — and I'd rather not invent one. What I could verify from the diff:

  • The 7-file diff is internally consistent. Every new symbol resolves — RadioModel::isDiversityAllowed(), ModelCapabilities::hasExtendedDsp(), and VfoWidget::setHasExtendedDsp() all exist; RadioModel.h already #includes ModelCapabilities.h; ModelCapabilities.h includes <QString>.
  • The struct reorder is safe. Making platform the first ModelCapabilities member could have broken any positional brace-init, but every other consumer (SpectrumOverlayMenu, XvtrPolicy, xvtr_policy_test) uses named-field access or = {}, so none are affected.
  • It's not a stale base. The branch is 0 commits behind main (merge-base is the current main HEAD), and nothing on main has touched these files since you branched — so this isn't a merge-time conflict against a moved base.

In short, nothing in the diff jumps out as an obvious compile break.

Two most likely causes — please grab the real error line

  1. A Linux-toolchain nuance the diff surfaces. Our Linux job builds against the Qt 6.4.2 floor + gcc/libstdc++, and Linux-only compile failures here are almost always one of: (a) a Qt 6.5+ API that AppleClang/MSVC tolerate but the floor rejects, or (b) a header that compiled via a transitive include under one STL and needs an explicit #include under libstdc++. I don't see an obvious candidate, which is why the exact line matters.
  2. A CI/toolchain hiccup unrelated to your code. In the failed build job, Setup DeepFilterNet3 (DFNR) was skipped while Cache DeepFilterNet3 ran. Your diff doesn't touch DSP/DFNR at all, so if a DFNR-dependent TU couldn't find its headers/libs, that's an infra failure, not your change. A plain re-run of the failed job is the fastest way to distinguish this from case 1 — if it goes green with no code change, it was environmental.

Concrete next step: open the build log, expand the Build step, and jump to the first error: line (the file + line will point straight at it). If you paste that line here I can pin down the fix immediately — and if it turns out to be the DFNR/setup step rather than one of your files, that's on our CI, not your patch.

The change itself looks solid; this is very likely one explicit include or a re-run away from green. Thanks again for the careful work. 🙏


🤖 aethersdr-agent · cost: $4.0641 · model: claude-opus-4-8

…atform field

Adding the RadioPlatform `platform` field as the first member of
ModelCapabilities broke xvtr_policy_test's positional brace inits
({false,true} / {true,false} assumed the old {has4Meters, has2Meters, ...}
order), which failed to compile in CI (the local incremental build hadn't
rebuilt this target). Switch to designated initializers ({.has2Meters=true}
/ {.has4Meters=true}) so the call sites are order-independent and resilient
to future capability fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice work — this is a well-scoped fix that turns a recurring class of drift bug (hand-maintained model.contains(...) sniffing) into a single FlexLib-sourced table lookup, which is exactly the right direction under Principle I. The two-fold root cause (classification gap on the S server variants + the one-shot timing bug) is clearly reasoned, and the test coverage is genuinely good: the MLS/CLS/au-510 extended-DSP regressions and the both-directions diversity flips (6500 false-positive, ML/CL false-negative) each have an explicit assertion.

A few things I verified while reviewing:

  • All three diversity call sites are covered. Grepping the tree for the old contains("6500")|...|contains("AU-520") diversity gate finds exactly the three sites this PR replaces (MainWindow.cpp ×2, MainWindow_Wiring.cpp ×1). The remaining contains("6500")/contains("AU-520") hits are in maxSlicesForModel and SmartCatProtocol (CAT ID strings) — correctly out of scope.
  • Table ordering is right. MLS-9601 before ML-9600 and CLS-9301 before CL-9300 (and the pre-existing suffix-before-base ordering) all hold, so the first-substring-match contract is preserved.
  • The xvtr_policy_test switch to designated initializers ({.has2Meters = true}) is necessary, not cosmetic — since platform is now the first struct member, the old positional {false, true} would no longer compile. Good catch handling that.
  • All six checks (build / macOS / Windows / CodeQL / analyze / a11y) are green.

Two non-blocking notes:

  1. Upstream fidelity is asserted by the table against itself. model_capabilities_test encodes the same diversity/platform values the table declares, so a green test proves internal consistency, not that the row matches FlexLib's actual IsDiversityAllowed/Platform columns. The flips all line up with real SCU counts (6500 single-SCU → false; 6600/6600M/6700(R) dual-SCU → true), so I believe they're correct — but a maintainer eyeball against the current ModelInfo.cs before merge would fully close the loop, since the whole point is that this table is the authority now.

  2. RadioModel::maxSlicesForModel (and maxPanadapters...) still sniff by substring — the same anti-pattern this PR is retiring. Reasonable to leave for a follow-up, but eventually folding slice/panadapter counts into ModelCapabilities too would finish the job. RT-2122 was correctly added to the 2-slice branch here in the meantime.

Thanks for the thorough writeup and the honest note that the AU-510 symptom is most likely the timing half — closing the class of bug regardless is the right call. LGTM pending the FlexLib row-check.


🤖 aethersdr-agent · cost: $2.4304 · model: claude-opus-4-8

…orm table

Complete the single-source-of-truth migration started for extended-DSP and
diversity: slice/panadapter capacity and firmware-family routing now derive
from the FlexLib-sourced ModelCapabilities table (Principle I) instead of
parallel model.contains() ladders that had drifted.

- ModelCapabilities gains a maxSlices field (FlexLib SliceList size; also the
  max panadapter count, since pan capacity tracks SCU/slice capacity on every
  current model). Default 2 mirrors FlexLib's DEFAULT entry.
- maxSlicesForModel() and maxPanadapters() now return capabilitiesFor().maxSlices.
  Fixes the dual-SCU ML-9600/MLS-9601/CL-9300/CLS-9301: maxPanadapters() omitted
  them from its contains() list, capping them at 2 panadapters instead of 4.
- FirmwareStager::modelToFamily() routes by RadioPlatform (DragonFire -> 9600,
  everything else -> 6x00) instead of contains("9600"). The old substring test
  misrouted both ways: ML-9600/ML-9600W (BigBend) went to the government 9600
  firmware family, while RT-2122 (the actual DragonFire radio) fell to 6x00.

Not migrated (no FlexLib source): maxPanBandwidthMhz/minPanBandwidthMhz come
from packet captures (#1385) and FlexLib has no bandwidth field; hasGpsHardware
isn't in ModelInfo either. Left as-is rather than guessing ML/CL values.

model_capabilities_test asserts maxSlices for all 23 models plus explicit
ML/CL=4 and AU-510M=2 / AU-520M=4 regressions. Full app + test build verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ten9876 ten9876 merged commit 0de7973 into main Jul 2, 2026
6 checks passed
@ten9876 ten9876 deleted the fix/model-platform-capabilities branch July 2, 2026 04:35
ten9876 added a commit that referenced this pull request Jul 2, 2026
#3961)

## Release documentation prep for **v26.7.1** (2026-07-02)

29 commits since v26.6.5. Docs-only + version bump — no code behavior
change.

### Version bump 26.6.5 → 26.7.1
The three canonical locations (per `AGENTS.md`):
- `CMakeLists.txt:2` — `project(AetherSDR VERSION 26.7.1)` →
`AETHERSDR_VERSION` (the app's version string)
- `README.md` — Current version line
- `AGENTS.md` — Current version line

### `CHANGELOG.md`
Promoted `[Unreleased]` → **`## [v26.7.1] — 2026-07-02`** in house style
(v-prefix, em-dash, "N commits since…" opener + headline), authored from
all merged PRs and categorized:
- **Added:** 3D stacked-trace spectrum (#3899), 3D dBm scale (#3937),
in-process NVIDIA BNR + AFX download-on-demand (#3902), TX meter
mouse-over readouts (#3936), SWR manual sweep range (#3885), CW sidetone
capture (#3895), KiwiSDR metadata scaffolding (#3898), bridge verbs
(#3920)
- **Changed:** BNR container/NIM backend removed, 60 fps + per-pixel GPU
FFT trace (#3958), FlexLib-sourced model capabilities (#3954/#2177), RX
speaker-latency cut (#3897), Display pane sections (#3935)
- **Fixed:** #3922, #3926, #3941, #3940, #3942, #3921, #3903, #3892,
#3924, #3891, #3890, #3900, #3947
- Fresh empty `[Unreleased]` restored above the release block.
Internal/CI/docs/self-fix PRs (#3931/#3916/#3934/#3929) omitted per
house style.

### `ROADMAP.md`
- Cycle header → "post-v26.7.1"
- NVIDIA BNR removed from **In flight** (shipped this cycle)
- Five v26.7.1 highlights prepended to **Recently shipped**

### `README.md`
- Highlights: GPU spectrum bullet now notes the **per-pixel FFT trace at
up to 60 fps** and the **3D stacked-trace** spectrum mode

### Reviewer notes
- Touches protected paths (`*.md` + `AGENTS.md` Tier-1 +
`CMakeLists.txt`) — needs `@aethersdr/infrastructure` sign-off.
- Tagging `v26.7.1` and cutting the release build are **not** part of
this PR (docs prep only).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <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.

1 participant