Review audit · firmware & RF
17 September 2026
DM-1701 · HR-C6000 · AT1846S · STM32F405 · P25

Claude Opus 5’s P25 review, graded

Claude Opus 5 got the same brief as every P25 audit on bench: find why this firmware struggles to decode P25 Phase 1 and Phase 2 voice on the radio’s HR-C6000, and what to do about it. It worked in a clean room, with a fresh copy of the repository, no earlier reviews and no network — but through the Claude Code CLI rather than the harness the other fourteen shared, which is why this page is not ranked with them. I checked its claims against the code and the manual, and reran both experiments it left behind.

Overall grade
A
92 / 100 weighted

It measured what everyone argued about.

Fourteen audits argued about where the samples come from. This one settled it. HRC6000SetFMRx writes 0xE0 = 0x89 under the comment “Turn off Microphone input”; the monitor writes 0xC9, which is that value with the microphone bit added back. Then it found the stock driver reading the identical expression the monitor reads — i2s_Rx_Buffer[bufNum][j][i*2] — commented “only use the Left Channel of the Mic Audio”. Nobody else found either line.

It also wrote an emulator profiler and measured the thing every other audit missed: the IMBE vocoder at 9.44M instructions per frame, 655% of a 72 MHz core. Its profiler reruns here bit-for-bit. And it explains the symptom nobody had connected — one frame stalls the monitor tick for 55–190 ms, the capture ring holds 21 ms, and an overrun resets the parser, so even with a perfect input the call dies after the first voice frame of every LDU. All four decode-critical issues, a first. What it never opens is the instrument layer: no capture defect, no starvation counter, no squelch conflict.

Claims that check out
20 / 21
1 overstated · 0 wrong
Decode-critical issues found
4 of 4
the only audit of the sixteen
The vocoder, measured
655%
of a 72 MHz core; its profiler reruns exactly
Ranking
Unranked
different harness, no step cap

The run, not graded: 25 min · 120 agent steps · 54 tool calls · 185K output tokens (119K reasoning) · 17.5M tokens re-read · Claude Code CLI, subscription, one-shot

Headline findings

Six things to know about this review

Validated

It proves the microphone instead of inferring it

Every audit before this one argued from absence: the manual documents no route from the discriminator to I2S, so the samples are probably the microphone. This one found the positive evidence twice over. HRC6000SetFMRx writes 0xE0 = 0x89 with the driver’s own comment, “Turn off Microphone input”. The monitor writes 0xC9 — that same value with bit 0x40 set, the bit the driver calls “Mic Input to Line in 1”. And the stock code reads the very expression the monitor reads, i2s_Rx_Buffer[bufNum][j][i*2], commented “only use the Left Channel of the Mic Audio”.

HR-C6000.c:2849, 2962–2965 · sound.c:444 · p25mon.c:443, 648

Validated

It measured the vocoder, and the measurement reruns

It wrote a Unicorn profiler, ran the demo firmware’s self-test inside it and counted instructions per call: mbe_processImbe7200x4400Frame at a mean of 9.44M instructions per frame, max 13.8M — 655% of what a 72 MHz core can issue at 50 frames per second, worst frame 955%. The demodulator, by contrast, is 39%. I reran its profiler in a clean copy of the repository and got the same numbers to the instruction. Sixteen audits have now looked at this firmware; this is the first to put a number on the vocoder.

analysis/review/emu_profile.py · mbelib.c:306

New

It connects the CPU cost to the symptom on the radio

The number alone would be an optimisation note. What makes it a decode blocker is the chain it traces: mbelib runs inline inside p25monTick(), one frame stalls the tick for 55–190 ms, the capture ring holds 512 samples — 21 ms at 24 kHz — so it overruns, and the next pass sees the overrun counter move and calls p25rx_reset_stream(). So the parser returns to hunt mode after the first IMBE frame of every LDU: link control never completes, late entry never unmutes, and the call is dropped. Even with a perfect input.

p25mon.c:93, 578 · p25rx.c:105–123

Validated

It swept the filter corner rather than asserting the direction

Through the unmodified firmware demodulator, on standards-shaped C4FM: 0% symbol errors on a flat discriminator, 0.14% with a 20 Hz high-pass, 5.6% at 50 Hz, 17.5% at 100 Hz, 42.5% at 300 Hz — the typical CTCSS-reject corner — and 48.9% for de-emphasis plus a 3 kHz low-pass. Rebuilt and rerun here; every row reproduces. It then names the fix as the firmware’s own DMR profile, where the “magic register which allows low freq audio” is set, and adds the point the numbers imply: the demodulator fits DC once per window, so it cannot follow baseline wander.

analysis/review/c4fm_channel.c · AT1846S.c:204–225 · p25_4fsk.c:186–210

Major gap

It never opens the instruments

Its first step is a bench capture using the c command and tools/p25_capture.py — and it never checks either. The export stages the contiguous first 80 halfwords of each row while the decoder reads [j][i*2], so 158 of 160 positions differ; the parser certifies captures with missing chunks; pcm_starve is printed but never incremented. Nor does it find the stock squelch rewriting the codec mid-session, the ±10% health gate against a ±1% timing clamp, or the unknown-talkgroup audio bypass. Four other audits found those. This one measured the physics and skipped the plumbing.

p25mon.c:123, 443, 460–466 · trx.c:515–587 · p25rx.c:185–195

Overstated

The one place its own data disagrees with it

On the circular test oracle it did what nobody else did — it tested whether the shared filter matters, generating true C4FM shaping and running it through the production receiver. Its conclusion: the mismatch is harmless and the matched filter needs no change. The second half holds. The first understates its own table: at σ = 500 Hz the true-C4FM waveform gives 0.064% symbol errors against 0.034% for the self-shaped one. That is a 2× penalty, small in absolute terms and real.

analysis/review/results_c4fm.txt

Scorecard

How the 92 breaks down

The same rubric, weights and answer key as every P25 audit on bench — but a different harness, so the scores are shown for reading rather than for ranking.

Dimension scores

Hover or focus a bar for the reasoning. Weighted total 92, grade A.

0255075100Score out of 100Accuracy & evidenceweight 30%94Coverage of decode problemsweight 25%86Root cause & prioritisationweight 15%96Fix plan & acceptance gatesweight 15%93Originality & attributionweight 10%95Clarity & calibrationweight 5%92
Rubric table
DimensionWeightScorePointsWhy
Accuracy & evidence30%9428.220 of 21 claims hold and none is wrong. Both experiments reproduce bit-for-bit, and three citations nobody else found are exact to the line. One judgement is overstated: it calls the test oracle’s shared filter harmless when its own numbers show a 2× symbol-error penalty.
Coverage of decode problems25%8621.5All four decode-critical issues — the first audit to manage it — plus the MFID muting, the memory ceiling with a concrete overlay, and the chip’s one-layer mode developed into a route. It finds no instrument defect at all: not the capture export, the starvation counter, the squelch conflict or the health gate.
Root cause & prioritisation15%9614.4Three independent blockers, each stated as sufficient on its own, and the only audit to join the CPU cost to the observed field symptom through the ring overrun and the parser reset. It also says plainly which earlier work was correct but aimed at the wrong signal.
Fix plan & acceptance gates15%9313.9A ten-minute bench test before anything else, then two routes: the chip’s own demodulator with a falsifiable acceptance test, or the hardware tap. The RAM overlay names the three buffers to reuse. It does not fix the instruments its own bench test would run through.
Originality & attribution10%959.5The microphone proof, the vocoder measurement, the filter-corner sweep, the one-layer route and the memory overlay are five things no other audit produced, and two of them are runnable programs left in the tree.
Clarity & calibration5%924.6Ranked findings with an explicit “verified correct, no action needed” list, tables of measured numbers, and a closing statement of what the experiments do not establish. The register-flow diagram at the top is the clearest orientation any of the sixteen gave.
Weighted total100%92Grade scale: A ≥ 90 · B 80–89 · C+ 75–79 · C 70–74 · C− 65–69 · D 50–64 · F < 50
Why this page is not ranked

Same rubric, different harness

Everything below is graded exactly as the other fifteen audits were. What differs is how the model was run.

The thirteen ranked audits all ran through one harness: a Hermes profile with no skills, memory or delegation, its tools inside a container with no network and only the repository mounted, and a 150-step ceiling. This run used the Claude Code CLI on a subscription instead, stripped as far as that client allows — no skills, no plugins, no MCP servers, no memory files, no web tools, and no subagent tool, so one model did all the work.

Three differences remain and none of them can be argued away. The system prompt and the tool implementations are Claude Code’s, not the bench profile’s. The tools ran on the host rather than in a no-network container, so the absence of network access came from the toolset rather than from a sandbox. And Claude Code has no equivalent of the step cap — this run took 120 steps, which no ranked run would have been stopped at, but the ceiling was not there.

So the grade is real and the evidence is checkable, and the number is not comparable with the thirteen. It sits here the way the UNIONALPHA audit does: published, in the comparison tables, and outside the ranking.

Receive chain

Where its findings sit

Opus 5 covers the signal path end to end — source, conditioning, clock, vocoder and the parser’s call logic — and measures three of those stages. The red tags are the diagnostics, which it never examines even though its own plan depends on them.

RADIO · ANALOG FRONT ENDSTM32F405 · 72 MHz · -OsMicrophoneLineIn1 · enabled by E0 bit 6P25 C4FM carrier12.5 kHz channel±1.8 kHz peak deviationAT1846S receiveranalog FM register setHPF + LPF on · LF audio off · 25 kHzHR-C6000 codec ADCE0 = 0xC9 · 0x34 de-emphasis onno documented RF input to I2SI2S to the MCULRCK 24 kHz (manual: 8 kHz)left slot, 16-bitRFaudio?ADCDMA half-buffer ISRp25monI2sHalfBuffer160 frames per callbackSample ring512 samples ≈ 21 ms≤128 drained per tickResampler ×1.624 → 38.4 kHz, nominalhealth gate ±10 %RRC FIR + timing81 taps · 768/512 windowperiod clamp ±1 %p25rx framingsync · NID · LDU · FECfail-closed mutembelib IMBE≈16 M instructions/framecosf slow pathPCM ring → I2S TXradioSetAudioPath() per frame→ speaker amplifierCapture export ‘c’ISR stages [j][0..79]framed hex over USBUSB CDC consoleshared with 1 Hz reportp25_capture.pyparser on the hostdiagnostic tapF1F2F3F4F5F6N1N2N3N4
F1 raised by Claude Opus 5N1 never examinedDashed edge: no documented connection
F1The I2S stream is the microphone, proved from the driver’s own writes
F2AT1846S FM profile: filters on, low-frequency bit cleared, 25 kHz
F3De-emphasis and voice filtering in the capture path
F4The manual’s 8 kHz frame-clock rule makes 24 kHz out of spec
F5The vocoder needs 6.5× the CPU, and an overrun resets the parser
F6The test oracle shares the receiver’s filter (and it measured the effect)
N1The capture exports the wrong halfwords
N2The parser certifies incomplete captures
N3pcm_starve never counts
N4The stock squelch rewrites the codec mid-session
The front end

