You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(flex): take panadapter and slice capacity from the radio (#5594 item 3) (#5603)
Closes#5594 (item 3 — the last open item). Refs #5262 (M1), #5554
(§2.4, §2.6), #3849 (step 3).
Follows #5602, which closed items 1, 2, 4 and 5.
## The answer was on the status path, not discovery
`FlexBackend.cpp` reported `maxPanadapters = mc.maxSlices` under a
comment admitting it was an approximation *"refined from live radio
status in a later touchpoint conversion."* This is that conversion — and
the radio has been sending the answer all along.
The issue proposed reading the `max_panadapters` /
`available_panadapters` **discovery** keys. That would have meant
touching `RadioInfo` and the discovery parser, both above the seam and
both moving under §2.7 — which is exactly why #5594 flagged this item as
*"the one item genuinely entangled with §2.6/§2.7"* and said to defer it
alone if sequencing was in doubt.
**It isn't entangled.** The radio-global status already carries
`panadapters=N`, the exact mirror of the `slices=N` we have decoded
since aetherd RFC 2.3:
- FlexLib parses it into `PanadaptersRemaining` (`Radio.cs:3652`),
beside `SlicesRemaining` (`:3714`).
- Our own `docs/architecture/digital-voice-thumbdv-waveform.md:311-316`
already records a live run reading **5/6** with a foreign Multi-Flex
client's objects open, then **8/8** once that client left.
- Our own test fixture at `RadioConnection.cpp:138` emits `radio
slices=1 panadapters=1`.
We were receiving the key and dropping it on the floor. So the fix rides
the proven path: one `carry()` in `decodeRadioStatus`, and a derivation
in `applyRadioChanges` that mirrors the slice block line for line,
reusing `RadioStatusOwnership::boundedSliceCapacity` — capacity
arithmetic, not slice-specific. **No `RadioInfo` change, no
discovery-parser change, no `RadioConnectRequest` change**, and every
line lands in code §2.6 keeps.
## Remaining is not capacity
Both keys report *free slots*, so capacity is open objects plus free
ones. Reading either directly as a capacity under-reports the radio by
however many objects happen to be open — the trap the issue warned
about, just on the status keys rather than the discovery ones. The
existing slice block already reasons this way; the pan block now says so
too, logs an impossible total against the model table, and clamps rather
than believing it.
## Both fields, not just panadapters
`caps.maxSlices` had the identical gap: `RadioModel` refined
`m_maxSlices` from the radio while the backend descriptor kept reporting
the model-table estimate. Since `RadioResourceAdapter.cpp:95` serializes
`backendCapabilities()` onto the aetherd control protocol, **a protocol
client was told the estimate while the GUI and the automation bridge
used the radio's own number.** Fixing one and not the other would have
left the descriptor asymmetric, so `RadioModel` now hands both back
through `FlexBackend::setRadioReportedCapacity()`, which change-guards
them and announces a revision through the mechanism added in #5602.
The setter is **transitional and documented as such**: turning
"remaining" into "capacity" needs the count of open objects, which lives
in `RadioModel` today. When §2.6 moves slice/pan lifecycle behind the
seam this becomes a backend-side tally and the setter goes away. That
was the explicit scope call — the alternative (fix the GUI value, leave
the wire descriptor guessing) would have missed the point of M1, which
exists to make the descriptor honest *before* the protocol freezes it.
`0` means "the radio has not said" and leaves the model-table fallback
in place, so firmware that never sends the keys is unaffected. Both
values reset on disconnect — a FLEX-6700 followed by a FLEX-6400 must
not inherit 8.
## A correction to the issue, verified under Principle I
#5594's table says FlexLib maps `available_slices` onto `MaxSlices`,
*"which invites exactly that confusion."* It doesn't. `Discovery.cs`
keeps **four** separate keys mapping to four separate properties:
| Key | Property |
|---|---|
| `available_panadapters` | `AvailablePanadapters` (`:141`) |
| `available_slices` | `AvailableSlices` (`:154`) |
| `max_panadapters` | `MaxPanadapters` (`:247`) |
| `max_slices` | `MaxSlices` (`:260`) |
`API.cs:186-189` copies all four separately. The remaining-vs-capacity
trap is real, but it is not FlexLib's doing — and on the status path,
which is where this fix lives, FlexLib's own naming (`…Remaining`) is
unambiguous.
## Verification
Linux x86-64, GCC / Qt 6.11, RelWithDebInfo, RTL + ASR enabled, isolated
worktree.
| Check | Result |
|---|---|
| Full build (3165 targets) | passed |
| **Full CTest suite** | **405 / 405 passed**, 3 skipped |
| `check_engine_boundary.py --strict` | 1223 files, **0 would block**
(102 pre-existing EB3 warnings, unchanged) |
| `check_test_registration.py` / `check_ci_test_gate.py` /
`gen_touchpoint_manifest.py --check` | all OK |
### Mutation checks
| Mutation | Failures |
|---|---|
| Pan capacity re-derived from slice capacity (the assumption this
removes) | 3 |
| Announce guard removed (a storm on every `radio …` status) | 3 |
| `0` treated as a real capacity rather than "not reported" | 2 |
| No reset on disconnect (next radio inherits the previous one's limits)
| 1 |
| `panadapters` key not decoded | 1 |
Unmodified source passes both affected tests again.
### Coverage boundary
Decode and ok-guard are pinned in `aetherd_radio_decode_test` (the
`panadapters` key is carried independently of `slices`, and a malformed
value is dropped rather than becoming 0). The backend descriptor —
fallback, precedence, the change guard, the "not reported" sentinel and
the disconnect reset — is pinned in `backend_capability_revision_test`.
**Not pinned:** the `RadioModel` arithmetic that turns
`m_panadapters.size() + available` into a capacity.
`boundedSliceCapacity` itself is already covered at
`radio_status_ownership_test.cpp:367-381`; what is uncovered is the
wiring between it and the pan collection. Driving that needs a
`RadioModel` with a live backend and adopted pans, which has no
socket-free harness today — the same boundary #5602 documented, and
#5254's layer-1 work is what would close it.
No physical radio was connected and no RF was transmitted.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments