Reconstructed chapter · 2026-06
Reconstructed chapter: 2026-06-13 → 2026-06-24 · Sources: 18 parent commits, 15 app commits, 22 memory-file changes · No transcript survives; this is rebuilt from commits and memory diffs.
There are no commits from 2026-06-02 to 06-15. Memory committed later records one early-June fix (2026-06-10): _decodeTHD read eb08[1], always 0x00, instead of eb08[0].
By 2026-06-13 the app could sweep THD at 0 dBV. It could not at −20 dBV. The app memory of that date says every −20 dBV point decoded "to nonsense positive THD values". A bootstrap blob had been tried that day. A blob here is a recorded run of APWIN's USB packets, replayed to the instrument verbatim; this one was 6766 packets cut from thd-sweep-30pts-2.pcapng. It did not help.
THD was decoded with a per-range table: the low byte of EB 1C was a "gain range selector", each range with its own slope and offset, two of them fitted from a single point.
Can the documented command model replace byte-level reverse engineering? And, as a test of it, why does THD fail at −20 dBV?
The pivot, 2026-06-13. project_scpi_pivot.md (dated that day, committed in 8c735c2) says blob replay "has hit fundamental limits": Cascade matched APWIN's bytes but not its "host-side stateful behavior", and each measurement type would be "a months-long expedition". The new API was two AP manuals: the GPIB Programmer's Reference (docs/8211-0046, 438 pages) and the Cascade APWIN BASIC reference (746 pages). APWIN BASIC is AP's scripting language, where a setting is a property such as AP.Gen.Freq = 1000. The memory stops with scripts T1–T5 written and not yet run.
The method works, 2026-06-16. A pcap is a USB packet capture taken while APWIN runs. The method: run a script that sets one property, capture it, subtract a baseline. T1–T5 carry file times 18:40–18:52. APWIN's idle polling used about 41 unique OUT payloads across 17,739 packets in T1, so AP.Gen.Freq = 1000 came down to 2 new payloads, 8a 02 21 b0 b1 and 8a 01 23 80. The captures ran through T21 by 22:20. Commit 8c735c2 (23:20) lists what they gave:
upper14 = round(16383 - base_hz × 0.8), exact at 12 frequencies from 100 Hz to 10 kHz, replacing a table with interpolation;0x18: 0x80 Amplitude, 0xF8 THD+N;8a 01 07 05;8a 01 24/25 90 and e0;ed 40 …, for AP.Gen.ChAAmpl, with the formula not yet fitted.Validating writes that cannot be read back. Register 0x18 always read 0xE1. App commit 731f318 added a TEST PROTOCOL button that snapshots neighbouring registers instead; 0x0F landed on 0x33 for Amplitude and 0xbb for THD+N. The lesson became feedback_validate_via_adjacent_regs.md.
One memory file, two answers. protocol_property_to_apib.md calls the frequency value "a totally different encoding" from the older one, then, lower down, says it "consolidates" with it. Both went in together in 8c735c2; the record doesn't say which came first.
The level read, 2026-06-17 afternoon. T23 stepped the generator from −20 to +20 dBV in 10 dB steps; 8b9fb7a says APWIN read "matching commanded levels to ~30 mdB". But no polled stream (01 9e, 01 9f, 01 87) tracked the level. The memory guessed APWIN computes dBV on the PC, and chose to keep the app's own calibrated level read rather than rebuild FuncRdg.
T30 corrects T4. T30, a full THD run at 1 kHz / −20 dBV, read −88.0246 dB in APWIN. 751d406 (19:05) found the THD+N byte was 0xC8, not T4's 0xF8: T30 "is authoritative because it actually completed a successful THD measurement." The switch was 13 commands plus a 0x1a write mirroring the generator frequency, and the 0x05 trigger was not used.
Five rebuilds from scratch, 19:05 to 20:09. App commits 4c2c63e through 6bd34bd tried to get a THD reading from the documented bytes alone: pause the background poll (v2), prime the DSP (v3), poll during integration (v4), add the 8a 01 07 06/07 trigger pair (v5). From v3 on, every read was 01 87 00 00 01, a zero. a7b1d16 closed it: the mode byte "is not self-sufficient", and the DSP's filter coefficients load in a way that "isn't a clear byte sequence on USB." Next: diagnose the working path instead.
Three reversals in two hours, 20:15 to 22:40. A wire log of IN and OUT packets (6dab7ad, cf460da) showed Ch A's harmonic reads jittering at −20 dBV while Ch B's descended. 69e0357 (20:57) used Ch B's THD for Ch A; c6c6f95 reverted it twelve minutes later, since both came from the same EB08/EB1C reads. 0174acc (21:47) computed THD+N from the harmonic reads instead, saying EB08 did not "correlate with APWIN's reported THD". 69213b8 (22:36) reverted that: it read +6 to +9 dB everywhere, while with a distorting device under test (DUT) the old decode gave −20 to −36 dB, in "the right ballpark" next to REW, a PC measurement program. 2ca08a0 then doubled the harmonic cycles (10 → 20) so REW had longer per point.
Ground truth, 2026-06-18. The calibration procedure, docs/THD_calibration_procedure.md, opens by calling the old decode "~40-60 dB offset from REW/APWIN". That contradicts 69213b8's ballpark verdict the night before; the record doesn't reconcile them. APWIN ran T40v2 (−20 dBV) and T41 (0 dBV) at seven frequencies, each script logging its own readings (23:12 to 23:39). The first runs read about −70 and −75 dB; later no-DUT runs read about −88 and −101 dB.
The formula, 2026-06-19 00:43. App commit 9e07514 replaced the per-range table with one line:
THD_dB = 20*log10(EB08_u16 / (1024 * EB1C_u16))
Both registers are little-endian 16-bit words. EB08 is the residual and EB1C is the fundamental, and the 1024 is a fixed-point headroom shift. The commit says the old table "was an artifact of treating one byte at a time; the whole word is the fundamental amplitude". It was validated from −70 dB down to −101 dB with docs/verify_thd_formula.py. The floor-mystery memory says it matched APWIN "to 0.001 dB" on the −88 and −101 dB runs.
A gap the formula left. The MEMORY index says "CONFIRMED 2026-06-18". But the topic file protocol_thd_measurement.md, committed in the same 1c0b0e6 whose message says it was updated, still holds the per-range table and has no 1024 in it.
A new open question, 2026-06-19 to 06-24. Now Cascade read about 12 dB cleaner than APWIN (−101 vs −88 dB at −20 dBV, −113 vs −101 dB at 0 dBV). Overrides of 0x3A and 0x3D, verbatim from the T40v2 pcap, changed nothing mid-band and were reverted. 23d9452 recorded why the diff that found them misled: the bootstrap blob ran last, so each register's "last value" was its 20 kHz one. T43–T49 followed that evening; their memory, committed 07-03, says the session "hung mid-analysis".
AP.Gen.Freq into 2 packets; the documented API became a tool.a7b1d16: five failed rebuilds were stopped and written down as a limit, not retried a sixth time.EB08 flip-flop.| Believed | Then | What overturned it |
|---|---|---|
| more blob replay will fix −20 dBV (to 06-13) | the byte approach has "hit fundamental limits" | the 6766-packet bootstrap blob failed (06-13) |
| frequency needs a table + interpolation | closed form, exact at 12 points | T-series captures (06-16, 8c735c2) |
| a register write is checked by reading it back | read a neighbour that changes | 0x18 always read 0xE1 (06-16) |
THD+N mode byte is 0xF8 | it is 0xC8 | T30, which completed a −88.0246 dB reading (06-17, 751d406) |
| the mode byte plus a trigger yields a THD reading | it needs DSP coefficients too | v1–v5 all read zero (06-17, a7b1d16) |
| Ch A's THD is the broken one at −20 dBV | both channels decode the same reads | the wire log (06-17, c6c6f95) |
EB08 doesn't track APWIN's THD | the old decode is in the ballpark | +6 to +9 dB from the harmonic method; a DUT run (06-17, 69213b8) |
EB1C's low byte is a range selector | the whole word is the fundamental | APWIN logs at two levels (06-19, 9e07514) |
| one diverging register explains the 12 dB | the last-value diff was misleading | 0x3A and 0x3D overrides moved nothing mid-band (06-24, 23d9452) |
0x0D and 0x0F.EB08 over 1024 × EB1C. It is not computed on the PC.feedback_check_before_asking.md, 06-16); the memory says this had been pointed out more than once.8c735c2, 8b9fb7a, 751d406, a7b1d16, 1c0b0e6, 23d9452, plus 12 submodule bumps. 2026-06-17 alone had 14 parent and 12 app commits.origin/cascade-main): 731f318, 4c2c63e–6bd34bd (THD tests v1–v5), 6dab7ad, cf460da, 69e0357, c6c6f95, 0174acc, 69213b8, 2ca08a0, 9e07514.docs/memory/): project_scpi_pivot.md, protocol_property_to_apib.md, protocol_frequency.md, protocol_thd_measurement.md, project_thd_floor_mystery.md, project_thd_chb_duplicate_buffer.md, feedback_setup_diff_lastval.md.docs/T1.pcapng–T21.pcapng (06-16), T22/T23/T30 (06-17), T40_…_v2.pcapng, T41_…pcapng (06-18), T43–T49 (06-24).docs/T*_*.apb, docs/thd_cal_*.log, docs/THD_calibration_procedure.md, docs/verify_thd_formula.py, verify_freq_formula.py.e4544f63 (06-13–06-17), 10b5aeef (06-19–06-24), b7e2247d (06-24, committed 07-03).THD decode was one formula, checked against APWIN's own numbers. The documented-API method was proven for generator and analyzer settings. Open at 06-24: Cascade reading about 12 dB cleaner than APWIN, with a stale-DSP-buffer test as the next step; Ch B decoding from Ch A's buffer; the amplitude opcode's formula; the frequency section's two contradictory paragraphs; and the THD topic file still describing the old table. The MEMORY index now records the 12 dB question as resolved on 2026-07-18, with no Cascade bug: the −101 dB reference came only from the T41 calibration capture.