The corner sweep, and the register that fixes it

Its filter table is the only measured sweep any audit produced, and it lands on the same conclusion the grading model reaches from the other direction: this path cannot carry C4FM, and the fix is already in the firmware under another name.

Register state for a P25 session

What trxSetModeAndBandwidth(RADIO_MODE_ANALOG, true) leaves in place, next to OpenGD77’s own settings for 4800-baud 4FSK.

RegisterMonitor today (analog FM)OpenGD77 DMR modeEffect on C4FM
AT1846S 0x400x0030, low-frequency bit cleared: “so it should be cleared to receive FM”0x0031, “allows low freq audio”Removes the near-DC content that runs of equal symbols depend on
AT1846S 0x580xBC85, “enable some filters for FM e.g. high and low pass filters”0x9CDD, “disable all filters in DMR mode”High- and low-pass shaping of the symbol stream
AT1846S 0x440x06CC, AF gain 80%0x07FF, 100%; the DM-1701 “needs higher AF gain” to decode 4FSKLower discriminator level into the next stage
AT1846S bandwidth25 kHz12.5 kHz, forced for digitalWider noise bandwidth and deviation scaling set for ±5 kHz FM
HR-C6000 0x340x3C, “compressor off, de-emph on, 3 kHz audio filter”not written; the FM value persists−6 dB/octave from 300 Hz and a 3 kHz low-pass on the FM receive path (manual §6.2)
Sources: AT1846S.c:191–217, HR-C6000.c:2846–2854 and 2858–2875, trx.c:228–279, p25mon.c:724. Register comments are OpenGD77’s; the manual translation documents 0x34.

What the inherited 0x34=0x3C does to C4FM

Standard C4FM through each HR-C6000 receive stage alone, then the firmware’s live demodulator and framer. 20 LDUs: 180 IMBE frames possible.

04590135180IMBE frames decoded, noise-free (of 180)no audio filtering180 / 1803 kHz low-pass only180 / 180de-emphasis only0 / 180de-emphasis + 3 kHz low-pass (0x34=0x3C)0 / 180
De-emphasis alone leaves no frames decodable in the grading model. Its own run puts de-emphasis plus a 3 kHz low-pass at 48.9% symbol errors — the same verdict measured a different way.
Data table
Audio stageNoise-free frames · NIDsSymbol errors20 dB frames · NIDs
no audio filtering180/180 · 20/200.0%180/180 · 20/20
3 kHz low-pass only180/180 · 20/200.0%180/180 · 20/20
de-emphasis only0/180 · 0/2051.3%0/180 · 0/20
de-emphasis + 3 kHz low-pass (0x34=0x3C)0/180 · 0/2049.8%0/180 · 0/20
The measurement

What 655% looks like against the budget

Its profiler and the grading emulator disagree on the multiple and agree on the conclusion. The difference is the build: it profiled the demo firmware at -O2 with hard float; the answer key’s 11–16× comes from the live monitor build. It labels its own counts a lower bound on cycles.

Where one second of decoding goes

The firmware’s streaming loop, compiled with its own GCC flags and newlib and run in an emulated Cortex-M4F over 2.2 s of input (12 LDUs, 108 IMBE frames, all decoded). Instruction counts become time by assuming one clock cycle per instruction, the optimistic end.

0%200%400%600%800%1000%1200%Share of one CPU second at 72 MHz (-Os, 1.0 cycles per instruction)Resampler ×1.61.4 M instructions/s1.9%RRC FIR, timing and level fit28.0 M instructions/s39%Framing, NID and FEC0.7 M instructions/s1.0%mbelib IMBE decode and synthesis781.9 M instructions/s1,086%whole CPU
The demodulator fits: about 40% of the core, 60% at 1.5 cycles per instruction. The vocoder needs roughly 11 times the whole CPU, 16 times at 1.5. Building with -O2 changes these by under 1%, because the time is spent inside newlib’s prebuilt libm.
Per-stage table
Stage-Os, M instr/sShare at 1.0 · 1.5 c/i-O2, M instr/s
Resampler ×1.61.401.9% · 2.9%1.32
RRC FIR, timing and level fit28.0038.9% · 58.3%27.70
Framing, NID and FEC0.741.0% · 1.5%0.73
mbelib IMBE decode and synthesis781.901,086.0% · 1,629.0%781.60
Total812.11,128% · 1,692%811.4

CPU time for each 20 ms of speech

One call to mbelib’s IMBE decoder per frame, uvquality 1, FEC-valid codewords with chosen pitch and voicing, mean over 100 consecutive frames.

