Session story · 2026-08-29
Session: 2026-08-29, 05:23 → 08:45 (one sitting, about twenty bench runs) · Outcome: the FFT works natively, −20.02 dBV against APWIN's −19.9905, from a self-contained T69 blob.
The FFT decode had been solved offline two days earlier. T67 had been written to answer the open questions on the bench. Chris had run it overnight.
The working belief was: the decode is the hard part; once it is right, a replayed setup blob plus the decoded writes will give a working spectrum.
Chris opened with: "ok first - i ran T67. the pcap is called T67 and is in docs. the csv files are as you named them and they are also in docs. have a look at those and we will do the power testing later"
Can the app capture an FFT natively and read the same number APWIN does?
T67: a clean answer from a lossy capture. The CSVs alone showed the range question: every analyzer range offset from the T65 constant by an exact integer multiple of 6.020599913 dB, residual 0.00000 dB. The pcap had lost data (five multi-second gaps, four passes missing), but each surviving cluster matched its CSV after a constant offset. A pass meant for autorange caught the analyzer still on the previous fixed range, which showed that the offset follows the range in force, not the one commanded.
T68: markers instead of pauses. "how about doing T67 again but either you split it or put in pauses or slow things down - something like that." APWIN BASIC has no Sleep. Claude reshaped the procedure: bare property writes with no readbacks, each preceded by a generator-frequency marker. Every property change came out as exactly one command. N, window and range were all solved, and the unified formula decoded T68's sweeps to 0.000009 dB.
Implementation, then noise. Claude cut a 40,474-packet setup blob from T68 and wrote acquireSpectrum. The first bench run read −160 to −167 dBV, and moving the generator 20 dB moved the reading 4 dB. The range registers read 0/1/2: their own indices.
Chris's data point. "one other data point, these captures took a few minutes each. the APWIN captures took seconds. I don't know if that is related to the issue" It found a bug: _transact defaults to a 50 ms sleep, and seven transactions × 512 points × 50 ms is 179 seconds. The FFT path now passes an explicit 2 ms. Claude said it was almost certainly unrelated to the noise, and it was.
The register file echoes its own address. A state diff against T68 showed 19/19 registers wrong: EB 02 1C returned 1C 1D. During init the same register had read 31945. The FFT blob was breaking the register interface.
A bisect, and three broken detectors. The bisect said "broke at 36500", inside a 4306-write serial bit-bang on 8A 01 08. Claude built a story on it: a pacing limit, then a shift-out phase. Pacing at 60 µs changed nothing. Skipping the shift-out "fixed" it, until Claude saw the check read 1C 1C and passed it, because the detector only matched 1C 1D. A fine bisect then passed 1D 1D as healthy. Each detector had been written without being run against known-bad data. Claude wrote a differential detector, validated it offline against APWIN's healthy values and all three broken patterns, and only then put it back on the instrument.
"Park it" was the wrong advice. Claude had recommended parking the FFT and switching to the Power & THD meter. Chris: "i'm really interested in getting more info out of FFT. I don't know why it would be different if i parked it or had you work on it now. Can you tell me?" Claude agreed there was no mechanism by which waiting would help. The real problem was spending each bench run on one hypothesis whose measurement had not been checked.
The true break, and why. With the validated detector the break was at packet 31750, before the bit-bang. It was a second serial chain, 8A 01 20. Our own apib_init.bin already loads both chains (8720 and 11626 writes). The T68 blob loads them again, as deltas from APWIN's mid-session state. Shifting is relative, so replaying one session's chain traffic onto another session's chain state cannot be correct. The THD blobs do the same thing and survive, which Claude called provenance luck.
Stripping the chains. Chris asked whether breaking the capture-after-APWIN house rule would help. Claude said probably not, and proposed stripping both chains instead. The interface survived. Then the harness failed twice more: the acquisition never ran because a guard checked _sweepActive, and a level check passed +302.97 dBV as "hears the tone". Claude called that the fourth instrumentation bug in the stretch. All-ones registers meant the analyzer was simply unconfigured.
T69, shaped by Chris's constraints. "i would rather run t69. is it complete?" Claude added phase markers, a pinned range, two acquisitions, and a step to record APWIN's own reading. Then Chris reshaped the plan in four messages:
apib_fft_full.bin, and apib_init.bin stayed untouched.Plan A worked. T69 held APWIN's init and both DSP program waves (974 0xEF packets, twice T68's).
It works. Replayed in full, chains included, the interface survived and the tone appeared. A first run read +2.54 dBV because Claude's code overwrote the blob's correct range code 3 with a computed 6, an 18.06 dB error. After that fix: −20.02 dBV @ 994.8 Hz, against APWIN's −19.9905 @ 994.73 Hz.
On-bin. In the app, −20 and 0 dBV read −20.55 and −0.52 at 1000 Hz, a 20.03 dB step across three range codes. Claude traced the half-dB to scalloping: 1000/64 is bin 15.625. At 1024 Hz the runs read −20.01, −20.04 and +0.02 dBV, and N=4096 dropped the floor about 10 dB with the peak unchanged.
One more claim, made too fast. Comparing the FFT with the analog input check lines, Claude concluded the analog level path reads about 0.3 dB high and would put about 7% into wattage. It went into memory before the break. (It was retracted at the first Power & THD test that afternoon.)
| Believed | Then | What overturned it |
|---|---|---|
| the decode is the hard part | the instrument state is | T68 blob broke the register interface |
| the bit-bang at 36500 breaks it | detector artifact; break is at 31750 | differential detector, validated offline |
| pacing is the cause | it is not | 60 µs paced replay still broke |
| the T68 blob can be fixed by trimming | chain loads are relative | strip test: interface survives, analyzer unconfigured |
| capture after APWIN, always | Plan A must capture first | APWIN cannot survive a power cycle |
| analog level path reads 0.3 dB high | runs were in different range states | Test 1, later the same day |
8A 01 00 <code>, readable at EB 01 00.docs/T67_FFT_P2_range_N_window_v1.apb, docs/T68_FFT_config_property_map_v1.apb, docs/T69_FFT_coldstart_to_fft_v1.apb, _v2.apbdocs/T67, T68, T69 pcaps and CSVs; screenshots docs/fft1–fft9apib_fft_setup.bin (from T68), apib_fft_full.bin (from T69, 122,703 packets)build_fft_setup_blob.py; diagnostic log /tmp/cascade_fft_diag.logdocs/protocol_fft_readback.md, docs/bench_checklist_T67_and_power_meter.mdNative FFT within 0.03 dB of APWIN, THD blobs byte-identical to HEAD. Open: the Power & THD meter had still never run on hardware, and the "0.3 dB high" claim was sitting in memory unverified.