Files
HaloKeymind/docs/sf10_rollback_failure_validation.md
T
mikecarper 091c8d8324 Preserve radio lab experiments and reuse unchanged SX1262 modulation
Skip redundant modulation writes during owned fast RX retunes when the last
acknowledged SF/BW/CR/LDRO tuple matches. Invalidate that cache after ordinary
setters, failed writes, and lifecycle changes.

Include the remaining scan, preamble, settling, and memory-soak experiments,
their collectors, validation notes, and original capture records. Preserve
capture bytes across checkouts and keep private soak credentials local.

Run lab collector and compiled contract tests in CI. Update the expectation,
profile mapping, and result-buffer tests for the extended lab tools, and make
the private WiFi override header optional for ordinary soak diagnostics.

Validation: 145 host tests passed from the staged source snapshot. Clean
heltec_v4_repeater and Xiao_S3_WIO_companion_radio_usb builds passed their
RAM/flash gates. All 229 staged capture files retain their original bytes;
all 75 local documentation links resolve in the clean snapshot.
2026-09-14 22:34:17 -07:00

233 lines
13 KiB
Markdown

# SF10/125 rollback and wider-spacing failure isolation
2026-09-14. **No strict four-channel 400/400 pass. Reverting the switching
optimizations did not remove failures. Off-channel reception also occurs with
a fixed, fully initialized receiver, without any channel switching.** A traced
timeout shows an off-channel preamble holding the scanner away from the
intended channel until after its transmit preamble. Wider spacing did not
eliminate either wrong-channel delivery or actual missing packets.
This isolates a concrete scanner failure mechanism, not the underlying
physical RF cause. Commanded frequency words are observed, but actual emitted
spectrum, antenna-port power, image responses and overload have not been
independently measured. Do not claim front-end saturation or a specific spur
as proven. Do not treat these first-failure-censored samples as unbiased PER.
## Fixture and requested controls
- RX: XIAO ESP32-S3 / Wio SX1262, COM31, MAC 28:84:85:B4:09:80; normal RX gain
register 0x94 throughout (boosted gain off).
- TX: XIAO nRF52840 / Wio SX1262, COM15, serial B35E71C1C3726CE7.
- SF10, 125 kHz BW, CR4/5, 32-symbol preamble, 16-byte sequenced CHS1 probes,
-9 dBm chip request, four channels, 5.1-symbol post-settle listening visits.
- Original centers: 909.5, 909.75, 910.0, 910.25 MHz. User-requested wider
centers: 909.5, 910.5, 911.5, 912.5 MHz (1 MHz spacing).
- Existing fixed-channel positive control was 100/100; see
`sf10_nrf52_double_write_settling_validation.md`. No failed packets retried.
- Unrelated RAK COM29 was not opened. New nRF52 upload was application-only,
using normal SoftDevice ID 0x0123 validation; no bootloader/SoftDevice erase.
Rollback modes are runtime HIL controls on the same current source, not
historical firmware checkouts. No production default or receive safety guard
was changed during this investigation. Tests preserve ownership, packet-in-
progress, BUSY and command-result checks. Existing unrelated working-tree
changes were left intact. No commit or push was requested this turn.
## Maximum practical settling control
At SF10/125, a symbol is 8,192 us, the programmed preamble is 262,144 us,
and each 5.1-symbol post-settle visit is 41,780 us. For each rollback mode:
1. Measure six seconds of RX-only switching without added delay.
2. Choose the largest 100 us delay step satisfying
`4 * (41780 + observed_max_switch_us + added_delay_us + 500) <= 262144`.
3. Measure another six seconds; require at least eight full idle cycles and
maximum observed idle cycle below the preamble before allowing TX.
The 500 us per-hop reserve covers some observed scheduling variation, not
all possible jitter. RX is active during added settling. Near-ceiling cycle
timing is not itself a guarantee of acquisition for every arrival phase,
especially when an IRQ causes an extended packet hold. Idle-cycle statistics
exclude guard/packet-held visits; those are measured separately.
## 250 kHz spacing: eleven rollback cases
Each case stopped on its **first probe**, TX channel 3, with a real timeout.
No strict 400/400 pass, TX fault, RX mode/cache failure, or hardware error.
The first nine cases used the checkout's existing 1,600 us TCXO setting;
the final two explicitly restored 6,000 us. Production timing was not edited.
| Case | Added delay (us) | Zero-delay mean switch (us) | Max settled idle cycle (us) |
| --- | ---: | ---: | ---: |
| Fast, cached modulation, double frequency write | 22500 | 535.88 | 259297 |
| Always write modulation | 22500 | 625.50 | 259682 |
| Individual SF/BW/CR writes | 22200 | 840.97 | 259346 |
| Full normal RX restart | 22200 | 896.89 | 259577 |
| Full RX + individual modulation | 22000 | 1113.95 | 259623 |
| Above + byte-wise SPI, warm XOSC | 21800 | 1215.79 | 259220 |
| Above + RC standby, 1.6 ms TCXO | 20100 | 2942.50 | 259339 |
| Fast, cached modulation, single frequency write | 22600 | 450.25 | 259341 |
| RC/full RX/individual/byte-wise, single write, 1.6 ms TCXO | 20200 | 2837.50 | 259329 |
| RC/full RX/individual/byte-wise, double write, 6 ms TCXO | 15800 | 7293.25 | 259550 |
| RC/full RX/individual/byte-wise, single write, 6 ms TCXO | 15800 | 7190.31 | 259139 |
Actual frequency/modulation command counts and fast-RX counts verified each
policy. Slower modes received less added delay to preserve the total sweep
budget. These are eleven probes with eleven timeouts, **not eleven complete
400-packet runs**. Packet arrival pauses use the same seeded scheduling
method but not hardware-synchronized identical RF phases across modes.
## Fixed-frequency controls: off-channel responses without switching
Each pair received three independent probes after full normal initialization.
Positive controls were interleaved. The receiver remained at one frequency:
zero observed frequency commands after setup, no hopping or cache shortcut.
Payload success requires the exact expected sequence/channel/body, length 16
and clean CRC. It does not require RX center to equal TX center in this
explicit offset diagnostic. Offset below means programmed RX minus TX.
| Spacing | RX-TX offset (kHz) | Probes | Intact expected payloads | Preamble IRQ seen |
| --- | ---: | ---: | ---: | ---: |
| 250 kHz | 0 | 6 | 6 | 6 |
| 250 kHz | -250 | 3 | 2 | 3 |
| 250 kHz | -500 | 3 | 0 | 3 |
| 250 kHz | -750 | 3 | 0 | 3 |
| 250 kHz | +250 | 3 | 0 | 2 |
| 1 MHz | 0 | 6 | 6 | 6 |
| 1 MHz | -1000 | 3 | 2 | 3 |
| 1 MHz | -2000 | 3 | 0 | 0 |
| 1 MHz | -3000 | 3 | 3 | 3 |
| 1 MHz | +1000 | 3 | 3 | 3 |
At 250 kHz spacing, the two valid -250 kHz-offset packets reported RSSI
-91/-90 dBm and SNR -12.8/-10.8 dB. At 1 MHz spacing, valid off-channel
packets reported RSSI -102..-109 dBm and SNR -11.5..-15.8 dB. Wider on-channel
controls were all -33 dBm, SNR +8.0..+8.8 dB. These are radio-reported values,
not calibrated measurements of transmitted spurs or receiver rejection.
The non-monotonic offset pattern means that increasing spacing alone cannot
be assumed to remove this response. These fixed-frequency tests rule out a
channel-hop shortcut as a necessary condition for the observed off-channel
packets; they do not establish which physical component produces them.
## A traced real timeout at 250 kHz spacing
Fast/double-write RX, +22,500 us settling. One channel-3 probe timed out with
TX rc=0, RX mode=1 and device errors=0. The trace contains 454 events, overflow
0. Trace adds 64 us IRQ polling; use it for ordering, not an unperturbed timing
A/B comparison. Times below are relative to receiver expectation arming.
| RX time (ms) | Evidence |
| ---: | --- |
| 169.190 | Frequency command for channel 3, 910.25 MHz |
| 234.032 | Leaves channel 3 for channel 0 |
| 298.869 | Frequency command for channel 1, 909.75 MHz |
| 330.030 | PREAMBLE_DETECTED (0x0004) while on channel 1 |
| 363.632 | Requested next hop is held by receive-in-progress guard |
| 726.734 | Guard clears the lingering preamble IRQ |
| 726.826 | Frequency command for channel 2 |
| 791.654 | Finally commands intended channel 3 again |
| 814.586 | Channel-3 hop/settling completes |
TX setup was 86,914 us and its transmit call 529,297 us. Host/firmware timing
places TX RF start approximately 287..289 ms after RX arming and the end of
its programmed preamble around 550 ms. This estimate is not a synchronized
RF measurement, but the receiver's several-hundred-millisecond absence from
the intended channel is much larger than USB timestamp uncertainty.
The held channel-1 visit measured **427,395 us**, versus normal idle four-
channel cycle maximum **259,358 us**. The guard timeout is consistent with
`RadioLibWrapper::calcMaxPacketMillis`: `(32 + 8 + 4.25) * 8192 = 362496 us`,
rounded to 363 ms. `CustomSX1262::isReceiving()` starts that timer when the
guard first observes the preamble, here later than the diagnostic IRQ poll.
Thus an off-channel preamble can monopolize the scanner long enough to miss
the real channel. Do not simply remove or shorten this safety guard as a
production fix: it also protects legitimate weak or long-preamble reception.
## 1 MHz spacing: strict results versus payload delivery
Three strict rollback cases (fast double, fast single, fully rolled back
single/6 ms) each stopped at their first probe. All three actually decoded
the intact channel-3 payload while programmed for channel 2: RX 911.5 MHz,
TX 912.5 MHz, RF word at read 955777024. They are **channel mismatches, not
lost payloads**. RSSI was -102/-101/-101 dBm; SNR -11.5/-11.5/-11.2 dB.
An explicitly separate payload-delivery diagnostic then continued through
such packets while retaining strict validity in raw evidence:
| Mode | Added delay | Delivered before stop | Strict same-channel | Stop | Mean switch | Max idle cycle |
| --- | ---: | ---: | ---: | --- | ---: | ---: |
| Fast, cached, double write | 22.5 ms | 3 of 4 probes | 0 | TX channel 0 timed out | 557.721 us | 259381 us |
| RC/full RX/individual/byte-wise, single write, 6 ms TCXO | 15.8 ms | 2 of 3 probes | 0 | TX channel 1 timed out | 7310.510 us | 259643 us |
Every delivered packet in these two runs arrived one programmed channel below
its transmitter. Both runs had zero mode/cache/RX/device errors and actual
switch-policy counts matched. Neither is a 400/400 pass, even under the relaxed
delivery-only criterion. The true timeout in the fully rolled-back path also
shows the problem is not limited to the accelerated RX restart or 0x8B cache.
No traces were enabled for these two runs, so do not assign their individual
timeouts the exact IRQ timeline from the earlier traced probe.
## Changes and verification
- HIL-only rollback, explicit TCXO timing, selectable 250/1000 kHz spacing,
frequency-command trace events and fixed-frequency offset controls.
- Separate opt-in payload-delivery test; strict wire validity remains intact.
Host/device counters distinguish strict from off-channel deliveries;
settling sweeps reject delivery-only captures, including reused evidence.
- 16 native profile tests and 46 HIL host tests passed. Receiver and nRF52
builds/uploads succeeded with normal verification. Receiver reported
94,844 bytes RAM / 366,777 bytes flash; nRF52 18,220 / 95,572 bytes.
- `git diff --check` passed; unrelated pre-existing CRLF warnings only.
- Temporary PlatformIO overlay removed. Controllers stopped, both boards
rebooted idle with HIL firmware retained. No autonomous TX or restoration.
Next useful isolation is the physical off-channel response (controlled RF
attenuation or reversed radio roles), followed by safe acquisition/hold-policy
experiments. More settling delay alone does not address the demonstrated
false-hold mechanism. No claim of an identified silicon defect or verified
production fix is made here.
## Evidence provenance (SHA-256)
Raw JSON files below are under `tools/hil/`; prior captures were not rewritten.
Matrix manifests include per-case paths and timing controls. All listed new
experiments completed under their stated stopping policies without fixture or
cleanup errors. Completed first-failure runs still have incomplete 400-packet
levels by design.
| Capture | SHA-256 |
| --- | --- |
| profile_switch_sf10_rollback_results.json | `e87c504b3c89fbafe90c18fcd0ab37df34a6aeb51d95b5c69f58d60f70de185a` |
| profile_stationary_sf10_offset_results.json | `d3d87315513a70fc3f00cc7f17369043328f53ee3028d499087b21e767370ab7` |
| profile_switch_sf10_rollback_fast_trace_results.json | `8ac9db50f0f06eb7511d7d233957119618df62afdc7112dd83eb68d292fcd725` |
| profile_switch_sf10_rollback_6ms_results.json | `74e7bfeb23bc569a5786e2322b5e3b1753cba9f628e2969bf4f5c4296ca5817a` |
| profile_switch_sf10_wide_rollback_results.json | `d79096fbe3e15e0375457b487fd40be3418789e798a978efb79d8298c880eea2` |
| profile_switch_sf10_wide_payload_results.json | `1316a2c2f32a8c0773fc56587c4220ee8f6f6cdf4ab9b87e00fd361fc9395257` |
| profile_switch_sf10_wide_legacy_payload_results.json | `fcf2123270634e2f27800787e20b9b8b7ae63bcfb03e17d26a4a4f6f4e23d32f` |
| profile_stationary_sf10_wide_offset_results.json | `4c494a59fa793d274b0366185cfe3230b6c9fce7a57dfaa61f9f29c36465bbd3` |
RX firmware images used, in chronological order:
| Stage | firmware.bin SHA-256 |
| --- | --- |
| Initial nine-mode rollback | `5f8dbf9c4584e0de94a7aebda5a20e961b644a880ecb6a34ab5871621958696f` |
| Fixed 250 kHz offsets and traced timeout | `cd839caa33c869dd448cc55e9c8b959e39d105f80a2ac4a8029393698b993fd3` |
| Explicit 6 ms TCXO rollback | `0ba044035c4986749c9ad4f50c702dc8c57e8e0fecf9d46fc11515cad5678166` |
| Wider strict rollback | `5fc919c80438635b4a96021b39fae2ada9e767508d7bb73854e4e108f0d4920f` |
| Wider delivery diagnostics and fixed offsets; final installed | `7b313a0f8f1eb04f6fc12197af5e9481ad43154b981ab7627e50167d2a97d8c2` |
The 250 kHz tests used the prior nRF52 TX ZIP hash
`7466625ef5e7bcc8f7dd16db2b843db29b4312a33b0d32bc0b1e1193d6aee03a`.
All wider tests used the new selectable-spacing application:
- TX ZIP: `c474b7598a2c0b6c529ead897f3fc7aab8caf4b7581c73d7acac6ffae081377d`
- TX HEX: `b400fc53c496f4a956f88ccee5cda83b5f6679b8e2b59bfcb4b8155ea9e1e5e9`
Build artifacts are generated and may be replaced by a subsequent build;
hashes identify the images used, not a promise that every historical binary
remains in `.pio/build`. Source starts from keymindCascade commit
`0e5955f889788a4317863e731b38ffe09e3d5441` with the existing dirty changes and
these uncommitted HIL additions; this is not a clean-release comparison.