0 ms100 ms200 ms300 ms400 ms500 msCPU time per decoded 20 ms frame at 72 MHz (mean, 1.0 cycles per instruction)L=20, all bands unvoiced3.0 M instructions42 msL=20, typical (15 of 20 voiced)9.7 M instructions134 msL=30, typical (21 of 30 voiced)14.0 M instructions194 msRandom pitch and voicing15.9 M instructions220 msL=56, all bands voiced31.2 M instructions434 ms20 ms deadline
Every frame type misses its 20 ms deadline, from about 2× for the simplest frames to more than 20× for loud voiced ones. The cause is mbelib’s per-harmonic phase, which accumulates without wrapping (mbelib.c:306). Once a call has run for about half a second, nearly every voiced-band cosf argument exceeds about 201 radians, and newlib’s cosf takes its __kernel_rem_pio2f path at roughly 1,700 instructions per call instead of 40 to 100. The real firmware links that same path.
Per-frame table
Frame typeMean, M instrWorst, M instrMean ms at 1.0 · 1.5 c/i
L=20, all bands unvoiced3.005.0242 · 62
L=20, typical (15 of 20 voiced)9.6810.23134 · 202
L=30, typical (21 of 30 voiced)13.9714.76194 · 291
Random pitch and voicing15.8626.17220 · 330
L=56, all bands voiced31.2532.97434 · 651
Claim check

21 claims, checked one by one

Twenty of twenty-one hold, five of them validated by rerunning the programs it left in the tree. One is overstated. None is wrong.

ItemWhat Claude Opus 5 claimsWhereResultNotes
C10xE0=0xC9 enables the microphone into Line-in 1: HRC6000SetFMRx writes 0xE0=0x89 to “Turn off Microphone input”, and 0xC9 is 0x89 | 0x40, the mic bit setHR-C6000.c:2849, 2962–2965; p25mon.c:648, 733ValidatedVerified at both lines. This is the decisive form of the argument and no other audit of the sixteen found it: the monitor’s own value is literally the mic-off value with the mic bit added back.
C1The stock driver reads the identical expression i2s_Rx_Buffer[bufNum][j][i*2] as “only use the Left Channel of the Mic Audio”, and the monitor reads that same samplesound.c:444; p25mon.c:443ValidatedExact, including the comment. The monitor and the DMR transmit path consume the same buffer the same way.
C1The long clock work in the bring-up log was real and correct, but it was work on the wrong signalSTAGE3-BRINGUP.mdHoldsThe fairest summary of that effort any audit has written.
C2mbe_processImbe7200x4400Frame costs a mean of 9.44M instructions per frame (max 13.8M), which is ~655% of a 72 MHz core at 50 frames/s, and the demodulator is ~39%analysis/review/emu_profile.pyValidatedIts Unicorn profiler reruns here bit-for-bit: mean 9,436,825, max 13,757,387, 655%. The first audit of sixteen to measure the vocoder at all. The answer key’s 11–16× comes from the live monitor build; this is the demo build at -O2 hard-float, and the review labels instruction counts a lower bound on cycles. Both say the same thing: the vocoder cannot run in real time.
C2The stall kills decoding, not just audio: mbelib runs inline in p25monTick, one frame stalls 55–190 ms, the 512-sample ring holds 21 ms, and an overrun calls p25rx_reset_stream, so the call ends after the first IMBE frame of every LDUp25mon.c:93, 578; p25rx.c:105–123HoldsThe mechanism checks out line by line, and it is the piece no other audit assembled: it converts a CPU number into the observed field symptom.
C2The cost is mbelib’s synthesis calling cosf per sample per harmonic, with newlib doing full range reduction every call; uvquality is already at its minimummbelib.c; p25mon.c:749HoldsMatches the emulator’s own call profile, which counts ~13,000 cosf and ~13,000 rem_pio2f calls per frame.
H1The monitor loads the AT1846S FM profile — voice-band filters on, the low-frequency “magic” bit cleared — and selects 25 kHz for a 12.5 kHz channelAT1846S.c:191–199, 204–225; p25mon.c:724ValidatedThe DMR profile’s {0x40, 0x00, 0x31} carries the comment “THIS IS THE MAGIC REGISTER WHICH ALLOWS LOW FREQ AUDIO”, and the FM profile leaves it clear. Both halves of C1, with the fix named as the firmware’s own DMR profile.
H1Measured symbol error through the unmodified firmware demodulator: 0% flat, 5.6% with a 50 Hz high-pass, 17.5% at 100 Hz, 42.5% at 300 Hz, 48.9% for de-emphasis plus a 3 kHz low-passanalysis/review/c4fm_channel.cValidatedRebuilt and rerun here; every row reproduces. It is the only audit to sweep the filter corner rather than assert the direction, and the de-emphasis row agrees with the grading model, which decodes 0 of 180 frames on that path.
H1The demodulator removes DC once per window, so any high-pass corner above a few tens of Hz causes baseline wander it cannot follow; add decision-directed baseline restorationp25_4fsk.c:186–210HoldsCorrect, and it is the only audit to connect the demodulator’s missing baseline tracking to a specific measured failure rather than listing it as a design nit.
H2The manual says the I2S frame clock “must be 8KHz”, so the 24 kHz latch is outside specification and its heal machinery should not be carried into a new designmanual 1219HoldsQuoted at the right line.
H3The link is exactly full: .data 17,000 + .bss 112,532 + 1,540 reserved = 131,072, and CCM is 64,376 of 65,536MDUV380_FW.mapHoldsMatches the map.
H3Overlay P25 buffers on memory the monitor mode does not use: ambebuffer_encode (8 KB), satelliteDataNative (~12 KB), NMEARecordingBuffer (4 KB)OpenGD77 sourcesHoldsThe three buffers exist and are unused while monitoring. The only audit to propose where the memory would actually come from rather than saying a budget is needed.
M1p25rx.c:159 treats any LDU1 link control with MFID != 0 as unknown and mutes, and Motorola systems send MFID 0x90, so calls flip muted/clear at every LDUp25rx.c:159–163HoldsThe code half is exact (bytes[1] != 0 forces P25_CALL_UNKNOWN). The Motorola MFID 0x90 behaviour is outside what the repository can show, and the “roughly half of the frames play” figure is a modelled consequence, not a measurement — but the defect and its direction are right.
M2The generator shapes symbols with the receiver’s own RRC, but the mismatch turns out harmless: the production filter decodes true C4FM shaping with essentially the same marginanalysis/review/c4fm_channel.cOverstatedIt is the only audit to test this rather than assert it, and the conclusion that the matched filter needs no change is sound. “Essentially the same margin” understates its own numbers slightly: at σ=500 Hz the true-C4FM waveform gives 0.064% symbol errors against 0.034% for the self-shaped one, about a 2× penalty.
M3Phase 2 needs a 6000-baud H-DQPSK demodulator, burst and superframe sync, ISCH/SACCH/FACCH, descrambling seeded from WACN/System ID/NAC, and the AMBE+2 half-rate path; p25_trunk.c:26 invalidates TDMA identifiersp25_trunk.c:26; ref-sources/dsd-fmeHoldsComplete and correct.
Route AThe C6000’s one-layer continuous mode demodulates 4800-baud 4FSK into 36-byte frames from RX RAM 0x30, and the stock firmware contains a matching register blockmanual 2553–2632; analysis/stock-p25/REPORT.md:77, 88HoldsThe manual passage and the stock block are both where it says. Sixteen audits have now seen this mode: most missed it, two denied it, and this is the only one to turn it into a plan with an acceptance test.
Route ADMR and P25 use the same symbol-to-dibit map (+3→01, +1→00, −1→10, −3→11) and P25 deviation is within 8% of DMR’sp25_4fsk.c; manual §5.4HoldsThe deviation arithmetic is right (1800/1944 = 7.4%). The shared symbol map is correct for the wire order the parser expects; whether the chip emits frames without DMR sync remains the open question the review itself flags.
Route AAcceptance test: the P25 sync 0x5575F5FF77FF should appear with ≤4 bit errors, exactly 1728 bits apart during voice; if spacing jitters, the mode is not gap-free and Route A failsp25rx.cHoldsThe sync word and the 1728-bit LDU spacing are correct, and this is the only falsifiable acceptance test any audit has written for the untried hardware route.
Route BADC1 channels 0, 1 and 3 are volume, battery and VOX, so none carries receive audio; the M17 precedent taps AT1846S AF to an MCU pinCore/Inc/main.h:130–137; analysis/stock-p25/REPORT.md:175–176HoldsChecked; it also says to verify the pin against the schematic rather than trusting the precedent.
VerifiedIts “verified correct” list: sync and polarity, BCH generator and parity bit, status-symbol positions, LDU layout 144 + 6×92 + 16 + 72, HDU/TDU/TDULC lengths, RS/Golay/Hamming parameters, TSBK trellis, IDEN_UP and grant layouts, the timing loop, linear resamplingp25rx.c; p25_fec.c; p25_nid.cHoldsEvery item matches the answer key’s own list of what works. It is also the only audit to publish such a list, which is what stops a reader concluding the parser is the problem.
ScopeInstruction counts are a lower bound on cycles; the frames are synthetic; the AT1846S corners are unmeasured; whether one-layer mode is gap-free is unknownHoldsFour limits, each of them real, stated without being asked.
Provenance

What was already known, and what it added

Every clean-room audit could see only the repository: its notes, code comments and the HR-C6000 manual, summarised in the first column. Opus 5 raises every decode-critical row, including the vocoder row no audit had raised before, and none of the instrument rows.

IssueProject docs
in the repository
Grok 4.6
Sep 17
DeepSeek V4.1 Flash
Sep 17
Muse Spark 1.3 Contributor
Sep 17
GLM 5.3 Flash
Sep 17
UNIONALPHA
Sep 17
Qwen3.8 Flash
Sep 17
Qwen3.8 Max
Sep 17
GLM 5.3
Sep 17
DeepSeek V4 Pro
Sep 17
Gemini 3.8 Flash
Sep 17
HY4 Preview
Sep 17
GPT-5.6 Sol
Sep 17
GPT-5.6 Luna
Sep 17
GPT-6 Astra
Sep 17
Claude Opus 5
Sep 17
Capture export stages the wrong I2S halfwords never examinedabsentraised
§1, first code change
absent
its first experiment uses that capture
absent
trusts the capture
absent
relies on the capture
raised
F3, fixed and verified
raised
P1-2, tests miss the adapter
absent
calls the capture validated
raised
§2, proven with a reproducer
absent
calls the capture module sound
absent
certifies the capture path
absent
its plan depends on it
raised
derived from the stride
absent
its first step relies on it
raised
F4, reproduced on the host
absent
Capture parser certifies an incomplete streamabsentabsentabsentabsent
trusts the parser
absent
relies on the parser
absentabsentabsent
relies on the parser
absentabsentabsentabsentpartly
module tests miss the stride
absentpartly
asks for tests at the callback
absent
pcm_starve never increments never examinedabsent
documented as working
absentabsentabsent
relies on it
absentraised
F7
raised
P1-7
absentraised
§2
absentabsent
named only as a predicted symptom
absentabsentraised
F7, first to find it
raised
F9, reproduced
absent
No static RAM marginpartly
margins still to measure
absentraised
M6, 0 bytes free
partly
“nearly full”, from the docs
raised
F5, byte-exact map audit
raised
F6, byte-exact
partly
calls the reservation headroom
absentpartly
“~zero headroom”
partly
from the docs, not the map
raised
§3.5, exact map symbols
absentraised
exact map figures
absent
could not read the map
raised
F8, from its own link map
raised
with the overlay buffers named
The I2S stream is most likely microphone audiopartly
open, leaning sceptical
raised
§1, 0x89 versus 0xC9
raised
B1, four-value test
raised
P1-1
partly
open, leans towards RF
partly
blocking, but never says microphone
raised
P1-4, 0xE0 mic bit
raised
A, the clearest case yet
raised
§1, three sources
raised
§1–§2, with the tap as the fix
raised
§1 and §3.1
partly
listed unverified, tested first
raised
Blocker 1, source unproven
raised
F1, as the safe default
raised
F2, as far as the evidence goes
raised
proved from 0xE0=0x89 and sound.c
HR-C6000 de-emphasis on the capture pathabsentraised
§2, bit 5 of 0x34
absent
calls it benign
raised
P1-2, 0x34=0x3C
raised
F2, closes the eye
partly
cited, called unmeasured
raised
P1-3, 0x34=0x1C
raised
C, with the 0x34 bit 5 fix
raised
§1, tilts the eye
absentraised
§3.3, 0x34=0x3C
raised
P1-1, and the boot table too
raised
0x34=0x3C, never replaced
raised
F2, without the fix
raised
F2, 0x34=0x3C
raised
measured at 48.9% SER
AT1846S FM filters, low-frequency bit, 25 kHzpartly
“require characterization”
raised
§2, register level
raised
B2, filter register
raised
P1-2, 0x58 filters
partly
“voice filtering”; wrong bandwidth premise
partly
cited, called unmeasured
raised
P1-3, DMR 0x58 probe
absentabsentabsentraised
§3.3, with the DMR fix
absentraised
25 kHz for a 12.5 kHz channel
absentraised
F2, the FM settings table
raised
all three, with the DMR fix
0x10=0x6E hybrid state; 0x36 dual rolepartly
bring-up clock rules
partly
misses 0x6E and the 0x36 clock gate
partly
“undocumented hybrid state”
partly
quiet-chip registers
raised
F4, 0x10=0x80 kills the clock
partly
F1, 0x6E against 0x80
partly
“hybrid I2S state”
partly
slot engine and eco only
partly
§4.3, 0x10=0x80 hazard
partly
“inconsistent hybrid state”
partly
0x36 and 0xE0, not 0x10
partly
0x36, 0xE0, 0x26; not 0x10
partly
FM mode writes, not 0x10
partly
0x36, 0xE0, 0x26 listed
partly
0x36 and 0x10 via the squelch path
partly
0x36 and 0xE0, not 0x10
Manual: I2S frame clock “must be 8KHz”absentraised
§3
absent
quotes the paragraph, not the rule
absentabsentraised
F4
absent
quotes the formulas, not the rule
absent
cites the section, not the rule
absentabsentabsentraised
P1-2, with a test for it
absentabsentraised
F7, with the divisor arithmetic
raised
H2
One-layer 4FSK test mode as a P25 tappartly
stock BER-test block only
raised
Gate D
raised
B5, exact recipe
absent
dismissed
absentpartly
worth a bounded test
absent
ruled out at “9600 Bd”
raised
step 2, a symbol source
absent
“not a P25 symbol source”
absent
“no raw modem mode”
absent
“zero internal silicon capability”
partly
cited, then dismissed
absentabsentpartly
points at the layer architecture
raised
Route A, with an acceptance test
±10% health gate versus ±1% timing clampabsentabsentraised
B3, impact overstated
raised
P1-3
raised
F3
absentabsentabsentabsentabsentabsentabsentraised
with the resampler consequence
raised
F4, with the resampling consequence
raised
F7
absent
Fail-closed muting at LDU cadenceabsentabsent
late-entry mute only
absentabsent
calls it an asset
absentabsentpartly
“keep it”
absentabsent
calls it tested
absentabsent
certifies it as correct
absentabsent
certifies it as correct
raised
F8, weighed as a trade
raised
F6, the late-entry half
partly
via the MFID path
Non-standard MFID mutes clear callsabsentabsentabsentabsentabsentabsentabsentabsentabsentabsentabsent
certifies it as correct
absentabsent
certifies it as correct
absentabsentraised
M1, with the Motorola case
Test waveform shares the receiver’s RRC filterpartly
“synthetic RRC/AWGN” caveat
absentpartly
tested it, says not to fix
partly
synthetic only, wants recordings
absent
would extend that model
raised
F5, unquantified
absent
would extend that model
partly
synthetic only, not the circularity
partly
synthetic only
partly
“ideal RRC-shaped signal”
absentpartly
P1-5, circularity without the filter
raised
names the shared table
partly
synthetic, not circular
raised
F10, with why the tests missed F1/F4
raised
tested, called harmless
MCU runs at 72 MHzraisedraised
in passing
absentraised
P1-5
absentraised
F7
absentabsentraised
§5
raised
§7, with the OpenRTX precedent
raised
§3.5, with the PLL settings
raised
P1-4, with the PLL lines
partly
named as the target, never costed
raised
F5, as the real-time risk
raised
F9, the target for measurement
raised
and measured against it
Vocoder needs 11–16× the 72 MHz CPUpartly
decode timing unmeasured
absent
“fine on a 1 ms tick”
absent
“vocoder question settled”
partly
unmeasured; fix order backwards
absent
“in good shape”
partly
deadlines “unproven”
absent
“not the problem”
absent
“not the problem”
partly
inline, unmeasured
partly
cites 4.36M, calls it 60%
absent
puts it at 15–18 ms per frame
partly
87% measured, budget unresolved
absent
left as a later measurement
partly
named, never sized
partly
counted the filter, not the vocoder
raised
measured 6.5× on the demo build
Direct discriminator tap (M17 mod)raisedraised
uncredited
raised
pins, timer ADC, 48 kS/s
raised
fallback, pin 9
raised
fallback
raised
fallback
raised
fallback
raised
step 5, the likely answer
raised
§1 pivot
raised
its central recommendation
raised
§5.1, with the ADC and DMA design
raised
the fallback if Gate 0 fails
raised
ADC with bias and anti-alias
raised
F1, the fallback route
raised
F2, ADC or another interface
raised
Route B, with the ADC channels checked
Phase 2 architecture and scopepartly
not implemented
partly
misplaces the AMBE+2 decoder
partly
voice via mbelib AMBE+2
raised
with RF band limits
partly
says mbelib has no AMBE+2
raised
most accurate section
partly
DMR and X2-TDMA parts
raised
AMBE+2 present but uncalled
raised
with a reference map
partly
no symbol rate or sync
raised
accurate on rate and slots
partly
right conclusion, H-CPM mislabelled
raised
plus the linked-symbol check
raised
careful about the vocoder
raised
the most complete of the fourteen
raised
including the descrambler seed
Two unverified SPI writes per decoded 20 ms frameabsentabsentabsentabsentabsent
treats them as protection
absentabsentabsentabsentabsentpartly
the SPI0inUse mechanism
partly
silent SPI0 failures
raised
radioSetAudioPath per frame
absentpartly
the sink is cited, not the writes
absent
Capture sessions lack epochsabsentabsentpartly
measured=0 only
absentabsentraised
F9, 8 kHz under a 24 kHz header
absentabsentabsentabsentabsentabsentpartly
asks for source metadata
absentraised
F4, segment on retune and clock change
absent
Ring and tick real-time budgetpartly
deadlines unproven
partly
calls it fine
absentpartly
overruns look like weak RF
absentraised
F7, 1 ms is a minimum
partly
register stalls against the ring
absentpartly
1 ms tick as a constraint
absentabsentabsentraised
the 2 s refresh stall
partly
buffer inventory only
raised
F9, all four figures exact
raised
the 21 ms ring against a 55–190 ms stall
Clock config 3 assumes 12,288 Hz; the codec formula gives 12,000absentabsentraised
B3, clock model
absentpartly
“guessed semantics”
absentraised
P1-6, for a different reason
absentpartly
“unvalidated on hardware”
absentabsentabsentabsentabsentpartly
computes config 2 instead
absent
Clock-config writes bypass the verified SPI writerpartly
SPI retry note
absentraised
M1
absentabsentraised
F9
absentabsentraised
§4.2
absentabsentabsentraised
traced through four files
raised
F6, both call sites
partly
asks that writes be verified
partly
calls the heal machinery fragile
Stock squelch re-arms FM audio (0x10=0x80) during monitoring never examinedabsent
assumes it can’t re-arm
absentabsentabsentpartly
names squelch logic as a risk
raised
F1, new
absent
“fixed” by forcing squelch open
partly
names the squelch path as a writer
partly
hazard flagged, “unlikely”
absentabsentabsentraised
same call chain, independently
absent
ownership named in general
raised
F1, reproduced on the host
absent
Stale clear-call state releases a new call’s first framesabsentabsentabsentabsentabsentraised
F8, probe
absentabsentabsentabsentabsentabsentabsentabsentpartly
via identity, not the 1,120 samples
absent
No frequency tracking; ad-hoc timing loop gainspartly
a code comment calls the DC estimate biased
absentabsentabsentabsentabsentabsentabsentabsentraised
§4, new
absent
reads the loop as sound
absentpartly
the ±1% clamp only
partly
asks for a timing/AFC loop
absentraised
DC fit once per window, with the fix
Unknown talkgroup opens audio (fail-open gating)absentabsentabsentabsentabsentabsentabsentabsentabsentabsentabsentabsentabsentabsentraised
F6, new and reproduced
absent

Project docs: README.md, CAPABILITY-REPORT.md, STAGE3-BRINGUP.md, STAGE4-TX-FEASIBILITY.md, analysis/stock-p25/REPORT.md, analysis/capture-milestone-REPORT.md, dm1701-p25-demo/README.md and source comments. This column is from a run in a different harness; it is here for comparison, not for ranking.

Its plan

What to keep, change and add

A ten-minute bench test first, then a fork: use the chip’s own demodulator, or tap the discriminator. Both branches carry the same vocoder, scheduling and memory work, which it treats as mandatory rather than optional.

Keep

Worth doing as written

  • Step 0, before any code: capture a keyed carrier with no microphone sound, then microphone taps with no carrier. The expected result — mic yes, carrier no — settles the whole question.
  • Route A: put the C6000 in one-layer continuous mode, read 36 bytes per 30 ms frame from RX RAM and feed the dibits straight to the existing parser, with a sync-spacing test that fails the route cleanly if the mode is not gap-free.
  • Route B: the discriminator tap to an ADC, with the DMR filter profile and 12.5 kHz loaded while monitoring.
  • A cosf-free vocoder — phase accumulator or interpolated table — and a decode task with a ring of at least one LDU.
  • The RAM overlay: reuse ambebuffer_encode, satelliteDataNative and NMEARecordingBuffer while the monitor owns the radio.
Change

Would cause new problems

  • Fix the capture export before Step 0 depends on it: the diagnostic it plans to use stages the wrong halfwords.
  • “Roughly half the frames play” on Motorola systems: the defect is real, the fraction is modelled, not measured.
Add

Missing from its plan

  • The instrument defects it never reached: the parser’s completeness check, the starvation counter, the squelch conflict and the health gate.

The one-layer path, and what it would take

Manual Table 5.6 (verified line by line), with the questions that decide whether it can carry P25.

RegisterValueManual description
0x01xxxx 0000IF receive mode
0x07 0x08 0x090x0B 0xD9 0x5424-bit IF word, 455 kHz by default (OpenGD77 boots at 450 kHz)
0x100x02One-layer mode, continuous receive; bit 5 for slot receive
0x400x40Receive enable, test mode
0x410x41Receive test enable
  • Output: 36 demodulated bytes per frame in RX RAM from 0x30, frame type in 0x51, system interrupt bit 0 when ready.
  • Rate fits exactly: 36 bytes every 30 ms is 9,600 bit/s, which is 4,800 symbols per second. Contiguous frames would lose nothing.
  • Open: Figure 5.15 is DMR burst geometry, so the mode may only emit frames after DMR sync. That’s the first thing to test.
  • Also needed: the AT1846S DMR register set, since the demodulator is fed by the AT1846S. And the vocoder still has to run in real time.
Phase 2

The only plan that starts from the physics

Its Phase 2 section is the only one to propose a concrete receive path rather than a list of missing blocks: a discriminator tap sampled at 48 kHz, with integrate-and-dump at 6,000 baud turning H-DQPSK’s ±π/4 and ±3π/4 phase steps into four levels that reuse the existing slicer — explicitly for strong, non-simulcast signals only. Then the MAC layer: superframe and ISCH synchronisation, 4V/2V voice bursts, SACCH and FACCH, and descrambling seeded from WACN, System ID and NAC, which it correctly says must come from the Phase 1 control channel.

It also makes the trunking change concrete — accept the TDMA identifier the follower currently invalidates, then map channel to carrier and slot — and it is honest about the vocoder: mbelib’s half-rate decoder is in the tree and carries the same cosf cost, while the radio’s licensed DMR codec is fast and already wired in, so re-interleaving Phase 2 frames into the order it expects is worth evaluating and is unverified. Its budget verdict is that none of this is realistic at 72 MHz with the current RAM.

Against the clean-room audits

The first to find all four, and the only one that measured them

Sixteen models have now reviewed this repository. The fourteen before this one all missed the vocoder CPU wall; this one profiled it in an emulator and tied it to the parser reset that ends the call. It also found both halves of the analog chain, the sample source and the frame-clock rule. GPT-6 Astra remains the best of the ranked audits and the one that found what this review skipped: the instruments.

GPT-6 Astra clean-room review, graded B (89) →

Scores by dimension

Same rubric and weights. Hover or focus a bar for the score.

  • GPT-6 Astra
  • GPT-5.6 Sol
  • HY4 Preview
  • Claude Opus 5
0255075100Score out of 100Accuracy & evidence92918594Coverage of decode problems84705686Root cause & prioritisation88848096Fix plan & acceptance gates92908493Originality & attribution90848295Clarity & calibration86868892Weighted total89847792
Method and limits

How this was checked

Disclosure: this review was written by a model from the same maker as the auditor that graded it. The rubric, the weights and the answer key were fixed before this run and are the same ones used for every audit on this task; every claim here was checked at its cited line in the firmware, the drivers or the manual, and both of the experiments the review ran were rebuilt and rerun during grading. The evidence is listed so a reader can check it rather than take the grade on trust.

I read CLAUDEOPUS5_REVIEW.md in full and verified its citations at source, including the three lines no earlier audit had found. I copied its emu_profile.py and c4fm_channel.c into a clean scratch copy of the baseline and ran both: the instruction counts reproduce exactly and every row of the filter sweep reproduces. The de-emphasis and CPU figures I compare against come from the model and emulator used for the Grok 4.6 audit.

The run itself: one model, 118 steps, no subagents — the subagent tool was not in its toolset — no skills, plugins or MCP servers, no web tools, and nothing read outside its copy of the repository, which I checked against the session transcript. It ran through the Claude Code CLI rather than the bench profile, which is why the page is not ranked.

  • Filter responses are modelled, not measured on the radio.
  • CPU figures are emulator instruction counts, which are a lower bound on cycles.
  • Its vocoder multiple is from the demo build; the answer key’s 11–16× is from the live monitor build.
  • No firmware was flashed, and no radio was touched.
python3 analysis/review/emu_profile.py          # in a clean copy of the baseline
# mbe_processImbe7200x4400Frame: mean 9,436,825  max 13,757,387  -> 655% of 72 MHz

bash analysis/review/run_c4fm_review.sh
# hpf=300 -> SER 42.5% · deemph+3kHz LPF -> 48.9% · flat -> 0%

Scripts: deemph_check.py, c4fm_sim.py and metrics.json. They need numpy and gcc.