# Discovery Notes

This file records hands-on observations separately from manual-derived facts.
Treat these as bench notes: useful and current, but still worth rechecking with
photos, continuity tests, and instrument captures.

## 2026-05-13 - RCP-TX7 10-Pin Power and Cable

Observed on the RCP-TX7 10-pin remote connector/cable during restoration work:

- Pin 9 confirmed as ground / DC return.
- Pin 10 confirmed as power input.
- Cable color for pin 9 / ground: brown.
- Cable color for pin 10 / +12 V power: brown-white.
- Cable colors for pins 1-8 have been continuity-mapped; see the working cable
  map below.
- Yellow and yellow-white conductors are present in the cable but did not map to
  connector pins during continuity testing.
- Multimeter reading from pin 9 ground to pin 4 serial data: about -9 V.
- Multimeter reading from pin 9 ground to pin 7 serial data: about +0.037 V.
- Pins 4 and 7 were the only serial-related combinations that produced a
  meaningful multimeter result during this check.
- With power present on pins 9 and 10, the panel shows a green `PANEL ACTIVE`
  light.
- The inside of the 10-pin cable contains 12 wires total.
- Three of those wire groups are twisted pairs.

Immediate implications:

- The bench result agrees with the RCP-TX7 and CCU-TX7 manual pinout for pins 9
  and 10.
- The 12-conductor cable construction suggests not every conductor maps
  one-to-one to the 10 connector pins; shielding/drain, duplicated grounds, or
  paired signal returns may be present.
- The three twisted pairs are likely important candidates for serial data,
  composite video, and/or power/ground pairing, but this should be confirmed by
  continuity testing rather than color or twist assumptions.
- Pin 4 measuring around -9 V relative to pin 9 strongly suggests true RS-232
  level idle on at least the RCP-to-CCU/camera data line.
- Pin 7 near 0 V may be inactive/floating until a CCU or camera drives the
  return data line.

### Working Cable Map

This table combines the manual-derived pin purpose with hands-on color mapping.
Rows marked `confirmed` have been checked on the current cable/panel under test.

| Pin | Purpose | Cable color | Status | Notes |
| ---: | --- | --- | --- | --- |
| 1 | Spare / unused | red | confirmed | No function shown in service manual. |
| 2 | VBS / composite video X | black | confirmed | 1.0 Vp-p composite video input to RCP. |
| 3 | VBS / composite video ground | green | confirmed | Video reference/ground. |
| 4 | Serial data, RCP to CCU/camera | orange | confirmed | RS-232C-based data direction. |
| 5 | Serial/data ground | blue | confirmed | One of two serial/data grounds. |
| 6 | Serial/data ground | grey | confirmed | One of two serial/data grounds. |
| 7 | Serial data, CCU/camera to RCP | purple | confirmed | RS-232C-based data direction. |
| 8 | Spare / unused | purple-white | confirmed | No function shown in service manual. |
| 9 | DC return / ground | brown | confirmed | Confirmed as ground on current cable. |
| 10 | +12 V remote power input | brown-white | confirmed | Confirmed as power input on current cable. |

### Unmapped Cable Conductors

The cable contains two additional conductors that did not show continuity to
the 10 connector pins during the current test:

| Conductor color | Current status | Notes |
| --- | --- | --- |
| yellow | unmapped | May be shield/drain-related, spare, broken, or connected only at one end. |
| yellow-white | unmapped | May be shield/drain-related, spare, broken, or connected only at one end. |

Recheck these against connector shells, shield braid/drain, cable strain relief
hardware, and both ends of the cable if accessible.

Suggested next observations to capture:

1. Connector orientation photo showing pin numbering reference.
2. Wire color list, including which colors form each twisted pair.
3. Confirm whether yellow and yellow-white connect to shield, shell, or one end
   only.
4. Resistance between pins 5, 6, and 9 with the cable disconnected.
5. Scope idle voltage and activity on pins 4 and 7 relative to pins 5/6 and
   pin 9 while pressing panel controls and, later, while connected to a CCU or
   camera.

## Serial Capture Setup

Initial USB serial adapter wiring for passive listening:

| Adapter terminal | RCP-TX7 cable pin | Cable color | Purpose |
| --- | ---: | --- | --- |
| `GND` | 9 | brown | Shared reference / DC return |
| `RXD` | 4 | orange | Listen to RCP-to-CCU/camera serial data |

Do not connect adapter `TXD` during the first capture pass. Pin 4 measured about
-9 V relative to pin 9, so use the adapter's RS-232 side, not TTL UART mode.

Capture helper:

```powershell
python -m pip install pyserial
python scripts/serial_sniff.py --list
python scripts/serial_sniff.py --port COM3 --baud 38400 --ascii
python scripts/serial_sniff.py --port COM3 --baud 38400 --frame-size 6 --log captures/rcp-pin4.txt
```

Replace `COM3` with the adapter port shown by `--list` or Windows Device
Manager. While the script is running, press simple RCP controls and watch for
new hex bytes.

### 2026-05-13 Initial Pin 4 Capture

With the adapter in RS-232 mode, adapter `RXD` connected to RCP pin 4, and
adapter `GND` connected to pin 9, the stream produced repeating 6-byte patterns:

```text
00 00 00 00 80 DA
00 00 07 80 00 DD
```

Observed behavior:

- Frames repeat roughly every 200 ms during the sample.
- The stream sometimes appeared split as `00` followed by five bytes, which is
  likely a read-timeout/chunking artifact rather than a protocol feature.
- Button presses did not obviously correlate with a visible byte change in the
  first capture.

Current interpretation:

- This looks like a regular RCP-origin heartbeat/status transmission on pin 4,
  not random noise.
- Because only pin 4 is connected, this may be the panel repeatedly trying to
  announce itself or poll a missing CCU/camera.
- Pin 7 measured near 0 V and is probably quiet until a CCU/camera drives the
  return channel.

Next capture passes:

1. Use `--frame-size 6` to avoid misleading `1 + 5` packet splits.
2. Capture a quiet baseline for 30 seconds.
3. Capture separate files while pressing one control repeatedly, naming the
   action in the filename.
4. Later, capture pin 7 when connected to a real CCU/camera or a controlled
   test transmitter.

### 2026-05-13 Baseline vs CAM POWER Capture

Capture files:

- `captures/rcp-pin4-baseline.txt`
- `captures/rcp-pin4-cam-power.txt`
- `captures/rcp-pin4-call.txt`

Frame counts from the available logs:

| Capture | Frame | Count | Current label |
| --- | --- | ---: | --- |
| baseline | `00 00 00 00 80 DA` | 67 | idle heartbeat |
| CAM POWER | `00 00 00 00 80 DA` | 23 | idle heartbeat |
| CAM POWER | `00 00 07 80 00 DD` | 4 | CAM POWER candidate |
| CALL | `00 00 00 00 80 DA` | 17 | idle heartbeat |
| CALL | `00 00 15 80 00 CF` | 4 | CALL candidate, state/high bit set |
| CALL | `00 00 15 00 00 4F` | 4 | CALL candidate, state/high bit clear |

Current interpretation:

- The baseline capture contains only `00 00 00 00 80 DA`.
- Pressing `CAM POWER` introduces `00 00 07 80 00 DD`.
- Pressing `CALL` introduces `00 00 15 80 00 CF` and `00 00 15 00 00 4F`.
- Other tested buttons did not obviously produce unique frames while the panel
  was not connected to a CCU/camera.
- `CAM POWER` and `CALL` may be among the few controls the panel transmits even
  without a completed host/CCU session.
- The CALL frames differ by byte 4 (`80` vs `00`) and final byte (`CF` vs `4F`),
  suggesting a state bit plus checksum or complement-style trailing byte.
- Current checksum hypothesis: byte 6 is XOR checksum with seed `0x5A` over the
  first five bytes. Examples:
  - `5A xor 00 xor 00 xor 00 xor 00 xor 80 = DA`
  - `5A xor 00 xor 00 xor 07 xor 80 xor 00 = DD`
  - `5A xor 00 xor 00 xor 15 xor 80 xor 00 = CF`
  - `5A xor 00 xor 00 xor 15 xor 00 xor 00 = 4F`

Helper for future captures:

```powershell
python scripts/analyze_capture.py captures/rcp-pin4-baseline.txt captures/rcp-pin4-cam-power.txt captures/rcp-pin4-call.txt
```

## Host Response Experiments

The RCP currently appears to be in an offline heartbeat state. With no CCU/camera
response present, only `CAM POWER` and `CALL` have been observed to send unique
frames beyond the heartbeat. The next protocol step is to learn what the RCP
expects on pin 7 (`CCU/camera -> RCP`).

Important wiring for host-response tests:

| Adapter terminal | RCP-TX7 cable pin | Cable color | Purpose |
| --- | ---: | --- | --- |
| `GND` | 9 | brown | Shared reference / DC return |
| `TXD` | 7 | purple | Candidate host-to-RCP transmit line |

Suggested safety precautions:

- Use the adapter's RS-232 side, not TTL UART.
- Keep adapter `RXD` on pin 4 if possible so the RCP output is still logged.
- Add a series resistor, for example 1 k to 4.7 k, between adapter `TXD` and pin
  7 for early experiments.
- Send one candidate frame at a time or repeat at a slow cadence. Avoid brute
  forcing unknown byte ranges.
- Watch for changes in heartbeat, LCD state, panel lock state, or new frames on
  pin 4.

Frame sender:

```powershell
python scripts/serial_send_frame.py --port COM3 --dry-run
python scripts/serial_send_frame.py --port COM3 --frame "00 00 00 00 80 DA" --repeat 5 --interval 0.2
```

On Windows, a COM port is usually exclusive, so the sniffer and sender cannot
open the same adapter at the same time. Use the combined probe script when RXD
is connected to pin 4 and TXD is connected to pin 7:

```powershell
python scripts/serial_probe_response.py --port COM3 --tx-frame "00 00 00 00 80 DA" --repeat 5 --interval 0.2 --log captures/rcp-response-test.txt
```

This listens first, sends the candidate response from the same serial session,
then keeps listening for changes on pin 4.

Candidate first response:

- `00 00 00 00 80 DA` - mirror the observed heartbeat as a possible no-op/ack.

If mirroring the heartbeat changes nothing, the next low-risk approach is to
capture a real CCU/camera response rather than guessing. If no host is available,
try only checksum-valid, documented-frame-shape candidates and record every
attempt in a separate capture log.

### 2026-05-13 Heartbeat Mirror Response Result

Experiment:

- Adapter `TXD` connected to RCP pin 7.
- Sent `00 00 00 00 80 DA` on the host-to-RCP line as a mirrored heartbeat /
  possible no-op acknowledgement.
- Capture file: `captures/rcp-response-heartbeat-mirror.txt`.

Observed result:

- The RCP screen changed to `CONNECT: NOT ACT`.
- During this capture, pin 4 still transmitted only `00 00 00 00 80 DA`.
- Frame count: 59 received heartbeat frames, 10 transmitted mirrored heartbeat
  frames.
- Pin 4 heartbeat timing became more frequent during the response window, then
  returned to the slower baseline cadence afterward.

Current interpretation:

- The RCP is detecting return-channel traffic on pin 7.
- Mirroring the heartbeat is enough to move the panel out of the simple offline
  state, but it does not complete active host/CCU negotiation.
- `NOT ACT` likely means connected/not active, connected/not activated, or a
  similar state where the link is electrically/protocol-visible but no valid
  control session has been established.
- The RCP did not emit a new command/status frame on pin 4 in response to the
  mirrored heartbeat, so the next handshake step is probably not simply an echo
  of its heartbeat.

Additional checksum-valid response tests:

| Capture | TX frame | RX result on pin 4 | Screen result |
| --- | --- | --- | --- |
| `captures/rcp-response-zero-state.txt` | `00 00 00 00 00 5A` | heartbeat only | `CONNECT: NOT ACT` |
| `captures/rcp-response-state-byte4.txt` | `00 00 00 80 00 DA` | heartbeat only | `CONNECT: NOT ACT` |
| `captures/rcp-response-invalid-checksum.txt` | `00 00 00 00 80 00` | heartbeat only | `CONNECT: NOT ACT` |
| TXD connected, no transmitted bytes | RS-232 idle only | heartbeat only | no `CONNECT: NOT ACT` |
| Single-byte test | `00` | heartbeat only | no `CONNECT: NOT ACT` |
| Single-byte test | `FF` | heartbeat only | no `CONNECT: NOT ACT` |
| Short-frame test | `00 00 00` | heartbeat only | no `CONNECT: NOT ACT` |
| Frame-length test | `00 00 00 00` | heartbeat only | no `CONNECT: NOT ACT` |
| Frame-length test | `00 00 00 00 80` | heartbeat only | no `CONNECT: NOT ACT` |
| Frame-length test | `00 00 00 00 80 DA 00` | heartbeat only | `CONNECT: NOT ACT` |

Updated interpretation:

- `CONNECT: NOT ACT` is probably a link-present state, not proof of a correct
  CCU handshake.
- The RCP reacts to several checksum-valid 6-byte frames on pin 7, but continues
  sending only the pin 4 heartbeat.
- An intentionally invalid checksum frame also produced `CONNECT: NOT ACT`, so
  that display state does not prove checksum acceptance.
- The response needed to enter an active control session likely needs a specific
  status/identity/activation frame, not just a valid no-op frame shape.
- TXD connected at idle without transmitted bytes did not produce
  `CONNECT: NOT ACT`, so the display state appears to require received byte
  activity on pin 7, not merely a driven RS-232 idle level.
- Single-byte and three-byte transmissions did not produce `CONNECT: NOT ACT`,
  so the RCP is likely recognizing a minimum frame length or parser shape rather
  than arbitrary serial bytes.
- Four-byte and five-byte transmissions did not produce `CONNECT: NOT ACT`, but
  a seven-byte transmission beginning with the known six-byte heartbeat did.
  This suggests the first six bytes are enough to trigger the parser/link state,
  and the seventh byte may be ignored, buffered for a later frame, or treated as
  extra data after the recognized packet.
- None of the tested host frames have caused the RCP to emit anything on pin 4
  except the heartbeat.

Command-field response tests, using frame shape `00 00 CMD 00 80 CHECKSUM`:

| Capture | TX frame | Checksum | Screen result | Notes |
| --- | --- | --- | --- | --- |
| `captures/rcp-response-cmd01.txt` | `00 00 01 00 80 DB` | valid | `CONNECT: NOT ACT` | 6-byte command-shaped frame accepted enough to change display. |
| `captures/rcp-response-cmd02.txt` | `00 00 02 00 80 DB` | invalid | `CONNECT: NOT ACT` | Bad checksum still changed display. |
| `captures/rcp-response-cmd02.txt` | `00 00 02 00 80 D8` | valid | `CONNECT: NOT ACT` | Valid checksum also changed display. |
| `captures/rcp-response-cmd03.txt` | `00 00 03 00 80 D9` | valid | `CONNECT: NOT ACT` | 6-byte command-shaped frame accepted enough to change display. |
| `captures/rcp-response-cmd04.txt` | `00 00 7F 00 80 A5` | valid | no screen change | First observed checksum-valid 6-byte frame that does not trigger `CONNECT: NOT ACT`. |
| `captures/rcp-response-cmd05.txt` | `00 00 80 00 80 5A` | valid | `CONNECT: NOT ACT` | 6-byte command-shaped frame accepted enough to change display. |

Implications from command-field tests:

- Screen change is not simply based on frame length or checksum validity.
- The command/status byte matters: `0x7F` appears ignored or treated as
  non-link-establishing, despite a valid checksum.
- Tested commands `0x00`, `0x01`, `0x02`, `0x03`, and `0x80` can trigger
  `CONNECT: NOT ACT`; `0x7F` did not.
- The RCP operating manual notes that `CAM POWER`, `MASTER`/`SLAVE`, and some
  monitor functions are available only when connected to a CCU, so active state
  may depend on CCU identity/status bits.

Next low-risk response experiments:

1. Repeat the same test with logging enabled so the pin 4 output before, during,
   and after `CONNECT: NOT ACT` is captured.
2. Try sending the mirrored heartbeat continuously at a cadence close to the RCP
   heartbeat, for example every 0.6 seconds, and watch whether the display state
   changes.
3. Probe semantic fields within the six-byte frame shape, changing one byte at
   a time and logging both screen state and pin 4 output. Prioritize small
   command values and avoid broad brute-force sweeps.
4. Prefer capturing a real CCU/camera pin 7 response before broad guessing.

### Command Sweep Helper

A cautious command-byte sweep helper is available at
`scripts/serial_sweep_commands.py`. It sends only checksum-valid six-byte frames
using the current frame/checksum hypothesis and marks any RCP output that is not
the known heartbeat.

Recommended first sweep:

```powershell
python scripts/serial_sweep_commands.py --port COM5 --start 0x00 --end 0x20 --after-each 1.0 --log captures/rcp-sweep-cmd-00-20.txt
```

Optional dry run:

```powershell
python scripts/serial_sweep_commands.py --port COM5 --start 0x00 --end 0x20 --dry-run
```

Use small ranges and keep watching the RCP screen while the sweep runs. The log
captures TX/RX bytes, but it cannot record screen messages unless they are noted
manually afterward.

The `0x00-0x20` sweep produced `CONNECT: NOT ACT` roughly halfway through the
run, but the exact command was not recorded in the log. Rerun a narrower range
with manual screen prompts:

```powershell
python scripts/serial_sweep_commands.py --port COM5 --start 0x0C --end 0x14 --after-each 1.2 --prompt-screen --log captures/rcp-sweep-cmd-0c-14-screen.txt
```

At each prompt, press Enter for no screen change, type `CONNECT: NOT ACT` when
that appears, or type `q` to stop.

Prompted sweep result:

- Capture: `captures/rcp-sweep-cmd-0c-14-screen.txt`.
- The RCP was reset after each screen trigger to clear its state, so recorded
  triggers should be treated as independent fresh observations.
- First recorded screen marker: after command `0x0C`, frame
  `00 00 0C 00 80 D6`, screen `CONNECT: NOT ACT`.
- Later manual screen markers were recorded after `0x0D`, `0x10`, `0x11`,
  `0x12`, `0x13`, and `0x14`.
- No manual screen markers were recorded after `0x0E` or `0x0F`.
- Pin 4 output remained the heartbeat `00 00 00 00 80 DA` throughout.

Interpretation:

- Commands `0x0C`, `0x0D`, `0x10`, `0x11`, `0x12`, `0x13`, and `0x14` have
  independent evidence of triggering `CONNECT: NOT ACT` in this sweep.
- Commands `0x0E` and `0x0F` did not have a screen marker recorded in this
  sweep and are current non-trigger candidates.
- Because pin 4 stayed heartbeat-only, this state change is visible on the LCD
  but does not yet produce a new RCP-to-host serial response.

Second prompted sweep result:

- Capture: `captures/rcp-sweep-cmd-15-30-screen.txt`.
- The log includes one partial/restarted pass at the beginning, then a fuller
  prompted sweep through `0x30`.
- Pin 4 output remained the heartbeat `00 00 00 00 80 DA` throughout.

Commands with recorded `CONNECT: NOT ACT` screen markers:

| Command | TX frame |
| ---: | --- |
| `0x15` | `00 00 15 00 80 CF` |
| `0x16` | `00 00 16 00 80 CC` |
| `0x17` | `00 00 17 00 80 CD` |
| `0x18` | `00 00 18 00 80 C2` |
| `0x19` | `00 00 19 00 80 C3` |
| `0x1A` | `00 00 1A 00 80 C0` |
| `0x1B` | `00 00 1B 00 80 C1` |
| `0x1C` | `00 00 1C 00 80 C6` |
| `0x1D` | `00 00 1D 00 80 C7` |
| `0x28` | `00 00 28 00 80 F2` |
| `0x29` | `00 00 29 00 80 F3` |
| `0x2C` | `00 00 2C 00 80 F6` |
| `0x2D` | `00 00 2D 00 80 F7` |
| `0x30` | `00 00 30 00 80 EA` |

Commands with no recorded screen marker in this sweep:

```text
0x1E 0x1F 0x20 0x21 0x22 0x23 0x24 0x25
0x26 0x27 0x2A 0x2B 0x2E 0x2F
```

Emerging pattern:

- Some command byte ranges trigger `CONNECT: NOT ACT` while nearby checksum-valid
  commands do not.
- Triggering still does not make the RCP transmit anything except the heartbeat.
- `CONNECT: NOT ACT` appears to be a parser-recognized but not session-active
  state. It may indicate the RCP recognizes the command class as CCU-like, but
  the remaining status/identity/activation fields are wrong or incomplete.

### Targeted Field Matrix Probe

After the `0x15-0x30` sweep, the best next experiment is not a broader command
sweep. The command byte is clearly relevant, but active-session behavior may
depend on the state/value fields or the prefix bytes. Use
`scripts/serial_probe_matrix.py` to hold one promising command constant and vary
only a small set of fields.

Start with command `0x15` because it is already associated with the RCP's own
`CALL` output frames:

```powershell
python scripts/serial_probe_matrix.py --port COM5 --commands 0x15 --states "0x00 0x80" --values "0x00 0x80" --after-each 1.2 --prompt-screen --log captures/rcp-matrix-cmd15-state-value.txt
```

Dry-run frames:

```text
00 00 15 00 00 4F
00 00 15 00 80 CF
00 00 15 80 00 CF
00 00 15 80 80 4F
```

Why this is useful:

- `00 00 15 00 00 4F` and `00 00 15 80 00 CF` match the RCP's observed
  `CALL` frames.
- `00 00 15 00 80 CF` matches the command-sweep shape that triggered
  `CONNECT: NOT ACT`.
- `00 00 15 80 80 4F` checks whether both high/state bits together change the
  parser state.

If all four still produce only `CONNECT: NOT ACT` or no change, the next matrix
should keep `cmd=0x15`, `state=0x00`, `value=0x80`, and vary only prefix bytes:

```powershell
python scripts/serial_probe_matrix.py --port COM5 --prefix2s "0x00-0x0F" --commands 0x15 --states 0x00 --values 0x80 --after-each 1.2 --prompt-screen --log captures/rcp-matrix-cmd15-prefix2-00-0f.txt
```

Treat any result other than heartbeat-only pin 4 output as high-priority. In
particular, look for a new RCP frame, a different LCD message, or any transition
from `CONNECT: NOT ACT` to an active/connected state.

### 2026-05-13 Command `0x15` State/Value Matrix Result

Capture:

- `captures/rcp-matrix-cmd15-state-value.txt`

Frames tested:

| Command | State | Value | TX frame | Screen result |
| ---: | ---: | ---: | --- | --- |
| `0x15` | `0x00` | `0x00` | `00 00 15 00 00 4F` | `CONNECT NOT ACT` |
| `0x15` | `0x00` | `0x80` | `00 00 15 00 80 CF` | `CONNECT NOT ACT` |
| `0x15` | `0x80` | `0x00` | `00 00 15 80 00 CF` | `CONNECT NOT ACT` |
| `0x15` | `0x80` | `0x80` | `00 00 15 80 80 4F` | `CONNECT NOT ACT` |

Analyzer result:

- Pin 4 RX stayed at heartbeat only: `00 00 00 00 80 DA`.
- No non-heartbeat RCP-to-host frames were observed.
- The RCP was sensitive to all four command `0x15` variants, including both
  frames that match the panel's own `CALL` output, but none advanced the panel
  beyond `CONNECT NOT ACT`.

Interpretation:

- For command `0x15`, the tested state/value high bits are not enough to produce
  an active session.
- The missing host response is likely in another field, a required repeated
  cadence, a multi-frame exchange, or a CCU/camera identity/status frame.
- Since command `0x15` is parser-visible across all tested state/value variants,
  it is a good anchor for prefix-byte testing.

Recommended next matrix:

```powershell
python scripts/serial_probe_matrix.py --port COM5 --prefix2s "0x00-0x0F" --commands 0x15 --states 0x00 --values 0x80 --after-each 1.2 --prompt-screen --log captures/rcp-matrix-cmd15-prefix2-00-0f.txt
```

If all `prefix2` values behave the same, repeat with `prefix1s "0x00-0x0F"` and
`prefix2s 0x00`. Prefix bytes may encode device address, CCU identity, panel
number, or bus direction.

### 2026-05-13 Command `0x15` Prefix2 Matrix Result

Capture:

- `captures/rcp-matrix-cmd15-prefix2-00-0f.txt`

Test shape:

- `p1=0x00`
- `p2=0x00-0x0F`
- `cmd=0x15`
- `state=0x00`
- `value=0x80`

Analyzer result:

- Pin 4 RX stayed at heartbeat only: `00 00 00 00 80 DA`.
- No non-heartbeat RCP-to-host frames were observed.
- The log contains 263 heartbeat RX frames and 16 transmitted prefix2 variants.

Screen observations:

- `CONNECT NOT ACT` was recorded after prefix2 values `0x00`, `0x01`, `0x02`,
  `0x03`, `0x04`, `0x05`, `0x06`, `0x08`, `0x09`, `0x0A`, `0x0B`, `0x0C`,
  `0x0D`, and `0x0E`.
- No screen marker was recorded after prefix2 `0x07` or `0x0F`.
- One marker was typed as `CONNECT NTO ACT`; treat this as the same
  observation unless later testing proves otherwise.

Interpretation:

- Prefix2 did not produce an active session in the tested low-nibble range.
- The missing response is still not visible on pin 4.
- The missing markers at `0x07` and `0x0F` may be real parser behavior, because
  both have low three bits set, but this needs a focused confirmation run before
  treating it as a rule.

Recommended confirmation:

```powershell
python scripts/serial_probe_matrix.py --port COM5 --prefix2s "0x06 0x07 0x08 0x0E 0x0F" --commands 0x15 --states 0x00 --values 0x80 --after-each 1.2 --prompt-screen --log captures/rcp-matrix-cmd15-prefix2-confirm.txt
```

Reset the RCP after any screen-triggering result. This keeps the comparison
between trigger and non-trigger prefix2 values clean.

### 2026-05-13 Prefix2 Confirmation Result

Capture:

- `captures/rcp-matrix-cmd15-prefix2-confirm.txt`

Test shape:

- `p1=0x00`
- `p2=0x06`, `0x07`, `0x08`, `0x0E`, `0x0F`
- `cmd=0x15`
- `state=0x00`
- `value=0x80`

Screen observations:

| Prefix2 | TX frame | Screen marker |
| ---: | --- | --- |
| `0x06` | `00 06 15 00 80 C9` | `CONNECT NOT ACT` |
| `0x07` | `00 07 15 00 80 C8` | none recorded |
| `0x08` | `00 08 15 00 80 C7` | `CONNECT NOT ACT` |
| `0x0E` | `00 0E 15 00 80 C1` | `CONNECT NOT ACT` |
| `0x0F` | `00 0F 15 00 80 C0` | none recorded |

Analyzer result:

- 65 heartbeat RX frames: `00 00 00 00 80 DA`.
- 14 apparent non-heartbeat RX frames after `p2=0x0F`:
  `00 00 00 80 DA 00`.
- No other RCP-to-host frame shape was observed.

Interpretation:

- `p2=0x07` and `p2=0x0F` again failed to produce a recorded screen marker,
  while neighboring values did.
- The apparent `00 00 00 80 DA 00` response after `p2=0x0F` is probably a
  one-byte framing slip of the normal heartbeat stream, because it is exactly
  the heartbeat sequence viewed from byte offset 1:
  `00 00 00 00 80 DA 00 00 ...`.
- Because the shifted heartbeat also satisfies the current XOR checksum
  hypothesis, checksum validity alone is not enough to prove frame alignment.

Recommended raw confirmation for `p2=0x0F`:

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 0F 15 00 80 C0" --repeat 1 --delay 1.5 --after 5 --frame-size 0 --log captures/rcp-prefix2-0f-raw.txt
```

Then repeat for `p2=0x07`:

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 07 15 00 80 C8" --repeat 1 --delay 1.5 --after 5 --frame-size 0 --log captures/rcp-prefix2-07-raw.txt
```

Raw capture avoids imposing 6-byte alignment on the received stream, so it
should show whether the apparent non-heartbeat is a real frame or just a shifted
view of the heartbeat.

### Series Resistor Note

Current host-to-RCP tests use a series resistor between adapter `TXD` and RCP
pin 7 as a protection measure. A 4.7 kOhm series resistor should normally still
work with a high-impedance RS-232 receiver input, so it is unlikely to explain a
selective pattern where nearby checksum-valid frames behave differently.

Possible resistor-related failure modes:

- If the RCP input is much lower impedance than expected, 4.7 kOhm could reduce
  the voltage swing at pin 7.
- If the input is clamped internally, the resistor may limit current enough to
  make the received waveform marginal.
- Marginal signaling would more likely produce random missed/garbled frames
  than a repeatable distinction between specific prefix values.

Low-risk check:

- Measure pin 7 relative to pin 9 on the RCP side of the resistor while the
  adapter is idle; it should show a strong RS-232 idle level, not near 0 V.
- If testing without the resistor, first try a smaller protection resistor such
  as 1 kOhm rather than going straight to a direct connection.
- Compare one known-trigger frame, such as `00 06 15 00 80 C9`, and one
  suspected non-trigger frame, such as `00 07 15 00 80 C8`, using the same reset
  procedure.

### Direct Response Sweep Without Screen Logging

For response hunting, use `scripts/serial_direct_response_sweep.py` rather than
the older fixed-frame sweep. It sends checksum-valid 6-byte host frames, but
reads pin 4 as raw bytes and checks whether the received data can be explained
as the repeated heartbeat:

```text
00 00 00 00 80 DA
```

This avoids treating shifted heartbeat bytes such as `00 00 00 80 DA 00` as a
new response frame.

Recommended first direct sweep:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00-0xFF" --states 0x00 --values 0x80 --after-each 0.6 --stop-on-anomaly --log captures/rcp-direct-cmd-00-ff.txt
```

For a long sweep where every anomaly should be logged but the panel needs a
fresh power cycle before continuing, use `--pause-on-anomaly` instead of
`--stop-on-anomaly`. After the prompt, power-cycle the RCP, wait for the normal
heartbeat, then press Enter.

What to watch for:

- If the script reports `Anomalies: 0`, the panel never sent raw bytes that
  differed from the heartbeat stream during this sweep.
- If it stops on an anomaly, preserve the log and rerun only the reported frame
  with raw capture before assuming it is a real response.
- Keep the same resistor/wiring setup for this run so the result remains
  comparable to the earlier observations.

If the command-only direct sweep finds nothing, the next direct grid should be
split into two chunks to stay within the default safety limit:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix2s "0x00-0x0F" --commands "0x00-0x1F" --states 0x00 --values 0x80 --after-each 0.4 --stop-on-anomaly --log captures/rcp-direct-p2-00-0f-cmd-00-1f.txt
python scripts/serial_direct_response_sweep.py --port COM5 --prefix2s "0x00-0x0F" --commands "0x20-0x3F" --states 0x00 --values 0x80 --after-each 0.4 --stop-on-anomaly --log captures/rcp-direct-p2-00-0f-cmd-20-3f.txt
```

### 2026-05-13 Direct Command Sweep Response Hit

Capture:

- `captures/rcp-direct-cmd-00-ff.txt`

Sweep shape:

- `p1=0x00`
- `p2=0x00`
- `cmd=0x00-0xFF`
- `state=0x00`
- `value=0x80`
- Stop on first raw RX anomaly.

Important result:

- The sweep stopped immediately after transmitting command `0xB5`:
  `00 00 B5 00 80 6F`.
- The previous transmitted command was `0xB4`: `00 00 B4 00 80 6E`, about
  0.6 seconds earlier.
- The RCP produced repeated non-heartbeat frames:
  `07 80 6D 20 D8 48`.
- Final raw capture showed the same frame repeated, then the panel returned to
  the normal heartbeat `00 00 00 00 80 DA`.

Observed response:

```text
07 80 6D 20 D8 48
07 80 6D 20 D8 48
07 80 6D 20 D8 48
...
00 00 00 00 80 DA
```

Checksum check:

- `5A xor 07 xor 80 xor 6D xor 20 xor D8 = 48`.
- This means `07 80 6D 20 D8 48` is a real checksum-valid 6-byte frame under the
  current checksum hypothesis, not a shifted heartbeat artifact.

Interpretation:

- This is the first confirmed non-heartbeat RCP-to-host serial response on pin
  4 during host-frame probing.
- `cmd=0xB5` is the most likely trigger, but `cmd=0xB4` should be retested
  because it was sent one read window earlier and delayed responses are possible.
- The response frame shape suggests the RCP may be reporting a status or
  identity-like frame with `p1=0x07`, `p2=0x80`, `cmd=0x6D`, `state=0x20`,
  `value=0xD8`.

Recommended confirmation tests:

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 B4 00 80 6E" --repeat 1 --delay 1.5 --after 5 --frame-size 0 --log captures/rcp-confirm-cmd-b4-raw.txt
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 B5 00 80 6F" --repeat 1 --delay 1.5 --after 5 --frame-size 0 --log captures/rcp-confirm-cmd-b5-raw.txt
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 B6 00 80 6C" --repeat 1 --delay 1.5 --after 5 --frame-size 0 --log captures/rcp-confirm-cmd-b6-raw.txt
```

If `cmd=0xB5` reliably triggers the `07 80 6D 20 D8 48` response, test whether
the response depends on the state/value fields:

```powershell
python scripts/serial_probe_matrix.py --port COM5 --commands 0xB5 --states "0x00 0x80" --values "0x00 0x80" --after-each 1.2 --prompt-screen --log captures/rcp-matrix-cmd-b5-state-value.txt
```

### 2026-05-13 B4/B5/B6 Single-Frame Confirmation

Captures:

- `captures/rcp-confirm-cmd-b4-raw.txt`
- `captures/rcp-confirm-cmd-b5-raw.txt`
- `captures/rcp-confirm-cmd-b6-raw.txt`

Single frames tested:

| Command | TX frame | Pin 4 result |
| ---: | --- | --- |
| `0xB4` | `00 00 B4 00 80 6E` | heartbeat only |
| `0xB5` | `00 00 B5 00 80 6F` | heartbeat only |
| `0xB6` | `00 00 B6 00 80 6C` | heartbeat only |

Interpretation:

- The earlier `07 80 6D 20 D8 48` response did not reproduce from isolated
  single-frame `B4`, `B5`, or `B6` tests.
- The response may require prior sweep history, a command sequence, repeated
  cadence, or a temporary parser/session state produced by many earlier frames.
- The `B5` frame is still the best suspect because the direct sweep reported the
  anomaly in the read window immediately after transmitting `B5`, but it is not
  sufficient by itself in a fresh single-frame test.

Recommended focused replay:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB8" --states 0x00 --values 0x80 --after-each 0.6 --stop-on-anomaly --log captures/rcp-direct-cmd-b0-b8-replay.txt
```

If that does not reproduce the response, try the same range with a shorter
cadence to better mimic the long sweep:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB8" --states 0x00 --values 0x80 --after-each 0.25 --stop-on-anomaly --log captures/rcp-direct-cmd-b0-b8-fast.txt
```

If the focused range still does not reproduce it, rerun the longer sweep from
`0xA0-0xB8` rather than the full `0x00-0xFF` range:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xA0-0xB8" --states 0x00 --values 0x80 --after-each 0.6 --stop-on-anomaly --log captures/rcp-direct-cmd-a0-b8-replay.txt
```

### 2026-05-13 B0-B8 Focused Replay Hit

Capture:

- `captures/rcp-direct-cmd-b0-b8-replay.txt`

Replay shape:

- `p1=0x00`
- `p2=0x00`
- `cmd=0xB0-0xB8`
- `state=0x00`
- `value=0x80`
- `after-each=0.6`
- Stop on first raw RX anomaly.

Important result:

- The sweep sent `cmd=0xB0`, then `cmd=0xB1`.
- The anomaly was captured in the read window immediately after transmitting
  `cmd=0xB1`: `00 00 B1 00 80 6B`.
- The RCP emitted one checksum-valid non-heartbeat frame:
  `07 80 6C 20 D8 49`.
- The final read window returned to heartbeat-only traffic.

Checksum check:

- `5A xor 07 xor 80 xor 6C xor 20 xor D8 = 49`.

Comparison with the earlier full sweep hit:

| Trigger window | RCP response |
| --- | --- |
| After `cmd=0xB1` in focused `B0-B8` replay | `07 80 6C 20 D8 49` |
| After `cmd=0xB5` in full `00-FF` sweep | `07 80 6D 20 D8 48` |

Interpretation:

- The non-heartbeat response is reproducible with a short local sequence, so it
  does not require the entire `0x00-0xFF` sweep history.
- The response may be sequence-dependent: `B1` alone is not yet proven as the
  trigger because `B0` was sent one window earlier.
- The response command byte changed from `0x6D` to `0x6C`, which suggests the
  RCP may be returning a status/identity code related to the host command or to
  internal state.

Recommended tight confirmations:

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 B0 00 80 6A" --repeat 1 --delay 1.5 --after 5 --frame-size 0 --log captures/rcp-confirm-cmd-b0-raw.txt
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 B1 00 80 6B" --repeat 1 --delay 1.5 --after 5 --frame-size 0 --log captures/rcp-confirm-cmd-b1-raw.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB1" --states 0x00 --values 0x80 --after-each 0.6 --stop-on-anomaly --log captures/rcp-direct-cmd-b0-b1-replay.txt
```

If `B1` alone is heartbeat-only but `B0-B1` reproduces the response, treat
`B0 -> B1` as a required two-frame sequence.

### 2026-05-13 B0/B1 Tight Confirmation Result

Captures:

- `captures/rcp-confirm-cmd-b0-raw.txt`
- `captures/rcp-confirm-cmd-b1-raw.txt`
- `captures/rcp-direct-cmd-b0-b1-replay.txt`

Results:

| Test | Pin 4 result |
| --- | --- |
| Single `B0`: `00 00 B0 00 80 6A` | heartbeat only |
| Single `B1`: `00 00 B1 00 80 6B` | heartbeat only |
| Sequence `B0 -> B1` | heartbeat only, `Anomalies: 0` |

Interpretation:

- The `07 80 6C 20 D8 49` response from the `B0-B8` replay did not reproduce
  with the minimal `B0 -> B1` sequence.
- The response may be intermittent, cadence-sensitive, dependent on a longer
  sequence such as `B0-B8`, or affected by panel state that was not identical
  between runs.
- The next priority is measuring reproducibility of the same short range rather
  than expanding the search space.

Recommended reproducibility test:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB8" --states 0x00 --values 0x80 --after-each 0.6 --cycles 5 --cycle-pause 2 --log captures/rcp-direct-cmd-b0-b8-cycles.txt
```

Run this without `--stop-on-anomaly` so all five cycles complete and the log can
show whether the response happens consistently, intermittently, or only once.

### Power-Cycle Isolation Test Plan

Use this plan when intentionally power-cycling the RCP between tests. The goal
is to distinguish a cold-boot reproducible protocol response from a response
that only appears after accumulated parser/session state.

Before each test:

1. Stop any serial script.
2. Power off the RCP.
3. Wait at least 5 seconds.
4. Power on the RCP.
5. Wait until the panel is stable and heartbeat traffic has resumed.
6. Run exactly one test command.

Keep the wiring and series resistor the same between tests unless the test name
explicitly says otherwise.

#### Set A: Cold-Boot Reproducibility

Run these first. They test whether the `B0-B8` response is repeatable from a
fresh power cycle.

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB8" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-a1-b0-b8.txt
```

Power-cycle, then:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB8" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-a2-b0-b8.txt
```

Power-cycle, then:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB8" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-a3-b0-b8.txt
```

Expected useful outcomes:

- If all three produce the same response, the sequence is cold-boot
  reproducible.
- If only some produce a response, the behavior may be timing-sensitive or
  intermittent.
- If none produce a response, the earlier hit likely depended on prior panel
  state.

#### Set B: Minimum Sequence Length

Run this set only if Set A produces at least one response. Power-cycle between
each command. These tests find the shortest command prefix that can trigger a
non-heartbeat response.

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB1" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-b1-b0-b1.txt
```

Power-cycle, then:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB2" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-b2-b0-b2.txt
```

Power-cycle, then:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB3" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-b3-b0-b3.txt
```

Power-cycle, then:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB4" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-b4-b0-b4.txt
```

#### Set C: Cadence Sensitivity

Run this set if Set A is inconsistent or if Set B does not identify a clean
minimum sequence. Power-cycle between each command.

Slow cadence:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB8" --states 0x00 --values 0x80 --settle 3 --after-each 1.2 --stop-on-anomaly --log captures/rcp-powercycle-c1-b0-b8-slow.txt
```

Fast cadence:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB8" --states 0x00 --values 0x80 --settle 3 --after-each 0.25 --stop-on-anomaly --log captures/rcp-powercycle-c2-b0-b8-fast.txt
```

Very fast cadence:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB8" --states 0x00 --values 0x80 --settle 3 --after-each 0.1 --stop-on-anomaly --log captures/rcp-powercycle-c3-b0-b8-very-fast.txt
```

#### Set D: Control Tests

Run these if the `B0-B8` range is producing responses. Power-cycle between
each command. These confirm the response is specific to the `B0` range.

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xA8-0xAF" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-d1-a8-af-control.txt
```

Power-cycle, then:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB8-0xBF" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-d2-b8-bf-control.txt
```

If a response appears in control ranges too, the trigger may be a broader
command class rather than a specific `B0-B8` sequence.

### 2026-05-13 Power-Cycle Set A Result

Captures:

- `captures/rcp-powercycle-a1-b0-b8.txt`
- `captures/rcp-powercycle-a2-b0-b8.txt`
- `captures/rcp-powercycle-a3-b0-b8.txt`

Each run was performed after a panel power cycle. All three runs produced the
same non-heartbeat response.

| Run | Trigger window | RCP response | Result |
| --- | --- | --- | --- |
| A1 | after `B1`: `00 00 B1 00 80 6B` | `07 80 6C 20 D8 49` repeated | anomaly |
| A2 | after `B1`: `00 00 B1 00 80 6B` | `07 80 6C 20 D8 49` repeated | anomaly |
| A3 | after `B1`: `00 00 B1 00 80 6B` | `07 80 6C 20 D8 49` repeated | anomaly |

Observed raw pattern in each run:

```text
07 80 6C 20 D8 49
07 80 6C 20 D8 49
07 80 6C 20 D8 49
...
00 00 00 00 80 DA
```

Interpretation:

- The `B0-B8` response is cold-boot reproducible.
- The response appears immediately after the `B1` transmit window when the test
  starts from a fresh power cycle.
- The earlier `B0 -> B1` heartbeat-only result was likely affected by panel
  state from previous experiments, timing, or not starting from an equivalent
  cold condition.
- The next test should determine whether `B0 -> B1` is sufficient from a fresh
  power cycle, or whether the script/test context of the `B0-B8` run matters.

Recommended next power-cycle tests:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB1" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-b1-b0-b1.txt
```

Power-cycle, then:

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 B1 00 80 6B" --repeat 1 --delay 3 --after 5 --frame-size 0 --log captures/rcp-powercycle-b1-alone-raw.txt
```

If `B0-B1` reproduces but `B1` alone does not, treat `B0 -> B1` as the minimum
cold-boot sequence.

### 2026-05-13 Minimum Cold-Boot Sequence Result

Captures:

- `captures/rcp-powercycle-b0.txt`
- `captures/rcp-powercycle-b1-b0-b1.txt`
- `captures/rcp-powercycle-b1-alone-raw.txt`

Each test was run after a panel power cycle.

| Test | TX frame(s) | Pin 4 result |
| --- | --- | --- |
| `B0` alone | `00 00 B0 00 80 6A` | heartbeat only, `Anomalies: 0` |
| `B1` alone | `00 00 B1 00 80 6B` | heartbeat only |
| `B0 -> B1` | `00 00 B0 00 80 6A`, then `00 00 B1 00 80 6B` | `07 80 6C 20 D8 49` repeated |

Conclusion:

- The minimum confirmed cold-boot trigger is the two-frame sequence:

```text
Host -> RCP: 00 00 B0 00 80 6A
Host -> RCP: 00 00 B1 00 80 6B
RCP -> Host: 07 80 6C 20 D8 49
```

- Neither `B0` nor `B1` is sufficient alone from a cold panel.
- `B0` appears to prime the panel, and `B1` completes the query/trigger.
- The response repeats for a short period, then the panel returns to the normal
  heartbeat `00 00 00 00 80 DA`.

Recommended next tests:

1. Timing sensitivity between `B0` and `B1`.
2. State/value sensitivity of the `B0 -> B1` pair.
3. Whether the response changes when sending nearby pairs such as `B1 -> B2`,
   `B2 -> B3`, etc.

Suggested timing tests, with power cycle between each:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB1" --states 0x00 --values 0x80 --settle 3 --after-each 0.1 --stop-on-anomaly --log captures/rcp-powercycle-timing-b0-b1-100ms.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0-0xB1" --states 0x00 --values 0x80 --settle 3 --after-each 1.2 --stop-on-anomaly --log captures/rcp-powercycle-timing-b0-b1-1200ms.txt
```

Suggested nearby-pair tests, with power cycle between each:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB1-0xB2" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b1-b2.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB2-0xB3" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b2-b3.txt
```

### 2026-05-13 B0-B1 Timing Result

Captures:

- `captures/rcp-powercycle-timing-b0-b1-100ms.txt`
- `captures/rcp-powercycle-timing-b0-b1-1200ms.txt`

Each test was run after a panel power cycle.

| B0-to-B1 spacing | RCP response |
| ---: | --- |
| about 100 ms | `07 80 6C 20 D8 49` |
| about 1200 ms | `07 80 6C 20 D8 49` repeated |

Interpretation:

- The `B0 -> B1` trigger is not tightly timing-sensitive across the tested
  range.
- `B0` appears to prime a state that remains valid for at least about 1.2
  seconds.
- The sequence order is more important than exact short timing.

Recommended next tests:

Power-cycle between each test. Check whether the trigger is specific to the
`B0 -> B1` pair or whether nearby ordered pairs also trigger related responses:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB1-0xB2" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b1-b2.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB2-0xB3" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b2-b3.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xAF-0xB0" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-af-b0.txt
```

### 2026-05-13 Nearby Pair Results

Captures:

- `captures/rcp-powercycle-pair-af-b0.txt`
- `captures/rcp-powercycle-pair-b1-b2.txt`
- `captures/rcp-powercycle-pair-b2-b3.txt`

Each test was run after a panel power cycle.

| Host pair | Second frame window | RCP response |
| --- | --- | --- |
| `AF -> B0` | `00 00 B0 00 80 6A` | `07 80 6C 60 30 E1` repeated |
| `B1 -> B2` | `00 00 B2 00 80 68` | `07 80 36 10 0C F7` repeated |
| `B2 -> B3` | `00 00 B3 00 80 69` | `07 80 36 10 2C D7` repeated |

Previously confirmed:

| Host pair | Second frame window | RCP response |
| --- | --- | --- |
| `B0 -> B1` | `00 00 B1 00 80 6B` | `07 80 6C 20 D8 49` repeated |

Checksum checks:

- `07 80 6C 60 30 E1`: checksum valid.
- `07 80 36 10 0C F7`: checksum valid.
- `07 80 36 10 2C D7`: checksum valid.

Interpretation:

- The RCP responds to multiple adjacent two-frame host sequences in the
  `AF-B3` region, not only `B0 -> B1`.
- The response appears in the read window after the second frame of each pair.
- The response payload changes by pair, which suggests these are real command
  queries or status reads rather than a generic link-present acknowledgement.
- The repeated response pattern still returns to the normal heartbeat afterward.

Emerging map:

| Host sequence | RCP response fields |
| --- | --- |
| `AF -> B0` | `p1=07 p2=80 cmd=6C state=60 value=30 checksum=E1` |
| `B0 -> B1` | `p1=07 p2=80 cmd=6C state=20 value=D8 checksum=49` |
| `B1 -> B2` | `p1=07 p2=80 cmd=36 state=10 value=0C checksum=F7` |
| `B2 -> B3` | `p1=07 p2=80 cmd=36 state=10 value=2C checksum=D7` |

Recommended next tests:

Power-cycle between each. First continue the adjacent-pair map:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB3-0xB4" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b3-b4.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB4-0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b4-b5.txt
```

Then test whether adjacency and order matter:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB0 0xB2" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b0-b2-skip.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB1 0xB0" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b1-b0-reverse.txt
```

### 2026-05-13 Additional Pair/Control Results

User observation:

- All tests still showed `CONNECT NOT ACT` on the RCP/CCU screen, with no other
  visible state change.

Serial captures:

- `captures/rcp-powercycle-pair-b3-b4.txt`
- `captures/rcp-powercycle-pair-b4-b5.txt`
- `captures/rcp-powercycle-pair-b0-b2-skip.txt`
- `captures/rcp-powercycle-pair-b1-b0-reverse.txt`

Each test was run after a panel power cycle.

| Host pair | Second frame window | RCP response |
| --- | --- | --- |
| `B3 -> B4` | `00 00 B4 00 80 6E` | `07 80 6D 40 30 C0` repeated |
| `B4 -> B5` | `00 00 B5 00 80 6F` | `07 80 6D 20 D8 48` repeated |
| `B0 -> B2` | `00 00 B2 00 80 68` | `07 80 36 10 0C F7` repeated |
| `B1 -> B0` | `00 00 B0 00 80 6A` | `07 80 6C 40 30 C1` repeated |

Interpretation:

- The screen state remains `CONNECT NOT ACT`, but pin 4 responses are changing
  in a structured, checksum-valid way.
- The skip test `B0 -> B2` produced the same response as `B1 -> B2`, so the
  second command may be the main selector once any valid priming frame is sent.
- The reverse test `B1 -> B0` also produced a valid response, so strict
  ascending adjacency is not required.
- Current model: a first host frame primes/enters a response mode, and the
  second host frame selects a status/query response.

Expanded observed response map:

| Second host command | Observed response(s) |
| ---: | --- |
| `B0` | `07 80 6C 60 30 E1` after `AF -> B0`; `07 80 6C 40 30 C1` after `B1 -> B0` |
| `B1` | `07 80 6C 20 D8 49` after `B0 -> B1` |
| `B2` | `07 80 36 10 0C F7` after `B1 -> B2` and `B0 -> B2` |
| `B3` | `07 80 36 10 2C D7` after `B2 -> B3` |
| `B4` | `07 80 6D 40 30 C0` after `B3 -> B4` |
| `B5` | `07 80 6D 20 D8 48` after `B4 -> B5` |

Recommended next tests:

Power-cycle between each. Test whether a generic primer plus selected second
command is enough:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB0" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-primer-00-b0.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB2" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-primer-00-b2.txt
```

Then map the next selected second commands:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB5-0xB6" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b5-b6.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0xB6-0xB7" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-pair-b6-b7.txt
```

### 2026-05-13 Generic Primer and B6-B7 Results

Captures:

- `captures/rcp-powercycle-primer-00-b0.txt`
- `captures/rcp-powercycle-primer-00-b2.txt`
- `captures/rcp-powercycle-pair-b5-b6.txt`
- `captures/rcp-powercycle-pair-b6-b7.txt`

Each test was run after a panel power cycle.

| Host pair | Second frame window | RCP response |
| --- | --- | --- |
| `00 -> B0` | `00 00 B0 00 80 6A` | `07 80 6C 40 30 C1` repeated |
| `00 -> B2` | `00 00 B2 00 80 68` | `07 80 36 10 0C F7` repeated |
| `B5 -> B6` | `00 00 B6 00 80 6C` | `07 80 1B 08 C6 08` repeated |
| `B6 -> B7` | `00 00 B7 00 80 6D` | `07 80 1B 08 D6 18` repeated |

Checksum checks:

- `07 80 6C 40 30 C1`: checksum valid.
- `07 80 36 10 0C F7`: checksum valid.
- `07 80 1B 08 C6 08`: checksum valid.
- `07 80 1B 08 D6 18`: checksum valid.

Interpretation:

- `00 -> B0` produced the same response as `B1 -> B0`.
- `00 -> B2` produced the same response as `B0 -> B2` and `B1 -> B2`.
- This supports the model that the first frame can be a generic valid primer,
  and the second frame selects the response.
- The `B6` and `B7` selected responses introduce another response command class
  (`cmd=0x1B`) with changing value bytes.

Updated selected-command map:

| Selected second command | Observed response |
| ---: | --- |
| `B0` | `07 80 6C 40 30 C1` after `00 -> B0` and `B1 -> B0`; `07 80 6C 60 30 E1` after `AF -> B0` |
| `B1` | `07 80 6C 20 D8 49` after `B0 -> B1` |
| `B2` | `07 80 36 10 0C F7` after `00 -> B2`, `B0 -> B2`, and `B1 -> B2` |
| `B3` | `07 80 36 10 2C D7` after `B2 -> B3` |
| `B4` | `07 80 6D 40 30 C0` after `B3 -> B4` |
| `B5` | `07 80 6D 20 D8 48` after `B4 -> B5` |
| `B6` | `07 80 1B 08 C6 08` after `B5 -> B6` |
| `B7` | `07 80 1B 08 D6 18` after `B6 -> B7` |

Recommended next controls:

Power-cycle between each. First prove whether `B2`, `B6`, and `B7` need a
primer, or whether they can respond as single frames from cold boot:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands 0xB2 --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-single-b2.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands 0xB6 --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-single-b6.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands 0xB7 --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-single-b7.txt
```

Then continue the selected-command map using `00` as the primer:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB3" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-primer-00-b3.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB4" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-primer-00-b4.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-primer-00-b5.txt
```

### 2026-05-13 Single-Frame and One-Shot Primer Results

Captures:

- `captures/rcp-powercycle-single-b2.txt`
- `captures/rcp-powercycle-single-b6.txt`
- `captures/rcp-powercycle-single-b7.txt`
- `captures/rcp-powercycle-primer-00-b3.txt`
- `captures/rcp-powercycle-primer-00-b4.txt`
- `captures/rcp-powercycle-primer-00-b5.txt`

Single-frame controls, each after a panel power cycle:

| Test | Pin 4 result |
| --- | --- |
| `B2` alone | heartbeat only, `Anomalies: 0` |
| `B6` alone | heartbeat only, `Anomalies: 0` |
| `B7` alone | heartbeat only, `Anomalies: 0` |

Generic-primer map, each first run after a panel power cycle:

| Host pair | Second frame window | RCP response |
| --- | --- | --- |
| `00 -> B3` | `00 00 B3 00 80 69` | `07 80 36 10 2C D7` repeated |
| `00 -> B4` | `00 00 B4 00 80 6E` | `07 80 6D 40 30 C0` |
| `00 -> B5` | `00 00 B5 00 80 6F` | `07 80 6D 20 D8 48` repeated |

Repeated `00 -> B5` without power-cycling:

| Attempt | Power cycle before attempt? | Result |
| ---: | --- | --- |
| 1 | yes | `07 80 6D 20 D8 48` repeated |
| 2 | no | heartbeat only, `Anomalies: 0` |
| 3 | no | heartbeat only, `Anomalies: 0` |

Interpretation:

- A single selected command is not enough; the panel requires a preceding valid
  primer frame.
- `00` works as a primer for `B0`, `B2`, `B3`, `B4`, and `B5`.
- The same primed query may be one-shot after power-up. After `00 -> B5`
  returns its response, repeating `00 -> B5` without power-cycling does not
  produce another non-heartbeat response.
- Future mapping should power-cycle before each selected-command test unless
  intentionally studying latch/repeat behavior.

Current generic-primer selected-command map:

| Host query | RCP response |
| --- | --- |
| `00 -> B0` | `07 80 6C 40 30 C1` |
| `00 -> B2` | `07 80 36 10 0C F7` |
| `00 -> B3` | `07 80 36 10 2C D7` |
| `00 -> B4` | `07 80 6D 40 30 C0` |
| `00 -> B5` | `07 80 6D 20 D8 48` |

Recommended next tests:

Power-cycle before each query. Fill the missing `00 -> B1` entry and continue
the `00 -> selected` map:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB1" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-primer-00-b1.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB6" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-primer-00-b6.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB7" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --stop-on-anomaly --log captures/rcp-powercycle-primer-00-b7.txt
```

Optional latch test, without power-cycling after the first run:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.6 --cycles 3 --cycle-pause 2 --log captures/rcp-latch-primer-00-b5-cycles.txt
```

## Current Inferred Behavior

The current evidence suggests the RCP is entering a discovery/query/status-read
phase, not a completed active-control handshake.

Working model:

```text
Host/CCU -> RCP: valid primer frame
Host/CCU -> RCP: selected query/status command
RCP -> Host/CCU: checksum-valid response frame repeated briefly
RCP -> Host/CCU: returns to heartbeat
```

Important details:

- Single selected commands such as `B2`, `B6`, and `B7` do not respond from a
  cold panel.
- A preceding valid frame is required. `00 00 00 00 80 DA` works as a generic
  primer for several selected commands.
- The second command selects the response payload.
- The LCD can remain `CONNECT NOT ACT` while serial responses vary in a
  structured way. Serial response does not yet mean the active control session
  is accepted.
- At least some primed queries appear one-shot after power-up. Repeating the
  same primed query without power-cycling can produce only heartbeat traffic.

Likely protocol role:

- These `B0`-range commands may be a CCU discovery or capability/status query
  phase.
- The CCU may query RCP model/capability/state blocks before sending a later
  activation/session command.
- The next unknown is the command or command sequence that follows these
  discovery responses and moves the panel from `CONNECT NOT ACT` to active.

## Primer-Candidate Broad Sweep

Use `scripts/serial_primer_candidate_sweep.py` for broader searches based on
the current primer/query model. It sends:

```text
primer frame -> candidate frame -> raw RX classification
```

For clean mapping, use `--prompt-power-cycle` and power-cycle before each
candidate. This avoids the one-shot/latch behavior contaminating later
candidates.

Example dry run:

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0xB0-0xB7" --dry-run
```

Continue the known `B` range first:

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0xB1 0xB6 0xB7 0xB8 0xB9 0xBA 0xBB 0xBC 0xBD 0xBE 0xBF" --prompt-power-cycle --stop-on-anomaly --log captures/rcp-primer-sweep-b1-bf.txt
```

Because `--stop-on-anomaly` stops at the first response, after each hit:

1. Save the reported candidate and response frame.
2. Power-cycle the panel.
3. Restart the sweep from the next unmapped candidate.

For non-stop mapping, omit `--stop-on-anomaly`, but still power-cycle at each
prompt:

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0xB8-0xBF" --prompt-power-cycle --log captures/rcp-primer-sweep-b8-bf.txt
```

Suggested broad ranges after `B0-BF`:

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0xA0-0xAF" --prompt-power-cycle --log captures/rcp-primer-sweep-a0-af.txt
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0xC0-0xCF" --prompt-power-cycle --log captures/rcp-primer-sweep-c0-cf.txt
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0x00-0x1F" --prompt-power-cycle --log captures/rcp-primer-sweep-00-1f.txt
```

Recommended first run:

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0xB1 0xB6 0xB7 0xB8 0xB9 0xBA 0xBB 0xBC 0xBD 0xBE 0xBF" --prompt-power-cycle --log captures/rcp-primer-sweep-b1-bf.txt
```

## Primer Reuse and Sequential Query Tests

Two open questions:

1. After a cold boot, does the RCP only answer one selected query before it
   latches/suppresses further responses?
2. Is a fresh primer required before every selected query, or can one primer
   unlock several selected commands in sequence?

Use `scripts/serial_direct_response_sweep.py` for these tests because it can
send arbitrary command sequences without stopping between commands. For each
test below, power-cycle once before starting the script, then do not power-cycle
again until the script exits.

### Test S1: One Primer, Multiple Different Queries

Purpose: test whether one primer can unlock several different selected commands.

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB2 0xB3 0xB4 0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --log captures/rcp-seq-one-primer-b2-b5.txt
```

Interpretation:

- If only `B2` responds, the panel likely allows one selected response per
  cold-boot/primer state.
- If `B2`, `B3`, `B4`, and `B5` all respond, one primer can unlock multiple
  sequential queries.
- If some respond and some do not, there may be command-group or latch behavior.

### Test S2: Primer Before Every Query, No Power Cycle

Purpose: test whether a new primer can re-arm another selected query without
power-cycling.

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB2 0x00 0xB3 0x00 0xB4 0x00 0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --log captures/rcp-seq-reprimer-b2-b5.txt
```

Interpretation:

- If every selected command responds, a primer is required before each query but
  power-cycling is not.
- If only the first selected command responds, power-cycle or another reset-like
  command may be required to clear the latch.

### Test S3: Repeat Same Query With and Without Reprimer

Purpose: test whether the same selected query can be repeated in one powered
session.

Without re-primer:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB5 0xB5 0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --log captures/rcp-seq-repeat-b5-no-reprimer.txt
```

Power-cycle, then with re-primer:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB5 0x00 0xB5 0x00 0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --log captures/rcp-seq-repeat-b5-reprimer.txt
```

Interpretation:

- If only the first `B5` responds in both tests, the response is one-shot until
  power cycle or a yet-unknown reset/ack command.
- If the re-primer version responds repeatedly, the primer re-arms the selected
  query.

### 2026-05-13 Sequential Query Test Result

Captures:

- `captures/rcp-seq-one-primer-b2-b5.txt`
- `captures/rcp-seq-reprimer-b2-b5.txt`
- `captures/rcp-seq-repeat-b5-no-reprimer.txt`
- `captures/rcp-seq-repeat-b5-reprimer.txt`

Valid result:

| Test | Intended sequence | Actual sequence sent | Result |
| --- | --- | --- | --- |
| S1 | `00 -> B2 -> B3 -> B4 -> B5` | `00 -> B2 -> B3 -> B4 -> B5` | only `B2` responded: `07 80 36 10 0C F7` |

Tooling caveat:

- The original `serial_direct_response_sweep.py` de-duplicated command lists.
- Because of that, sequences containing repeated commands did not run as
  intended.
- `S2`, `S3 no re-primer`, and `S3 re-primer` need to be rerun after the
  script fix.
- The script has been updated to preserve repeated command values in explicit
  command lists.

Interpretation from S1:

- One primer did not unlock a whole list of feature/status queries.
- After `00 -> B2` returned `07 80 36 10 0C F7`, later `B3`, `B4`, and `B5`
  in the same powered session did not produce additional non-heartbeat frames.
- This supports a one-response latch model unless the re-primer test proves that
  the primer can re-arm another query.

Rerun these tests after the script fix:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB2 0x00 0xB3 0x00 0xB4 0x00 0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --log captures/rcp-seq-reprimer-b2-b5-v2.txt
```

Power-cycle, then:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB5 0xB5 0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --log captures/rcp-seq-repeat-b5-no-reprimer-v2.txt
```

Power-cycle, then:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0xB5 0x00 0xB5 0x00 0xB5" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --log captures/rcp-seq-repeat-b5-reprimer-v2.txt
```

### 2026-05-13 Sequential Query Rerun Result

Captures:

- `captures/rcp-seq-reprimer-b2-b5-v2.txt`
- `captures/rcp-seq-repeat-b5-no-reprimer-v2.txt`
- `captures/rcp-seq-repeat-b5-reprimer-v2.txt`

These reruns used the fixed `serial_direct_response_sweep.py`, which preserves
repeated command values in explicit sequences.

Results:

| Test | Sequence | Non-heartbeat response(s) |
| --- | --- | --- |
| Re-primer between different queries | `00 -> B2 -> 00 -> B3 -> 00 -> B4 -> 00 -> B5` | only `B2`: `07 80 36 10 0C F7` |
| Repeat `B5`, no re-primer | `00 -> B5 -> B5 -> B5` | only first `B5`: `07 80 6D 20 D8 48` |
| Repeat `B5`, re-primer each time | `00 -> B5 -> 00 -> B5 -> 00 -> B5` | only first `B5`: `07 80 6D 20 D8 48` |

Interpretation:

- The RCP appears to allow only one selected query response per powered session
  in the current `CONNECT NOT ACT` state.
- Sending another primer (`00 00 00 00 80 DA`) after the first response does not
  re-arm the query responder.
- Repeating the same selected query does not produce another response.
- This strongly suggests a one-shot discovery/status response followed by a
  required next-stage command, acknowledgement, reset, or activation step.

Implication for CCU behavior:

- The CCU may not scan a list of feature queries in the current state. It may
  send one discovery/status query, receive one response, then decide what
  activation/session command to send next.
- Alternatively, additional feature reads may require an acknowledgement or
  state-advance command that has not yet been identified.

Recommended next direction:

- Stop broad feature scanning for the moment.
- Search for the post-discovery acknowledgement/activation command that follows
  one known response such as `00 -> B5 => 07 80 6D 20 D8 48`.
- Use a three-step pattern:

```text
primer -> selected query -> candidate activation/ack command
```

Known reproducible setup:

```text
Host -> RCP: 00 00 00 00 80 DA
Host -> RCP: 00 00 B5 00 80 6F
RCP -> Host: 07 80 6D 20 D8 48
Host -> RCP: candidate next-stage command
```

## Post-Discovery Candidate Sweep

Use `scripts/serial_post_discovery_sweep.py` to search for the command that
comes after one known discovery/status response. This is the likely next stage
after the one-shot response behavior.

Default setup:

```text
primer: 00 00 00 00 80 DA
query:  00 00 B5 00 80 6F
RCP:    07 80 6D 20 D8 48
then:   candidate next-stage command
```

Recommended first post-discovery sweep:

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0x00-0x1F" --prompt-power-cycle --prompt-screen --log captures/rcp-post-discovery-b5-candidates-00-1f.txt
```

For each candidate:

1. Power-cycle the RCP.
2. Wait for heartbeat/panel stable.
3. Press Enter at the prompt.
4. Watch for any screen change after the candidate frame.
5. Type the screen state if it changes, or press Enter for no visible change.

Why this range first:

- Earlier frame-length tests showed small command values can change screen state
  to `CONNECT NOT ACT`.
- If a simple ACK/activation command exists, it may be in the low command range.

Next ranges if `00-1F` does not change state:

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0x20-0x3F" --prompt-power-cycle --prompt-screen --log captures/rcp-post-discovery-b5-candidates-20-3f.txt
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0x80-0x9F" --prompt-power-cycle --prompt-screen --log captures/rcp-post-discovery-b5-candidates-80-9f.txt
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0xB0-0xBF" --prompt-power-cycle --prompt-screen --log captures/rcp-post-discovery-b5-candidates-b0-bf.txt
```

If any candidate changes the screen away from `CONNECT NOT ACT`, or produces a
new RCP response after the candidate stage, retest that candidate alone with
three fresh power cycles.

### 2026-05-13 Post-Discovery Sweep `00-1F` Result

Capture:

- `captures/rcp-post-discovery-b5-candidates-00-1f.txt`

Sweep setup:

```text
primer:    00 00 00 00 80 DA
query:     00 00 B5 00 80 6F
expected:  07 80 6D 20 D8 48
candidate: 00-1F
```

Screen result:

- Every candidate remained at `CONNECT NOT ACT` / `CONNECTION NOT ACT`.
- No candidate in `0x00-0x1F` moved the panel into an active state.

Serial result:

- Most candidates produced only heartbeat-compatible traffic after the candidate
  frame.
- Candidate windows for `0x00`, `0x0E`, `0x0F`, `0x1A`, `0x1E`, and `0x1F`
  included additional bytes matching the known discovery response
  `07 80 6D 20 D8 48`.
- Those candidate-window anomalies are likely trailing/repeated discovery
  response frames from the `B5` query, not new candidate-specific responses.

Notable outlier:

- During candidate `0x03`, the primer read window contained
  `07 80 40 40 30 ED`, followed by heartbeat.
- This is checksum-valid, but it occurred before the `B5` query in that test
  window. Treat it as an outlier until reproduced. Possible explanations include
  incomplete power-cycle reset, a previous state/latch edge, or an accidental
  timing artifact.

Interpretation:

- Low command range `0x00-0x1F` does not appear to contain the simple
  post-discovery activation command when tested after the `B5` discovery query.
- The script's candidate read window can still catch residual discovery
  response frames; candidate anomalies must be checked against the known query
  response before treating them as new behavior.

Recommended next sweep:

Use a slightly longer query read window so the known discovery response has more
time to finish before the candidate frame:

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0x20-0x3F" --after-query 2.0 --prompt-power-cycle --prompt-screen --log captures/rcp-post-discovery-b5-candidates-20-3f.txt
```

If `20-3F` also keeps the screen at `CONNECT NOT ACT`, continue:

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0x80-0x9F" --after-query 2.0 --prompt-power-cycle --prompt-screen --log captures/rcp-post-discovery-b5-candidates-80-9f.txt
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0xB0-0xBF" --after-query 2.0 --prompt-power-cycle --prompt-screen --log captures/rcp-post-discovery-b5-candidates-b0-bf.txt
```

## Post-Discovery Test Ladder

Before manually sweeping every command byte, sample representative patterns from
several command regions. The goal is to identify which command families are
worth expanding.

Use the same known discovery setup for each sample:

```text
primer: 00 00 00 00 80 DA
query:  00 00 B5 00 80 6F
RCP:    07 80 6D 20 D8 48
then:   sampled candidate command
```

Power-cycle before each candidate prompt. Type any screen change, otherwise
press Enter.

### Ladder 1: Low-Range Sanity Sample

The full `00-1F` sweep did not activate the panel, but one outlier appeared
during the `0x03` test. Retest only representative low bytes plus the outlier:

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0x00 0x01 0x03 0x07 0x0F 0x10 0x1B 0x1F" --after-query 2.0 --prompt-power-cycle --prompt-screen --log captures/rcp-post-ladder-low-sample.txt
```

What this checks:

- `0x00`, `0x01`: no-op / ACK-like small commands.
- `0x03`: the outlier run produced `07 80 40 40 30 ED`.
- `0x07`, `0x0F`, `0x1F`: bit-mask/boundary values.
- `0x10`, `0x1B`: response command-family values observed in RCP frames.

### Ladder 2: Response-Command Echo Sample

Test host commands that match command bytes seen in RCP responses. If the CCU
acknowledges or advances using related command IDs, these are good candidates.

Observed RCP response command bytes so far:

```text
1B 36 40 6C 6D
```

Run:

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0x1B 0x36 0x40 0x6C 0x6D" --after-query 2.0 --prompt-power-cycle --prompt-screen --log captures/rcp-post-ladder-response-cmds.txt
```

Expand only if one of these changes screen state or produces a new
candidate-stage response.

### Ladder 3: Boundary and Bit-Pattern Sample

This tests command bytes that often mark command classes, flags, or boundaries:

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0x20 0x2F 0x30 0x3F 0x40 0x4F 0x50 0x5F 0x7F 0x80 0x8F 0x90 0x9F 0xA0 0xAF 0xB0 0xBF 0xC0 0xCF 0xE0 0xEF 0xF0 0xFF" --after-query 2.0 --prompt-power-cycle --prompt-screen --log captures/rcp-post-ladder-boundaries.txt
```

What this checks:

- Nibble/region boundaries.
- The high-bit transition at `0x80`.
- Known discovery query region around `0xB0`.
- High command space around `0xE0-0xFF`.

### Ladder 4: Known Query Region Sample

The `B0` range is known to produce discovery/status responses when used as the
selected query. It may also contain a next-stage command.

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --candidates "0xB0 0xB1 0xB2 0xB3 0xB4 0xB5 0xB6 0xB7 0xB8 0xBF" --after-query 2.0 --prompt-power-cycle --prompt-screen --log captures/rcp-post-ladder-b-region.txt
```

If any `B` candidate changes behavior, expand locally around it rather than
sweeping the full byte space.

### Ladder 5: Alternate Discovery Response Bases

If all candidate ladders after `00 -> B5` leave the screen at `CONNECT NOT ACT`,
try the same sampled candidates after a different discovery query. Different RCP
responses may expect different follow-up ACKs.

Use `B2` discovery:

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --query-command 0xB2 --candidates "0x00 0x01 0x1B 0x36 0x40 0x6C 0x6D 0x80 0xB0 0xB5 0xFF" --after-query 2.0 --prompt-power-cycle --prompt-screen --log captures/rcp-post-ladder-after-b2.txt
```

Use `B0` discovery:

```powershell
python scripts/serial_post_discovery_sweep.py --port COM5 --query-command 0xB0 --candidates "0x00 0x01 0x1B 0x36 0x40 0x6C 0x6D 0x80 0xB0 0xB5 0xFF" --after-query 2.0 --prompt-power-cycle --prompt-screen --log captures/rcp-post-ladder-after-b0.txt
```

Interpretation:

- If a candidate only works after one discovery response, the next-stage command
  may depend on the returned block.
- If the same candidate works after multiple discovery responses, it is a
  stronger activation/ACK candidate.

### When to Expand

Expand a region only when one of these occurs:

- Screen changes away from `CONNECT NOT ACT`.
- RCP sends a new candidate-stage frame that is not the known discovery response
  trailing into the candidate window.
- The panel begins sending different heartbeat/status frames after the
  candidate.

If none of the ladder samples produce a new behavior, stop command-byte guessing
and test other frame fields for the candidate stage: state byte, value byte, or
prefix bytes.

### 2026-05-13 Ladder 1 Result and Keepalive Hypothesis

Capture:

- `captures/rcp-post-ladder-low-sample.txt`

Ladder 1 candidates:

```text
00 01 03 07 0F 10 1B 1F
```

Result:

- Every sampled candidate left the screen at `CONNECT NOT ACT`.
- Candidate-stage RX was heartbeat-compatible for all candidates.
- The earlier `0x03` outlier did not reproduce.
- Query-stage response `07 80 6D 20 D8 48` reproduced reliably before each
  candidate.

Interpretation:

- The low/outlier sample did not find a post-discovery activation/ACK command.
- `CONNECT NOT ACT` may be unrelated to a one-shot ACK. It may mean the RCP sees
  host traffic but is not receiving the correct ongoing CCU heartbeat/session
  cadence.

Alternative working model:

```text
Host sends discovery/status query
RCP answers once
Host must then send a sustained keepalive/session heartbeat
RCP remains CONNECT NOT ACT until that heartbeat/cadence is correct
```

### Keepalive After Discovery Tests

Use `scripts/serial_keepalive_after_query.py` to test whether a sustained host
heartbeat changes the RCP state after a known discovery response.

Default setup:

```text
primer:    00 00 00 00 80 DA
query:     00 00 B5 00 80 6F
RCP reply: 07 80 6D 20 D8 48
keepalive: repeated candidate frame
```

Test K1: repeat the known primer/heartbeat shape:

```powershell
python scripts/serial_keepalive_after_query.py --port COM5 --keepalive-command 0x00 --duration 15 --interval 0.6 --prompt-screen --log captures/rcp-keepalive-after-b5-cmd00-600ms.txt
```

Test K2: repeat the zero-state frame:

```powershell
python scripts/serial_keepalive_after_query.py --port COM5 --keepalive-frame "00 00 00 00 00 5A" --duration 15 --interval 0.6 --prompt-screen --log captures/rcp-keepalive-after-b5-zero-state-600ms.txt
```

Test K3: repeat the alternate state frame:

```powershell
python scripts/serial_keepalive_after_query.py --port COM5 --keepalive-frame "00 00 00 80 00 DA" --duration 15 --interval 0.6 --prompt-screen --log captures/rcp-keepalive-after-b5-state80-600ms.txt
```

Test K4: faster primer/heartbeat cadence:

```powershell
python scripts/serial_keepalive_after_query.py --port COM5 --keepalive-command 0x00 --duration 15 --interval 0.2 --prompt-screen --log captures/rcp-keepalive-after-b5-cmd00-200ms.txt
```

Power-cycle before each keepalive test. Watch for:

- Screen changing away from `CONNECT NOT ACT`.
- Pin 4 changing from heartbeat to another recurring status frame.
- RCP controls beginning to transmit additional button/status data.

### 2026-05-13 Keepalive After Discovery Result

Captures:

- `captures/rcp-keepalive-after-b5-cmd00-600ms.txt`
- `captures/rcp-keepalive-after-b5-zero-state-600ms.txt`
- `captures/rcp-keepalive-after-b5-state80-600ms.txt`
- `captures/rcp-keepalive-after-b5-cmd00-200ms.txt`

Result:

| Test | Keepalive frame | Cadence | Screen result | Pin 4 RX |
| --- | --- | --- | --- | --- |
| K1 | `00 00 00 00 80 DA` | 0.6 s | `CONNECT NOT ACT` | heartbeat-compatible |
| K2 | `00 00 00 00 00 5A` | 0.6 s | `CONNECT NOT ACT` | heartbeat-compatible |
| K3 | `00 00 00 80 00 DA` | 0.6 s | `CONNECT NOT ACT` | heartbeat-compatible |
| K4 | `00 00 00 00 80 DA` | 0.2 s | `CONNECT NOT ACT` | heartbeat-compatible |

Interpretation:

- A simple sustained host heartbeat after `00 -> B5` does not activate the RCP.
- The RCP continues emitting only the known heartbeat-compatible stream on pin
  4 during these keepalive attempts.
- The correct next stage is probably not just "repeat the primer" or "hold a
  no-op frame at CCU cadence".
- The better next branch is to map additional `primer -> request` commands that
  cause one-shot RCP responses. Those response blocks may reveal the command
  families, status bits, or identity data needed for the later activation step.

Recommended next request sweep:

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0xB1 0xB6 0xB7 0xB8 0xB9 0xBA 0xBB 0xBC 0xBD 0xBE 0xBF" --prompt-power-cycle --log captures/rcp-primer-sweep-b1-bf.txt
```

Power-cycle before each candidate prompt. This fills the gaps around the known
`B0-B5` discovery/status region and checks whether `B8-BF` contain additional
one-shot readable blocks.

If this range is quiet, continue with neighboring command regions:

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0xA0-0xAF" --prompt-power-cycle --log captures/rcp-primer-sweep-a0-af.txt
python scripts/serial_primer_candidate_sweep.py --port COM5 --candidates "0xC0-0xCF" --prompt-power-cycle --log captures/rcp-primer-sweep-c0-cf.txt
```

### 2026-05-13 Primer Sweep A/B/C Region Result

Captures:

- `captures/rcp-primer-sweep-a0-af.txt`
- `captures/rcp-primer-sweep-b1-bf.tx`
- `captures/rcp-primer-sweep-c0-cf.txt`

These sweeps used a fresh power cycle before each candidate, with the standard
primer shape before the selected request:

```text
primer:    00 00 00 00 80 DA
candidate: 00 00 CMD 00 80 CHECKSUM
```

New selected-command response map:

| Selected command | Observed RCP response |
| ---: | --- |
| `A0` | `07 80 68 40 30 C5` |
| `A1` | `07 80 68 20 D8 4D` |
| `A2` | `07 80 34 10 0C F5` |
| `A3` | `07 80 34 10 2C D5` |
| `A4` | `07 80 69 40 30 C4` |
| `A5` | `07 80 69 20 D8 4C` |
| `A6` | `07 80 1A 08 C6 09` |
| `A7` | `07 80 1A 08 D6 19` |
| `A8` | `07 80 6A 40 30 C7` |
| `A9` | `07 80 6A 20 D8 4F` |
| `AA` | `07 80 35 10 0C F4` |
| `AB` | `07 80 35 10 2C D4` |
| `AC` | `07 80 6B 40 30 C6` |
| `AD` | `07 80 6B 20 D8 4E` |
| `AE` | `07 80 0D 04 A3 77` |
| `AF` | `07 80 0D 04 AB 7F` |
| `B1` | `07 80 6C 20 D8 49` |
| `B6` | `07 80 1B 08 C6 08` |
| `B7` | `07 80 1B 08 F6 38` |
| `B8` | `07 80 EE 40 30 43` |
| `B9` | `07 80 6E 20 D8 4B` |
| `BA` | `07 80 37 10 0C F6` |
| `BB` | `07 80 37 10 2C D6` |
| `BC` | `07 80 EF 40 30 42` |
| `BD` | `07 80 6F 20 D8 4A` |
| `BE` | heartbeat only |
| `BF` | heartbeat only |
| `C0` | heartbeat only |
| `C1` | `07 80 70 20 D8 55` |
| `C2` | `07 80 38 10 0C F9` |
| `C3` | `07 80 38 10 2C D9` |
| `C4` | `07 80 71 40 30 DC` |
| `C5` | `07 80 71 20 D8 54` |
| `C6` | `07 80 1C 08 C6 0F` |
| `C7` | `07 80 1C 08 D6 1F` |
| `C8` | `07 80 72 40 30 DF` |
| `C9` | `07 80 72 20 D8 57` |
| `CA` | `07 80 39 10 0C F8` |
| `CB` | `07 80 39 10 2C D8` |
| `CC` | `07 80 F3 40 30 5E` |
| `CD` | `07 80 73 20 D8 56` |
| `CE` | `07 80 0E 04 A3 74` |
| `CF` | `07 80 0E 04 AB 7C` |

Interpretation:

- The RCP has a much larger one-shot readable status/query surface than first
  assumed.
- The `A0-CF` region looks highly structured. Most commands return stable
  six-byte responses with the same `07 80` prefix and valid XOR checksum.
- Pairs often share a response command byte and differ in state/value fields:
  `A0/A1`, `A2/A3`, `A4/A5`, `A6/A7`, and similar patterns continue through
  the `B` and `C` regions.
- `BE`, `BF`, and `C0` are current no-response candidates in this mapping.
- This strongly supports a discovery/status table model: the CCU may read a
  specific set of one-shot blocks, then choose a later activation/session
  command based on the returned table values.

### 2026-05-13 Paused Direct Sweep Result

Capture:

- `captures/rcp-direct-remaining-after-b5-pause.txt`

The paused direct sweep logged anomalies and then allowed a manual power cycle
before continuing. Because the script continues with the next command after the
pause, this run is useful for finding response-producing commands, but it is
not a clean `00 -> B5 -> candidate` post-discovery sweep.

Response hits observed in this run:

| Command at anomaly | Observed RCP response | Caution |
| ---: | --- | --- |
| `B5` | `07 80 6D 20 D8 48` | expected known query response |
| `40` | `07 80 50 40 30 FD` | repeated twice in this run |
| `6D` | `07 80 5B 20 D8 7E` | may depend on prior `6C` |
| `4F` | `07 80 0A 04 AB 78` | needs clean one-per-boot confirmation |
| `8F` | `07 80 0C 04 AB 7E` | may depend on prior sequence |
| `A0` | `07 80 E8 40 30 45` | differs from primer-sweep `A0` response |
| `B0` | `07 80 6C 40 30 C1` | known response |
| `CF` | `07 80 0E 04 AB 7C` | matches primer-sweep `CF` response |
| `EF` | `07 80 0F 04 EB 3D` | needs clean one-per-boot confirmation |
| `B1` | `07 80 6C 20 D8 49` | known response |
| `B3` | `07 80 36 10 2C D7` | known response |
| `B6` | `07 80 1B 08 C6 08` | known response |
| `B8` | `07 80 6E 40 30 C3` | differs from primer-sweep `B8` response |
| `BA` | `07 80 37 10 0C F6` | matches primer-sweep `BA` response |
| `BC` | `07 80 6F 40 30 C2` | differs from primer-sweep `BC` response |

Next confirmations:

- Retest `40`, `4F`, `8F`, `EF`, and the differing `A0/B8/BC` cases as clean
  one-per-boot primer pairs.
- If a response differs between a plain/direct command and a primer-pair query,
  treat the first host frame as a mode/context selector rather than only a
  generic wake-up primer.

## Context Selector Confirmation Tests

Goal: confirm whether the first host frame is only a generic primer, or whether
it selects a response page/context for the next command.

The test method is to hold the second/query command constant and change only
the first frame:

```text
selector/primer -> selected query
```

Strong confirmation:

- Same selected query, different first frame, different RCP response.
- Example pattern: `00 -> B8` returns one block while `B7 -> B8` returns a
  different block.

Weak or negative result:

- Same selected query always returns the same block regardless of the first
  frame.
- The differing blocks from the paused direct sweep were caused by longer
  sequence/timing effects rather than a two-frame selector.

### Test CS1: Known Generic `00` Page

This rechecks the current generic-primer page for the commands that had
different-looking responses in the paused direct sweep.

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --primer-command 0x00 --candidates "0xA0 0xB8 0xBC" --prompt-power-cycle --log captures/rcp-context-selector-00-a0-b8-bc.txt
```

Expected from prior primer sweeps:

| Pair | Expected response |
| --- | --- |
| `00 -> A0` | `07 80 68 40 30 C5` |
| `00 -> B8` | `07 80 EE 40 30 43` |
| `00 -> BC` | `07 80 EF 40 30 42` |

### Test CS2: Suspected Alternate Selectors

These test the pairings implied by the paused direct sweep. Power-cycle before
each prompt.

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --primer-command 0x9F --candidates 0xA0 --prompt-power-cycle --log captures/rcp-context-selector-9f-a0.txt
python scripts/serial_primer_candidate_sweep.py --port COM5 --primer-command 0xB7 --candidates 0xB8 --prompt-power-cycle --log captures/rcp-context-selector-b7-b8.txt
python scripts/serial_primer_candidate_sweep.py --port COM5 --primer-command 0xBB --candidates 0xBC --prompt-power-cycle --log captures/rcp-context-selector-bb-bc.txt
python scripts/serial_primer_candidate_sweep.py --port COM5 --primer-command 0xAF --candidates 0xB0 --prompt-power-cycle --log captures/rcp-context-selector-af-b0.txt
```

Compare against these paused/direct observations:

| Pair under test | Context hypothesis if reproduced |
| --- | --- |
| `9F -> A0` | `A0` may return `07 80 E8 40 30 45` after selector `9F`. |
| `B7 -> B8` | `B8` may return `07 80 6E 40 30 C3` after selector `B7`. |
| `BB -> BC` | `BC` may return `07 80 6F 40 30 C2` after selector `BB`. |
| `AF -> B0` | `B0` may return `07 80 6C 60 30 E1` after selector `AF`. |

### Test CS3: Check for Three-Frame Context

The paused sweep's `A0` response happened after `90` and `9F` had both been
sent in the same powered session. If `9F -> A0` does not reproduce the alternate
`A0` block, try the full three-frame setup:

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x90 0x9F 0xA0" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --after 3 --log captures/rcp-context-seq-90-9f-a0.txt
```

Power-cycle once before this test and do not power-cycle until the script
exits.

Interpretation:

- If `9F -> A0` reproduces `07 80 E8 40 30 45`, a two-frame selector is likely.
- If only `90 -> 9F -> A0` reproduces it, the context/page setup may require
  multiple host frames.
- If neither reproduces it, treat the paused direct `A0` response as a
  sequence/timing artifact until another capture confirms it.

### Optional Single-Frame Controls

These check whether the candidate can respond as the first frame after boot.

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --commands 0xA0 --states 0x00 --values 0x80 --settle 3 --after-each 1.5 --after 2 --log captures/rcp-context-single-a0.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands 0xB8 --states 0x00 --values 0x80 --settle 3 --after-each 1.5 --after 2 --log captures/rcp-context-single-b8.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands 0xBC --states 0x00 --values 0x80 --settle 3 --after-each 1.5 --after 2 --log captures/rcp-context-single-bc.txt
```

Power-cycle before each single-frame control.

### 2026-05-13 Context Selector Dataset Results

New captures:

- `captures/rcp-context-selector-00-a0-b8-bc.txt`
- `captures/rcp-context-selector-9f-a0.txt`
- `captures/rcp-context-selector-b7-b8.txt`
- `captures/rcp-context-selector-bb-bc.txt`
- `captures/rcp-context-selector-af-b0.txt`
- `captures/rcp-context-seq-90-9f-a0.txt`
- `captures/rcp-context-single-a0.txt`
- `captures/rcp-context-single-b8.txt`
- `captures/rcp-context-single-bc.txt`

Observed results:

| Test | Sequence | Observed response |
| --- | --- | --- |
| CS1 | `00 -> A0` | `07 80 E8 40 30 45` |
| CS1 | `00 -> B8` | `07 80 6E 40 30 C3` |
| CS1 | `00 -> BC` | `07 80 6F 40 30 C2` |
| CS2 | `9F -> A0` | heartbeat only |
| CS2 | `B7 -> B8` | `07 80 6E 40 30 C3` |
| CS2 | `BB -> BC` | `07 80 6F 40 30 C2` |
| CS2 | `AF -> B0` | `07 80 6C 40 30 C1` |
| CS3 | `90 -> 9F -> A0` | `07 80 68 40 30 C5` |
| Single-frame control | `A0` | heartbeat only |
| Single-frame control | `B8` | heartbeat only |
| Single-frame control | `BC` | heartbeat only |

Important comparison against earlier sweeps:

| Selected query | Earlier primer sweep response | New `00 -> query` response |
| ---: | --- | --- |
| `A0` | `07 80 68 40 30 C5` | `07 80 E8 40 30 45` |
| `B8` | `07 80 EE 40 30 43` | `07 80 6E 40 30 C3` |
| `BC` | `07 80 EF 40 30 42` | `07 80 6F 40 30 C2` |

Interpretation:

- Single `A0`, `B8`, and `BC` frames after boot produced heartbeat only, so
  these responses require prior host traffic.
- The response is not determined only by the selected query command. The same
  selected query can produce different response blocks in different setup
  contexts.
- `B7 -> B8` and `BB -> BC` reproduced the alternate `B8`/`BC` responses seen
  in the paused direct sweep.
- `90 -> 9F -> A0` reproduced the earlier `A0` response
  `07 80 68 40 30 C5`, while `9F -> A0` alone produced no response.
- `00 -> A0` now produced the alternate `A0` response
  `07 80 E8 40 30 45`, so the `00` first frame is not always a simple
  deterministic "generic primer" in the current bench state.
- The evidence now favors a stateful/page-sensitive discovery model rather than
  a single fixed primer model.

Working model after these datasets:

```text
Host sends one or more setup/selector frames.
RCP arms one readable response.
The next selected query returns a block from the currently selected page/state.
After that response, the RCP latches until power cycle or an unknown reset/state
advance command.
```

Recommended next confirmation:

1. Repeat `00 -> A0`, `00 -> B8`, and `00 -> BC` once more after clean power
   cycles to see whether the alternate page is now stable.
2. Repeat `90 -> 9F -> A0` once more to confirm the earlier page can be selected
   reliably.
3. Test whether `90 -> A0` alone selects the earlier page, or whether `9F` is
   also required.
4. Test whether `00 -> 9F -> A0` behaves like `90 -> 9F -> A0`, which would
   suggest `9F` is the real selector and `90` is only a setup/arming frame.

Suggested commands:

```powershell
python scripts/serial_primer_candidate_sweep.py --port COM5 --primer-command 0x00 --candidates "0xA0 0xB8 0xBC" --prompt-power-cycle --log captures/rcp-context-repeat-00-a0-b8-bc.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x90 0x9F 0xA0" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --after 3 --log captures/rcp-context-repeat-90-9f-a0.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x90 0xA0" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --after 3 --log captures/rcp-context-seq-90-a0.txt
python scripts/serial_direct_response_sweep.py --port COM5 --commands "0x00 0x9F 0xA0" --states 0x00 --values 0x80 --settle 3 --after-each 0.8 --after 3 --log captures/rcp-context-seq-00-9f-a0.txt
```

### 2026-05-13 Context Confirmation Result

New captures:

- `captures/rcp-context-repeat-00-a0-b8-bc.txt`
- `captures/rcp-context-repeat-90-9f-a0.txt`
- `captures/rcp-context-seq-90-a0.txt`
- `captures/rcp-context-seq-00-9f-a0.txt`

Observed results:

| Test | Sequence | Observed response |
| --- | --- | --- |
| Repeat `00` page | `00 -> A0` | `07 80 68 40 30 C5` |
| Repeat `00` page | `00 -> B8` | `07 80 6E 40 30 C3` |
| Repeat `00` page | `00 -> BC` | `07 80 6F 40 30 C2` |
| Repeat three-frame A0 | `90 -> 9F -> A0` | `07 80 68 40 30 C5` |
| A0 with `90` only | `90 -> A0` | `07 80 68 40 30 C5` |
| A0 with `00` then `9F` | `00 -> 9F -> A0` | `07 80 68 40 30 C5` |

Updated interpretation:

- `90 -> A0` is enough to produce the `A0` response
  `07 80 68 40 30 C5`; `9F` is not required for that page.
- `00 -> 9F -> A0` also produces `07 80 68 40 30 C5`, so a frame before `9F`
  can arm the response, but `9F` does not appear to select the alternate `A0`
  response by itself.
- The repeat `00 -> A0` result returned to `07 80 68 40 30 C5`, while the
  previous `00 -> A0` context dataset returned `07 80 E8 40 30 45`. Treat the
  `E8` response as real but not yet deterministic from the current two-frame
  model.
- `00 -> B8` and `00 -> BC` remained stable as `07 80 6E 40 30 C3` and
  `07 80 6F 40 30 C2`, matching the earlier alternate-page observations.

Current best model:

```text
The RCP requires at least one setup frame before many query commands respond.
Some setup/query pairs are stable, for example 90 -> A0 and 00 -> B8.
Some response differences are still not explained by only the immediately
preceding frame, so a hidden boot/session/state bit or timing-sensitive page
selection may also be involved.
```

Next useful tests:

1. Retest `00 -> A0` several times in a row with a power cycle before each run
   to measure whether `68` or `E8` is the dominant response.
2. Try direct pairs for the observed alternate `B8/BC` family:
   `B8 -> B9`, `BC -> BD`, and `BD -> BE`.
3. Sweep the `D0-DF` region with the same primer-pair method to see whether the
   structured discovery table continues after `CF`.

## Unlatch / State-Advance Sweep

Goal: intentionally put the RCP into the known one-response latched state, send
a wide set of possible reset/ack/state-advance commands, then verify whether a
known query can respond again without a power cycle.

Use `scripts/serial_unlatch_sweep.py`. For each candidate it performs:

```text
latch primer -> latch query -> candidate unlatch command -> verify primer -> verify query
```

Default latch and verify sequence:

```text
00 -> B5        produces known response: 07 80 6D 20 D8 48
candidate       possible unlatch / state advance command
00 -> B5        verify whether the known response can happen again
```

Interpretation:

- If the verify query returns heartbeat only, the candidate did not unlatch the
  one-response state.
- If the verify query returns `07 80 6D 20 D8 48` again, the candidate likely
  cleared or advanced the latch.
- If the candidate itself changes the LCD or produces a new serial response,
  log it as a possible state-advance command even if the verify query does not
  respond.

First wide assortment, focused on known response command families, boundaries,
and current no-response gaps:

```powershell
python scripts/serial_unlatch_sweep.py --port COM5 --candidates "0x00 0x01 0x03 0x07 0x0A 0x0C 0x0D 0x0E 0x0F 0x10 0x1A 0x1B 0x1C 0x20 0x30 0x36 0x38 0x39 0x40 0x4F 0x50 0x5B 0x68 0x6C 0x6D 0x6E 0x6F 0x70 0x7F 0x80 0x8F 0x90 0x9F 0xA0 0xAF 0xB0 0xB5 0xB8 0xBC 0xBE 0xBF 0xC0 0xCF 0xD0 0xDF 0xE0 0xEF 0xF0 0xFF" --expected-verify-response "07 80 6D 20 D8 48" --prompt-power-cycle --prompt-screen --log captures/rcp-unlatch-wide-after-b5.txt
```

Power-cycle before each prompt. For the screen prompt:

- Press Enter if the screen stayed the same.
- Type the exact screen text if it changes.
- Type `q` to stop.

If this finds no verify responses, try the same idea after a different latch
query page:

```powershell
python scripts/serial_unlatch_sweep.py --port COM5 --latch-query-command 0xA0 --verify-query-command 0xA0 --candidates "0x00 0x01 0x0F 0x10 0x1A 0x1B 0x40 0x4F 0x68 0x6C 0x80 0x8F 0x90 0x9F 0xA0 0xB0 0xB8 0xBC 0xC0 0xCF 0xE0 0xEF 0xFF" --expected-verify-response "07 80 68 40 30 C5" --prompt-power-cycle --prompt-screen --log captures/rcp-unlatch-wide-after-a0.txt
```

For a fast dry run without touching the serial port:

```powershell
python scripts/serial_unlatch_sweep.py --port COM5 --candidates "0x00 0x01 0x90" --dry-run
```

### 2026-05-13 Unlatch Sweep Result

Captures:

- `captures/rcp-unlatch-wide-after-b5.txt`
- `captures/rcp-unlatch-wide-after-a0.txt`

User observation:

- No visible RCP state change was seen during the tests.
- The only screen note recorded in the logs was `CONNECT NOT ACT` after
  candidate `0x00`, which matches the already-known non-active connected state.

Serial result:

| Latch/verify query | Candidates tested | Expected verify response | Result |
| --- | ---: | --- | --- |
| `B5` | 49 | `07 80 6D 20 D8 48` | no confirmed unlatch |
| `A0` | 23 | `07 80 68 40 30 C5` | no confirmed unlatch |

Notes:

- Candidate `0xBF` in the `B5` unlatch sweep produced a verify-query anomaly,
  but the raw bytes were `00 00 00 00 00 00 80 DA`, not the expected
  `07 80 6D 20 D8 48`. Treat this as a heartbeat/chunking/classifier artifact,
  not a successful unlatch.
- No candidate-stage serial response clearly indicated a state advance.
- The broad command-byte assortment did not find a reset/ack/unlatch command in
  the tested six-byte frame shape.

Tooling update:

- `scripts/serial_unlatch_sweep.py` now accepts `--expected-verify-response`.
- Future unlatch sweeps should use this option so only the desired repeated
  query response counts as a hit.

## PT2/PT7 Compatibility Clue

Manual-derived note from a newer Sony RCP:

- A newer Sony RCP has a mode switch with `PT2` and `PT7` positions.
- `PT2` is documented for controlling the same camera line that the RCP-TX7 is
  associated with.

Working implication:

- The TX7 may be a fixed/PT2-era protocol personality rather than a generic
  Sony RCP protocol endpoint.
- If the CCU/RCP protocol family later split into PT2 and PT7 modes, then our
  current frame shape may be electrically correct but still missing a
  personality/mode/session assumption.
- The command bytes that look like "page selectors" may be table reads within
  the PT2 personality rather than an activation handshake.

Next direction from this clue:

1. Treat PT2 compatibility as the default target for TX7 restoration tests.
2. Search newer-RCP manuals for whether PT2/PT7 is only a physical switch or
   whether either mode has visible initialization, connect, or model-detect
   behavior.
3. Prefer tests that emulate a CCU already speaking the TX7/PT2 personality,
   rather than trying PT7-style/high-range activation guesses.

## Button Behavior While Latched

Open questions:

1. Does the one-response latched state suppress the RCP-origin button frames
   that are known to appear while disconnected?
2. If the RCP sends `CAM POWER`, does an immediate host-side response using the
   same or a similar command shape change the RCP state?

Known RCP-origin button frames:

| Button/action | RCP frame |
| --- | --- |
| Idle heartbeat | `00 00 00 00 80 DA` |
| `CAM POWER` | `00 00 07 80 00 DD` |
| `CALL` on/state high | `00 00 15 80 00 CF` |
| `CALL` off/state low | `00 00 15 00 00 4F` |

Use `scripts/serial_button_response_test.py` for these tests. It keeps RX and
TX in the same serial session, so it works around Windows COM-port exclusivity.

### Test BTN1: Offline Button Control

Purpose: establish a fresh control capture where the RCP is not intentionally
latched. During the listen window, press `CAM POWER` a few times and press/release
`CALL`.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 30 --prompt --log captures/rcp-buttons-offline-control.txt
```

Expected:

- `CAM POWER` should produce `00 00 07 80 00 DD`.
- `CALL` should produce one or both `00 00 15 80 00 CF` and
  `00 00 15 00 00 4F`.

### Test BTN2: Latched Button Emission

Purpose: put the RCP into the known `00 -> B5` latched state, then see whether
`CAM POWER` and `CALL` still produce RCP-origin frames. During the listen
window, press the same buttons as BTN1.

```powershell
python scripts/serial_button_response_test.py --port COM5 --latch --latch-query-command 0xB5 --duration 30 --prompt --log captures/rcp-buttons-latched-after-b5.txt
```

Interpretation:

- If `CAM POWER`/`CALL` frames still appear, the latch suppresses selected query
  responses but does not suppress basic panel-origin button events.
- If button frames disappear, the latch may be closer to a protocol/session
  hold state that blocks some panel event transmission.

### Test BTN3: Respond to `CAM POWER` With Exact Echo

Purpose: when the RCP sends the `CAM POWER` frame, immediately send the same
six-byte frame back on the host-to-RCP line. Watch the screen for any visible
change.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 30 --prompt --respond-to-cam-power --respond-once --response-frame "00 00 07 80 00 DD" --log captures/rcp-buttons-cam-power-exact-echo.txt
```

At the prompt, press `CAM POWER` once. If the screen changes, note the exact
display text after the run.

### Test BTN4: Respond to `CAM POWER` With Host-Shaped Variant

Purpose: test a related command-shaped host frame where command `0x07` is kept
but the `0x80` bit is in the value field, matching the host-query shape used in
many discovery tests.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 30 --prompt --respond-to-cam-power --respond-once --response-frame "00 00 07 00 80 DD" --log captures/rcp-buttons-cam-power-host-shaped.txt
```

Interpretation:

- If exact echo changes nothing but the host-shaped variant changes the screen
  or serial stream, the RCP may expect host responses in the same command class
  but with host-side state/value layout.
- If neither changes anything, `CAM POWER` may be an outbound event requiring a
  larger CCU state/session response rather than a direct ACK.

Optional latched version of BTN3:

```powershell
python scripts/serial_button_response_test.py --port COM5 --latch --latch-query-command 0xB5 --duration 30 --prompt --respond-to-cam-power --respond-once --response-frame "00 00 07 80 00 DD" --log captures/rcp-buttons-latched-cam-power-exact-echo.txt
```

### 2026-05-13 Button Test Result

Captures:

- `captures/rcp-buttons-offline-control.txt`
- `captures/rcp-buttons-latched-after-b5.txt`
- `captures/rcp-buttons-cam-power-exact-echo.txt`
- `captures/rcp-buttons-cam-power-host-shaped.txt`

Offline control:

- `CALL` produced `00 00 15 80 00 CF` and `00 00 15 00 00 4F`.
- `CAM POWER` produced `00 00 07 80 00 DD`.
- This matches the original offline button observations.

Latched after `00 -> B5`:

- The latch setup produced the known `B5` response
  `07 80 6D 20 D8 48`.
- While in this state, `CALL` still produced both known call frames.
- While in this state, `CAM POWER` still produced `00 00 07 80 00 DD`.

Interpretation:

- The one-response latch suppresses additional selected-query responses, but it
  does not suppress basic RCP-origin button/event frames.
- The panel is still actively reporting front-panel events after the discovery
  response/latch state.

`CAM POWER` response tests:

| Test | Host response sent after `CAM POWER` | Screen result | Serial result |
| --- | --- | --- | --- |
| BTN3 exact echo | `00 00 07 80 00 DD` | `CONNECT NOT ACT` | heartbeat/button-event only |
| BTN4 host-shaped | `00 00 07 00 80 DD` | `CONNECT NOT ACT` | heartbeat only after response |

Interpretation:

- Both `CAM POWER` response shapes are recognized enough to produce the familiar
  `CONNECT NOT ACT` display state.
- Neither response advanced the RCP into an active state or caused a new serial
  status stream.
- `CAM POWER` is likely an outbound event that requires broader CCU/session
  context, not a simple one-frame ACK.

Next useful button-side tests:

1. Repeat BTN3/BTN4 while already latched after `00 -> B5`; this checks whether
   the same `CAM POWER` response has different meaning after the RCP has already
   returned a discovery block.
2. Try responding to `CALL` with exact echo and host-shaped command `0x15`
   variants, since earlier `0x15` matrix tests changed the screen but did not
   activate the panel.
3. If a future session/keepalive candidate is found, rerun BTN1/BTN2 to see
   whether more front-panel controls begin emitting serial events in an active
   context.

### CALL Echo / Response Tests

Goal: test whether the RCP treats `CALL` differently from `CAM POWER` when the
host immediately echoes or acknowledges the outbound button event.

Known RCP-origin `CALL` frames:

```text
CALL high/on:  00 00 15 80 00 CF
CALL low/off:  00 00 15 00 00 4F
```

Test BTN5: exact echo of both known `CALL` shapes.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 30 --prompt --respond-to-call --respond-once --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --log captures/rcp-buttons-call-exact-echo.txt
```

Test BTN6: host-shaped `CALL` response, with command `0x15` and value `0x80`.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 30 --prompt --respond-to-call --respond-once --response-frame "00 00 15 00 80 CF" --log captures/rcp-buttons-call-host-shaped.txt
```

Test BTN7: host-shaped `CALL` response after the known `00 -> B5` latch.

```powershell
python scripts/serial_button_response_test.py --port COM5 --latch --latch-query-command 0xB5 --duration 30 --prompt --respond-to-call --respond-once --response-frame "00 00 15 00 80 CF" --log captures/rcp-buttons-latched-call-host-shaped.txt
```

During each test, press and release `CALL` once or twice. Record any screen
change after the run.

### 2026-05-13 CALL Echo Result

Captures:

- `captures/rcp-buttons-call-exact-echo.txt`
- `captures/rcp-buttons-call-host-shaped.txt`
- `captures/rcp-buttons-latched-call-host-shaped.txt`

User observation:

- All three CALL response tests ended with the RCP screen at `CONNECT NOT ACT`.

Serial result:

| Test | Host response after `CALL` | Serial result |
| --- | --- | --- |
| BTN5 exact echo | `00 00 15 80 00 CF` then `00 00 15 00 00 4F` | RCP sent `07 80 45 20 D0 68` once, then returned to heartbeat/CALL events |
| BTN6 host-shaped | `00 00 15 00 80 CF` | heartbeat/CALL events only |
| BTN7 latched host-shaped | `00 00 15 00 80 CF` after latch setup | heartbeat/CALL events only |

Interpretation:

- Exact CALL echo is more interesting than CAM POWER echo: it produced a new
  checksum-valid RCP response frame, `07 80 45 20 D0 68`.
- The new frame did not visibly activate the RCP; the panel still ended at
  `CONNECT NOT ACT`.
- The host-shaped `CALL` response did not reproduce the new frame, either
  offline or after the `B5` latch setup.
- This suggests the RCP has at least one event-response path for CALL exact
  echo, but that response is still not the missing active-session handshake.

Next CALL-focused checks:

1. Retest exact CALL echo three times with clean power cycles to see whether
   `07 80 45 20 D0 68` is repeatable.
2. Test echoing only `CALL high/on` and only `CALL low/off` separately to see
   which of the two echoed frames causes `07 80 45 20 D0 68`.
3. Try using `07 80 45 20 D0 68` as a follow-up host frame after CALL exact
   echo, only after confirming repeatability.

### Real-World CALL Hold/Release Test

Goal: mimic how CALL would likely work in use:

```text
User holds CALL     -> RCP sends CALL high/on
Host responds high  -> host echoes/acknowledges CALL high/on
User releases CALL  -> RCP sends CALL low/off
Host responds low   -> host echoes/acknowledges CALL low/off
```

Test BTN8: mirror CALL high and low once per state.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 30 --prompt --mirror-call --mirror-call-once-per-state --log captures/rcp-buttons-call-mirror-hold-release.txt
```

Procedure:

1. Power-cycle the RCP.
2. Start the command and press Enter at the prompt.
3. Hold `CALL` for about 2 seconds.
4. Release `CALL`.
5. Watch whether the screen changes and note the final screen text.

Interpretation:

- If the RCP again sends `07 80 45 20 D0 68`, the frame is likely part of the
  CALL response path.
- If the screen still ends at `CONNECT NOT ACT`, this mirrors CALL signaling but
  still does not satisfy the active-session handshake.

Optional latched version:

```powershell
python scripts/serial_button_response_test.py --port COM5 --latch --latch-query-command 0xB5 --duration 30 --prompt --mirror-call --mirror-call-once-per-state --log captures/rcp-buttons-latched-call-mirror-hold-release.txt
```

### 2026-05-13 Latched CALL Mirror Result

Capture:

- `captures/rcp-buttons-latched-call-mirror-hold-release.txt`

The capture contains three appended latched runs. Each run sent the `00 -> B5`
latch setup, then mirrored observed CALL high/low events with the matching CALL
frame.

Observed result:

- The RCP still emitted CALL high/low frames while latched.
- The mirror script sent `CALL high mirror` and/or `CALL low mirror` responses
  as expected.
- No run reproduced the earlier exact-echo response
  `07 80 45 20 D0 68`.
- Serial output returned to heartbeat/CALL event traffic.

Interpretation:

- The earlier `07 80 45 20 D0 68` response appears tied to sending both CALL
  echo frames immediately after CALL high, not to a realistic held/released CALL
  mirror sequence.
- Mirroring CALL state while latched does not appear to activate or unlatch the
  RCP.
- The latched state continues to allow front-panel CALL events to be transmitted.

Tooling note:

- `scripts/serial_button_response_test.py` now clears the serial input buffer
  immediately after the manual prompt. This avoids stale frames collected while
  waiting at the prompt contaminating button timing in future hold/release
  tests.

Clean non-latched repeat, if needed:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 30 --prompt --mirror-call --mirror-call-once-per-state --log captures/rcp-buttons-call-mirror-hold-release.txt
```

### CALL Exact-Echo Reproducibility Test

Goal: determine whether the new frame `07 80 45 20 D0 68` is reproducible when
using the artificial exact-CALL echo that originally produced it.

Test BTN9: repeat exact CALL echo and explicitly watch for `07 80 45 20 D0 68`.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-exact-echo-repro-1.txt
```

Run the same command three times, changing only the log filename:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-exact-echo-repro-2.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-exact-echo-repro-3.txt
```

Procedure for each run:

1. Power-cycle the RCP.
2. Start the command and press Enter at the prompt.
3. Press `CALL` once.
4. Stop after the script exits, then power-cycle before the next run.

Interpretation:

- If `Watch totals: 07 80 45 20 D0 68=1` appears consistently, the frame is a
  reproducible response to artificial exact CALL echo.
- If it appears intermittently, timing or current panel state is probably part
  of the trigger.
- If it does not appear, the original hit may have depended on a very specific
  press/release timing or buffered frame ordering.

### 2026-05-13 CALL Exact-Echo Repro Result

Captures:

- `captures/rcp-buttons-call-exact-echo-repro-1.txt`
- `captures/rcp-buttons-call-exact-echo-repro-2.txt`
- `captures/rcp-buttons-call-exact-echo-repro-3.txt`

All three runs detected `CALL` high/on and sent the same exact echo pair:

```text
00 00 15 80 00 CF
00 00 15 00 00 4F
```

Observed serial result:

| Run | Result |
| --- | --- |
| repro 1 | random assortment of button presses; no `07 80 45 20 D0 68`; returned to heartbeat/CALL traffic |
| repro 2 | press-and-hold `CALL`; `07 80 45 20 D0 68` observed once, about 33 ms after the echo pair |
| repro 3 | quick `CALL` press only; no `07 80 45 20 D0 68`; returned to heartbeat traffic |

Interpretation:

- The `07 80 45 20 D0 68` frame is reproducible, but appears sensitive to
  physical CALL timing. It has now been seen in the original exact-echo test
  and in the press-and-hold repro run.
- Because each run sent the same two response frames after `CALL` high, the
  strongest current hypothesis is that CALL must remain asserted briefly after
  the host echo pair.
- Receive buffer alignment or an internal RCP state bit may still be involved,
  but the quick-press miss and press-and-hold hit make button hold duration the
  next variable to isolate.
- This frame is still best treated as a CALL/event-response clue, not as an
  activation handshake.

Next tighter CALL tests:

1. Echo only `CALL high/on`.
2. Echo only `CALL low/off`.
3. Repeat the two-frame exact echo while deliberately holding CALL until the
   script has logged either the watch frame or the next heartbeat.
4. Repeat the two-frame exact echo with shorter and longer response delays.

High-only echo:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-frame "00 00 15 80 00 CF" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-high-only-echo.txt
```

Low-only echo:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-low-only-echo.txt
```

Two-frame echo with a shorter response delay:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.0 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-exact-echo-delay-0ms.txt
```

Two-frame echo with a longer response delay:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.2 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-exact-echo-delay-200ms.txt
```

### 2026-05-13 CALL Hold Timing Tests

Captures:

- `captures/rcp-buttons-call-high-only-echo.txt`
- `captures/rcp-buttons-call-low-only-echo.txt`
- `captures/rcp-buttons-call-exact-echo-delay-0ms.txt`
- `captures/rcp-buttons-call-exact-echo-delay-200ms.txt`

User procedure:

- All four tests used a roughly 2 second hold on the `CALL` button.

Observed result:

| Test | Host response | Result |
| --- | --- | --- |
| high-only echo | `00 00 15 80 00 CF` after CALL high | no `07 80 45 20 D0 68` |
| low-only echo | `00 00 15 00 00 4F` after CALL high | no `07 80 45 20 D0 68` |
| exact echo, 0 ms delay | high echo then low echo immediately | no `07 80 45 20 D0 68` |
| exact echo, 200 ms delay | high echo then low echo after 200 ms response delay | no `07 80 45 20 D0 68` |

Interpretation:

- Holding `CALL` alone is not sufficient to reproduce
  `07 80 45 20 D0 68`.
- Echoing only one of the two CALL states is not sufficient in these tests.
- The previous successful repro used the two-frame exact echo with the script's
  default 50 ms response delay. The failed 0 ms and 200 ms runs suggest the
  timing window may be narrower than expected, or the frame depends on a
  second uncontrolled variable.
- Current best hypothesis: `07 80 45 20 D0 68` requires the exact two-frame
  CALL echo pair near the default delay timing, with CALL still asserted.

Next timing-bracket test:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.02 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-exact-echo-delay-20ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.05 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-exact-echo-delay-50ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.08 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-exact-echo-delay-80ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.12 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-exact-echo-delay-120ms.txt
```

For each run, power-cycle first, press and hold `CALL` for about 2 seconds, then
release after either the watch frame appears or the next heartbeat appears.

### 2026-05-13 CALL Timing-Bracket Result

Captures:

- `captures/rcp-buttons-call-exact-echo-delay-20ms.txt`
- `captures/rcp-buttons-call-exact-echo-delay-50ms.txt`
- `captures/rcp-buttons-call-exact-echo-delay-80ms.txt`
- `captures/rcp-buttons-call-exact-echo-delay-120ms.txt`

User procedure:

- Each run used the same two-frame exact CALL echo pair.
- Button hold timing was kept as accurate and consistent as possible.

Observed result:

| Delay | Result |
| --- | --- |
| 20 ms | heartbeat only; no `0x45` response |
| 50 ms | heartbeat/CALL traffic only; no `0x45` response |
| 80 ms | new response `07 80 45 30 D0 78` |
| 120 ms | heartbeat/CALL traffic only; no `0x45` response |

Comparison of known CALL-response-family frames:

```text
07 80 45 20 D0 68
07 80 45 30 D0 78
```

Both frames are checksum-valid under the current XOR-with-`0x5A` hypothesis.
They share prefix `07 80`, command/status byte `45`, and value byte `D0`. The
state/status byte changed from `20` to `30`, with the checksum changing from
`68` to `78` as expected.

Interpretation:

- The RCP appears to have a CALL-related `0x45` response family.
- The observed `0x45` response is timing-sensitive but not locked only to the
  original 50 ms response delay.
- The `0x20` vs `0x30` byte may represent a CALL/button substate, link/session
  substate, or timing/window state.
- This is stronger evidence that the panel is responding meaningfully to host
  traffic, even though the LCD state still has not been driven active.

Next CALL timing tests should watch both known `0x45` family frames:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.06 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-exact-echo-delay-60ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.07 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-exact-echo-delay-70ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.08 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-exact-echo-delay-80ms-v2.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.09 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-exact-echo-delay-90ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.10 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-exact-echo-delay-100ms.txt
```

If the `0x45` family repeats in the 60-100 ms window, the next step is to test
whether the host can answer the RCP's `0x45` response with a checksum-valid
host-shaped frame using command byte `0x45`.

Tooling note:

- `scripts/serial_button_response_test.py` now supports
  `--followup-on-watch-frame` with one or more `--followup-frame` values. This
  lets the host answer a reproduced RCP response immediately in the same run.

Possible follow-up test after reproducing a `0x45` response:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.08 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --followup-on-watch-frame --followup-frame "00 00 45 00 80 9F" --log captures/rcp-buttons-call-45-followup-host-shaped.txt
```

This test is deliberately secondary. Run it only after the `0x45` family
repeats, so any change can be attributed to the follow-up rather than to the
initial CALL echo timing.

### 2026-05-13 CALL 60-100 ms Repeat Result

Captures:

- `captures/rcp-buttons-call-exact-echo-delay-60ms.txt`
- `captures/rcp-buttons-call-exact-echo-delay-70ms.txt`
- `captures/rcp-buttons-call-exact-echo-delay-80ms-v2.txt`
- `captures/rcp-buttons-call-exact-echo-delay-90ms.txt`
- `captures/rcp-buttons-call-exact-echo-delay-100ms.txt`

Observed result:

| Delay | Result |
| --- | --- |
| 60 ms | no `0x45` response |
| 70 ms | no `0x45` response |
| 80 ms repeat | no `0x45` response |
| 90 ms | no `0x45` response |
| 100 ms | no `0x45` response |

Interpretation:

- The `07 80 45 30 D0 78` response from the earlier 80 ms run did not repeat in
  the 80 ms repeat or nearby 60-100 ms bracket.
- The trigger is not controlled by the simple delay between CALL high detection
  and the two-frame echo pair alone.
- A more likely variable is the spacing between the host's CALL-high echo and
  CALL-low echo, or the RCP's internal heartbeat/call-scan phase when the echo
  pair arrives.

Next test direction:

- Keep the initial response delay fixed near the values that have produced hits
  before, but vary the spacing between the two echoed CALL frames.

Tooling note:

- `scripts/serial_button_response_test.py` now supports
  `--response-frame-interval`, which inserts a delay between multiple
  `--response-frame` values sent for the same observed button event.

Inter-frame timing ladder:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.05 --response-frame-interval 0.02 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-frame-gap-20ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.05 --response-frame-interval 0.05 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-frame-gap-50ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.05 --response-frame-interval 0.08 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-frame-gap-80ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.08 --response-frame-interval 0.05 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-delay-80ms-frame-gap-50ms.txt
```

For each run, power-cycle first and use the same roughly 2 second CALL hold.

### 2026-05-13 CALL Inter-Frame Gap Result

Captures:

- `captures/rcp-buttons-call-frame-gap-20ms.txt`
- `captures/rcp-buttons-call-frame-gap-50ms.txt`
- `captures/rcp-buttons-call-frame-gap-80ms.txt`
- `captures/rcp-buttons-call-delay-80ms-frame-gap-50ms.txt`

Observed result:

| Initial delay | Gap between host CALL echoes | Result |
| --- | --- | --- |
| 50 ms | 20 ms | `07 80 45 20 D0 68` |
| 50 ms | 50 ms | `07 80 45 20 D0 68` |
| 50 ms | 80 ms | `07 80 45 20 D0 68` |
| 80 ms | 50 ms | `07 80 45 20 D0 68` |

Interpretation:

- `07 80 45 20 D0 68` is now reproducible.
- The key change was adding a gap between the host's CALL-high echo and
  CALL-low echo. Earlier tests sent the two response frames back-to-back.
- The exact gap is not especially narrow; 20 ms, 50 ms, and 80 ms all worked in
  this run set.
- The RCP likely needs to process the high echo as one event before receiving
  the low echo. Sending high and low in one tight burst can land outside that
  event path.
- The `0x45` response is still not known to activate the panel, but it is a
  real, reproducible event-response path.

Next step: answer the reproducible `0x45` response in the same run.

Test F1: host-shaped generic `0x45` ACK.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.05 --response-frame-interval 0.05 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "00 00 45 00 80 9F" --log captures/rcp-buttons-call-45-followup-generic-ack.txt
```

Test F2: host-shaped echo of the RCP `0x45` payload.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.05 --response-frame-interval 0.05 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "00 00 45 20 D0 EF" --log captures/rcp-buttons-call-45-followup-payload-echo.txt
```

Test F3: exact echo of the RCP `0x45` response frame.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.05 --response-frame-interval 0.05 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "07 80 45 20 D0 68" --log captures/rcp-buttons-call-45-followup-exact-echo.txt
```

For each follow-up test, note whether the LCD changes, whether more serial data
appears after the follow-up, and whether the panel returns to heartbeat only.

### 2026-05-13 CALL `0x45` Follow-Up Result

Captures:

- `captures/rcp-buttons-call-45-followup-generic-ack.txt`
- `captures/rcp-buttons-call-45-followup-payload-echo.txt`
- `captures/rcp-buttons-call-45-followup-exact-echo.txt`

Observed result:

| Test | Follow-up sent after `07 80 45 20 D0 68` | Serial result |
| --- | --- | --- |
| F1 generic ACK | `00 00 45 00 80 9F` | heartbeat only after follow-up |
| F2 payload echo | `00 00 45 20 D0 EF` | heartbeat only after follow-up |
| F3 exact echo | `07 80 45 20 D0 68` | heartbeat only after follow-up |

Notes:

- F1 and F2 reproduced the normal CALL-high echo path, then sent the follow-up
  frame after the RCP emitted `07 80 45 20 D0 68`.
- F3 still reproduced `07 80 45 20 D0 68`, but the triggering CALL event was
  captured as a split CALL-off frame rather than a clean CALL-on log line. The
  script's rolling buffer still recognized the frame and sent the configured
  responses.
- None of the three follow-up shapes caused a visible serial-side state change
  in the captured data. After the follow-up, the RCP returned to heartbeat.

Interpretation:

- The `0x45` frame is likely an RCP-origin response/notification in the CALL
  event path, but these simple host answers are not the missing activation
  handshake.
- The panel may not expect a direct answer to `0x45`, or the answer needs more
  session context than a single command frame.
- Since `0x45` can be produced from the CALL-off side too, the RCP may be
  responding to a CALL state transition sequence rather than specifically to
  CALL high.

Next direction:

- Use the reproducible CALL echo/gap sequence as a diagnostic, but return to
  the main activation problem: what host/session traffic makes the RCP leave
  `CONNECT NOT ACT`.
- Test whether a known discovery query still works immediately after the CALL
  `0x45` path, and whether CALL `0x45` changes the one-shot/latch behavior.

CALL then discovery query:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --respond-to-call --respond-once --response-delay 0.05 --response-frame-interval 0.05 --response-frame "00 00 15 80 00 CF" --response-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "00 00 00 00 80 DA" --followup-frame "00 00 B5 00 80 6F" --log captures/rcp-buttons-call-45-followup-discovery-b5.txt
```

If this produces the known `B5` response `07 80 6D 20 D8 48`, then the CALL
path does not consume the one-shot discovery response. If it returns heartbeat
only, CALL/`0x45` may put the RCP into a similar one-shot consumed state.

### 2026-05-13 CALL `0x45` Then Discovery Result

Capture:

- `captures/rcp-buttons-call-45-followup-discovery-b5.txt`

Observed sequence:

```text
RCP CALL high:        00 00 15 80 00 CF
Host CALL high echo:  00 00 15 80 00 CF
Host CALL low echo:   00 00 15 00 00 4F
RCP CALL response:    07 80 45 20 D0 68
Host primer:          00 00 00 00 80 DA
Host B5 query:        00 00 B5 00 80 6F
```

Result:

- After the follow-up `00 -> B5` query, the RCP returned heartbeat-compatible
  traffic only.
- The known `B5` response `07 80 6D 20 D8 48` did not appear.

Interpretation:

- The CALL/`0x45` path does not unlock the known discovery query.
- It may consume or bypass the same cold one-shot discovery window, or the RCP
  may simply ignore discovery-style queries once the CALL event path has been
  exercised.
- This pushes the CALL path into the "useful diagnostic but probably not the
  activation handshake" bucket.

### Cold No-Button CALL Injection Tests

Question: have we tried sending the CALL response frames without first pressing
the `CALL` button?

Answer: partially, but not in the exact form that now matters.

- Earlier command `0x15` matrix tests sent individual `0x15` frames from a cold
  panel and saw `CONNECT NOT ACT`, but no non-heartbeat serial response.
- The newer reproducible `0x45` result depends on sending the CALL-high and
  CALL-low frames as a pair with a gap. That exact cold/no-button pair has not
  been tested yet.

Tooling note:

- `scripts/serial_button_response_test.py` now supports `--startup-frame`. These
  frames are sent automatically after the listen window begins, without waiting
  for a physical button event.

Test C1: cold CALL pair, 50 ms gap, no physical button press.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 12 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-pair-gap-50ms.txt
```

Test C2: cold CALL pair, 80 ms gap, no physical button press.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 12 --prompt --startup-delay 1.0 --startup-frame-interval 0.08 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-pair-gap-80ms.txt
```

Test C3: cold CALL high only, no physical button press.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 12 --prompt --startup-delay 1.0 --startup-frame "00 00 15 80 00 CF" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-high-only.txt
```

Test C4: cold CALL low only, no physical button press.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 12 --prompt --startup-delay 1.0 --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-low-only.txt
```

For each test, power-cycle first and do not press any panel buttons. If C1/C2
produce `0x45`, the host can synthesize the CALL event path. If they do not,
the RCP's own physical CALL transition is required before the echo pair has
meaning.

### 2026-05-13 Cold No-Button CALL Injection Result

Captures:

- `captures/rcp-buttons-cold-call-pair-gap-50ms.txt`
- `captures/rcp-buttons-cold-call-pair-gap-80ms.txt`
- `captures/rcp-buttons-cold-call-high-only.txt`
- `captures/rcp-buttons-cold-call-low-only.txt`

Observed result:

| Test | Host startup frame(s) | Result |
| --- | --- | --- |
| C1 | `00 00 15 80 00 CF`, 50 ms gap, `00 00 15 00 00 4F` | `07 80 45 20 D0 68` |
| C2 | `00 00 15 80 00 CF`, 80 ms gap, `00 00 15 00 00 4F` | `07 80 45 20 D0 68` |
| C3 | `00 00 15 80 00 CF` only | heartbeat only |
| C4 | `00 00 15 00 00 4F` only | heartbeat only |

Interpretation:

- The host can synthesize the CALL `0x45` response path without pressing the
  physical `CALL` button.
- The RCP does not require its own front-panel CALL transition before this path
  has meaning.
- The required trigger is the ordered CALL-high then CALL-low pair with a small
  inter-frame gap. Either frame alone is insufficient.
- This makes `00 00 15 80 00 CF -> 00 00 15 00 00 4F` a confirmed host-side
  event stimulus, not merely an echo of physical button traffic.
- The response still returns to heartbeat afterward; this remains useful for
  protocol probing but is not yet an activation/session handshake.

### Next CALL Tests

Two useful follow-ups now that the host can synthesize the CALL path:

1. Determine whether the synthetic CALL trigger is repeatable within one power
   cycle, or whether the first `0x45` response latches/suppresses later ones.
2. Probe adjacent `0x45` family responses that might drive the illuminated CALL
   button or another visible state.

### CALL Retrigger / Latch Tests

Tooling note:

- `scripts/serial_button_response_test.py` now supports repeating the startup
  frame group with `--startup-repeat` and `--startup-repeat-interval`.

Test R1: two synthetic CALL trigger cycles, 2 second gap.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 16 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --startup-repeat 2 --startup-repeat-interval 2.0 --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-repeat-2x-gap-2s.txt
```

Test R2: two synthetic CALL trigger cycles, 5 second gap.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 22 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --startup-repeat 2 --startup-repeat-interval 5.0 --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-repeat-2x-gap-5s.txt
```

Test R3: three synthetic CALL trigger cycles, 2 second gap.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 24 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --startup-repeat 3 --startup-repeat-interval 2.0 --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-repeat-3x-gap-2s.txt
```

Interpretation:

- If `Watch totals` shows one `0x45` hit per trigger cycle, this path is
  repeatable and not a one-shot latch.
- If only the first cycle produces `0x45`, treat the CALL path as latched until
  power cycle or some unknown reset.
- If later cycles produce `07 80 45 30 D0 78` instead of `...20...`, the RCP
  may be stepping through a small state machine rather than simply suppressing
  repeats.

### Adjacent `0x45` Family Follow-Up Tests

Goal: once the synthetic CALL pair has produced `07 80 45 20 D0 68`, send nearby
frames that might act like CALL lamp/tally control. Watch the CALL button lamp,
LCD, and serial stream after each follow-up.

Useful adjacent candidates:

| Candidate type | Follow-up frame | Why it is interesting |
| --- | --- | --- |
| host-shaped command below | `00 00 44 20 D0 EE` | adjacent command byte |
| host-shaped command known | `00 00 45 20 D0 EF` | same command, host-shaped |
| host-shaped command above | `00 00 46 20 D0 EC` | adjacent command byte |
| exact-family sibling seen once | `07 80 45 30 D0 78` | observed adjacent state |
| exact-family state below | `07 80 45 10 D0 58` | nearby state nibble |
| exact-family command above | `07 80 46 20 D0 6B` | nearby command nibble |

Run each candidate in a separate power cycle. The startup CALL pair is used only
to make the RCP produce the known `0x45` response first.

Test A1:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "00 00 44 20 D0 EE" --log captures/rcp-buttons-call-adjacent-44-host.txt
```

Test A2:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "00 00 45 20 D0 EF" --log captures/rcp-buttons-call-adjacent-45-host.txt
```

Test A3:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "00 00 46 20 D0 EC" --log captures/rcp-buttons-call-adjacent-46-host.txt
```

Test A4:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-call-adjacent-45-state30.txt
```

Test A5:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "07 80 45 10 D0 58" --log captures/rcp-buttons-call-adjacent-45-state10.txt
```

Test A6:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 15 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --watch-frame "07 80 45 20 D0 68" --followup-on-watch-frame --followup-frame "07 80 46 20 D0 6B" --log captures/rcp-buttons-call-adjacent-46-exact.txt
```

Record for each run:

- whether the CALL button lamp changes
- whether the LCD changes
- whether any non-heartbeat serial data appears after the follow-up

### 2026-05-13 Initial CALL Retrigger Result

Captures:

- `captures/rcp-buttons-cold-call-repeat-2x-gap-2s.txt`
- `captures/rcp-buttons-cold-call-repeat-2x-gap-5s.txt`
- `captures/rcp-buttons-cold-call-repeat-3x-gap-2s.txt`

Observed result:

- All configured synthetic CALL trigger cycles were transmitted.
- Each run recorded only one visible `07 80 45 20 D0 68`.
- No LCD change was observed beyond the already known `CONNECT NOT ACT`.

Important tooling limitation:

- In this first version of the repeat test, the helper sent all startup trigger
  groups before entering its main RX loop.
- That means the captured `0x45` frame count is not a clean per-cycle measure.
  A single buffered `0x45` at the end does not prove whether only one cycle
  triggered or multiple triggers collapsed into one unread serial burst.

Interpretation:

- These runs suggest the CALL path may be latched or at least not obviously
  retriggering, but they are not strong enough to prove it.
- A corrected repeat test must read after each trigger group before sending the
  next one.

Tooling update:

- `scripts/serial_button_response_test.py` now supports
  `--startup-read-after-group`, which reads and logs RX after each startup-frame
  group before the next repeat.

Corrected repeat tests:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 16 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --startup-repeat 2 --startup-repeat-interval 2.0 --startup-read-after-group 0.8 --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-repeat-2x-gap-2s-v2.txt
python scripts/serial_button_response_test.py --port COM5 --duration 22 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --startup-repeat 2 --startup-repeat-interval 5.0 --startup-read-after-group 0.8 --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-repeat-2x-gap-5s-v2.txt
python scripts/serial_button_response_test.py --port COM5 --duration 24 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --startup-repeat 3 --startup-repeat-interval 2.0 --startup-read-after-group 0.8 --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-repeat-3x-gap-2s-v2.txt
```

### 2026-05-13 Corrected CALL Retrigger Result

Captures:

- `captures/rcp-buttons-cold-call-repeat-2x-gap-2s-v2.txt`
- `captures/rcp-buttons-cold-call-repeat-2x-gap-5s-v2.txt`
- `captures/rcp-buttons-cold-call-repeat-3x-gap-2s-v2.txt`

User observation:

- No LCD changes were observed beyond the already known `CONNECT NOT ACT`.

Observed serial result:

| Test | Trigger groups sent | `0x45` result |
| --- | --- | --- |
| 2x, 2 second gap | 2 | both groups produced `07 80 45 20 D0 68`, four copies each |
| 2x, 5 second gap | 2 | only first group produced `07 80 45 20 D0 68` |
| 3x, 2 second gap | 3 | only first group produced `07 80 45 20 D0 68` |

Interpretation:

- The synthetic CALL path is not a strict one-shot latch, because the 2x/2s run
  clearly retriggered on the second group.
- It is also not cleanly repeatable on every later trigger, because the 2x/5s
  and 3x/2s runs only produced the `0x45` burst on the first group.
- Each successful trigger can produce a short burst of repeated identical
  `07 80 45 20 D0 68` frames rather than a single reply.
- Current best model: the CALL path is re-enterable but phase/state-sensitive.
  Something about timing relative to the RCP's internal scan/heartbeat/session
  state affects whether later trigger groups are accepted.

Practical takeaway:

- We can use the synthetic CALL pair as a reproducible probe, but not yet as a
  guaranteed repeatable command in every cycle of a run.
- For future CALL-path experiments, treat one successful `0x45` burst per power
  cycle as the reliable baseline, and repeated triggers as conditional behavior
  worth probing rather than assuming.

Next retrigger refinement:

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 20 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --startup-repeat 2 --startup-repeat-interval 2.0 --startup-read-after-group 1.5 --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-repeat-2x-gap-2s-read1500ms.txt
python scripts/serial_button_response_test.py --port COM5 --duration 23 --prompt --startup-delay 1.0 --startup-frame-interval 0.05 --startup-frame "00 00 15 80 00 CF" --startup-frame "00 00 15 00 00 4F" --startup-repeat 2 --startup-repeat-interval 3.0 --startup-read-after-group 1.5 --watch-frame "07 80 45 20 D0 68" --watch-frame "07 80 45 30 D0 78" --log captures/rcp-buttons-cold-call-repeat-2x-gap-3s-read1500ms.txt
```

These should help distinguish whether the second-trigger variability is caused
by too-short post-trigger read windows, or by a genuine acceptance window/state
inside the RCP.

### 2026-05-13 Adjacent `0x45` Follow-Up Result

Captures:

- `captures/rcp-buttons-call-adjacent-44-host.txt`
- `captures/rcp-buttons-call-adjacent-45-host.txt`
- `captures/rcp-buttons-call-adjacent-46-host.txt`
- `captures/rcp-buttons-call-adjacent-45-state30.txt`
- `captures/rcp-buttons-call-adjacent-45-state10.txt`
- `captures/rcp-buttons-call-adjacent-46-exact.txt`

User observation:

- No LCD changes were observed beyond the already known `CONNECT NOT ACT`.
- No CALL button lamp change was observed in these runs.

Serial result:

| Test | Follow-up frame after `07 80 45 20 D0 68` | Result |
| --- | --- | --- |
| A1 | `00 00 44 20 D0 EE` | heartbeat only after follow-up |
| A2 | `00 00 45 20 D0 EF` | heartbeat only after follow-up |
| A3 | `00 00 46 20 D0 EC` | heartbeat only after follow-up |
| A4 | `07 80 45 30 D0 78` | heartbeat only after follow-up |
| A5 | `07 80 45 10 D0 58` | heartbeat only after follow-up |
| A6 | `07 80 46 20 D0 6B` | heartbeat only after follow-up |

Interpretation:

- None of the first adjacent `0x45` family probes appear to drive the CALL lamp
  or advance the serial state.
- The obvious nearby command/state variants are not enough on their own to act
  like a CALL lamp/tally command.
- The CALL `0x45` family remains useful as a probe point, but the lamp control
  is probably elsewhere in the protocol or needs more session context.

### 2026-05-13 Outside-Region Clean Confirmation Result

Captures:

- `captures/rcp-outside-confirm-00-40-4f-8f-ef.txt`
- `captures/rcp-outside-repeat-00-40.txt`
- `captures/rcp-outside-repeat-00-4f.txt`
- `captures/rcp-outside-repeat-00-8f.txt`
- `captures/rcp-outside-repeat-00-ef.txt`
- `captures/rcp-outside-single-40.txt`
- `captures/rcp-outside-single-4f.txt`
- `captures/rcp-outside-single-8f.txt`
- `captures/rcp-outside-single-ef.txt`

Goal:

- Clean-confirm whether the paused direct-sweep outliers `0x40`, `0x4F`,
  `0x8F`, and `0xEF` are real primer-dependent queries outside the mapped
  `A/B/C` table.
- Check whether they also work as single first-frame commands after boot.

Primer-pair result:

```text
primer:    00 00 00 00 80 DA
candidate: 00 00 CMD 00 80 CHECKSUM
```

Observed responses:

| Pair | Multi-candidate confirm run | Single-candidate repeat | Current read |
| --- | --- | --- | --- |
| `00 -> 40` | heartbeat only in the 4-candidate pass | `07 80 50 40 30 FD` repeated | confirmed real, but missed once in the shared sweep |
| `00 -> 4F` | `07 80 0A 04 AB 78` | `07 80 0A 04 6B B8` repeated | confirmed real, response bytes vary across runs |
| `00 -> 8F` | `07 80 0C 04 AB 7E` repeated | `07 80 0C 04 AB 7E` once, then heartbeat | confirmed real and stable in content |
| `00 -> EF` | `07 80 0F 04 AB 7D` repeated | `07 80 0F 04 AB 7D` repeated | confirmed real; differs from earlier paused-sweep `07 80 0F 04 EB 3D` |

Single-frame control result:

All of these stayed heartbeat-compatible when sent as the first host frame
after boot, with no anomalies recorded:

- `00 00 40 00 80 9A`
- `00 00 4F 00 80 95`
- `00 00 8F 00 80 55`
- `00 00 EF 00 80 35`

Interpretation:

- `0x40`, `0x4F`, `0x8F`, and `0xEF` are now confirmed as real
  primer-dependent readable/query responses outside the clean `A/B/C` map.
- None of the four behave like direct first-frame activation/session commands.
  They sit much closer to the broader query/status surface than to an
  unlatching handshake.
- `0x40` appears slightly context- or timing-sensitive because it missed in the
  shared 4-candidate pass but reproduced cleanly when isolated.
- `0x4F` and `0xEF` need extra caution: the response family is real, but the
  value bytes are not yet fully nailed down across all captures.
  - `0x4F` has been seen as both `07 80 0A 04 AB 78` and
    `07 80 0A 04 6B B8`.
  - `0xEF` has been seen as both `07 80 0F 04 AB 7D` and the earlier paused
    direct-sweep `07 80 0F 04 EB 3D`.
- Best current model: these are legitimate outer-table queries whose returned
  payload can still depend on selector context, prior sequence, or exactly when
  in the panel's internal state machine they are sampled.

## Host Identity / Capability Exchange Lead Ladder

Goal:

- Test whether the CCU is expected to identify itself before asking for
  capability/state blocks.
- Separate "query selector/page" behavior from "host identity/session setup"
  behavior.
- Check whether a short query burst behaves more like a capability poll than a
  single one-shot request.

Tooling:

- Use `scripts/serial_sequence_probe.py` for fixed multi-frame sequences where
  the canonical primer stays constant and only later frames vary.
- Use `scripts/serial_primer_candidate_sweep.py` when only a simple
  `primer -> candidate` pair is needed.

### Test HI1: Prefix Variation On A Stable Query

Keep the known-good primer fixed and vary only the prefix bytes of the `A0`
query frame. If the response changes, the host prefix bytes may carry CCU
identity, addressing, or mode information rather than being ignored padding.

Power-cycle before each run.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --read-after-frame 1.2 --log captures/rcp-hostid-prefix-0000-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 80 A0 00 80 FA" --read-after-frame 1.2 --log captures/rcp-hostid-prefix-0080-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "80 00 A0 00 80 FA" --read-after-frame 1.2 --log captures/rcp-hostid-prefix-8000-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "07 80 A0 00 80 FD" --read-after-frame 1.2 --log captures/rcp-hostid-prefix-0780-a0.txt
```

What to watch for:

- Same `A0` block as baseline: prefix bytes probably are not the missing host
  identity on their own.
- Different structured block: prefix bytes likely select host identity, page,
  or role.
- Heartbeat only: that prefix pair may be invalid or reserved.

### Test HI2: State/Value Variation On A Stable Query

Keep the canonical prefix and command byte, but vary the state/value fields on
the `A0` query. This checks whether the host is supposed to present status or
capability bits in fields that we have mostly left at `00 80`.

Power-cycle before each run.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --read-after-frame 1.2 --log captures/rcp-hostid-a0-0080.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 20 D0 0A" --read-after-frame 1.2 --log captures/rcp-hostid-a0-20d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 40 30 8A" --read-after-frame 1.2 --log captures/rcp-hostid-a0-4030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 60 30 AA" --read-after-frame 1.2 --log captures/rcp-hostid-a0-6030.txt
```

What to watch for:

- Same `A0` block every time: host state/value fields may be ignored here.
- Different block family or different returned value bytes: these fields may be
  host-presented capability/status bits.
- LCD/LED changes without a different serial block: possible session-state side
  effect rather than a simple table read.

### Test HI3: Primer -> Host-Announce -> Query

Try likely selector/identity-looking bytes as a middle frame before the stable
`A0` query. This is the direct "CCU says who it is first" test.

Power-cycle before each run.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 90 00 80 4A" --frame "00 00 A0 00 80 7A" --read-after-frame 1.0 --log captures/rcp-hostid-announce-90-then-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 9F 00 80 45" --frame "00 00 A0 00 80 7A" --read-after-frame 1.0 --log captures/rcp-hostid-announce-9f-then-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 AF 00 80 75" --frame "00 00 A0 00 80 7A" --read-after-frame 1.0 --log captures/rcp-hostid-announce-af-then-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 B7 00 80 6D" --frame "00 00 A0 00 80 7A" --read-after-frame 1.0 --log captures/rcp-hostid-announce-b7-then-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 BB 00 80 61" --frame "00 00 A0 00 80 7A" --read-after-frame 1.0 --log captures/rcp-hostid-announce-bb-then-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 FF 00 80 25" --frame "00 00 A0 00 80 7A" --read-after-frame 1.0 --log captures/rcp-hostid-announce-ff-then-a0.txt
```

What to watch for:

- Middle frame gets heartbeat only, third frame still returns plain `A0` block:
  the announce byte probably is not sufficient.
- Middle frame changes the later `A0` response: strong evidence for a
  host-identity/selector stage.
- Middle frame alone produces a new block: it may itself be a readable
  capability/identity query rather than a pure host announce.

### Test HI4: Capability-Poll Block

Send a short family of related queries as if a CCU is polling multiple
capability blocks in one startup pass. This checks whether the panel expects a
cluster of reads instead of one isolated query.

Power-cycle before each run.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame "00 00 A1 00 80 7B" --frame "00 00 A4 00 80 7E" --frame "00 00 A5 00 80 7F" --read-after-frame 0.8 --log captures/rcp-hostid-capblock-a-family.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --frame "00 00 B1 00 80 6B" --frame "00 00 B8 00 80 62" --frame "00 00 BC 00 80 66" --read-after-frame 0.8 --log captures/rcp-hostid-capblock-b-family.txt
```

What to watch for:

- Only the first query in the block responds: the one-shot model still dominates.
- Later queries also respond once the family is polled as a burst: this would be
  a major new lead toward CCU-style startup behavior.
- A later query changes the LCD or LEDs even if the first one looks ordinary:
  still worth treating as a lead.

### Test HI5: Repeated Poll Group

Repeat the same short poll group with a gap, to test whether the panel wants
periodic polling or whether only the first startup block matters.

Power-cycle before each run.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --frame "00 00 B1 00 80 6B" --repeat 3 --repeat-interval 1.5 --read-after-frame 0.8 --read-after-group 0.8 --log captures/rcp-hostid-repeat-b0-b1.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --repeat 3 --repeat-interval 1.5 --read-after-frame 1.0 --read-after-group 0.8 --log captures/rcp-hostid-repeat-a0.txt
```

What to watch for:

- Only group 1 responds: startup window or latch behavior still dominates.
- Later groups begin to respond too: periodic polling may be part of the
  expected CCU session.
- A later group changes visible state even with similar serial output: possible
  session-timer or keepalive behavior.

Recommended order:

1. `HI3` because it most directly tests the "CCU identifies itself first"
   hypothesis.
2. `HI4` because a capability-poll burst is a plausible Sony startup pattern.
3. `HI1` and `HI2` if the first two stay flat and we need to isolate which host
   fields matter.

### 2026-05-13 Host Identity / Capability Result

Captures:

- `captures/rcp-hostid-prefix-0000-a0.txt`
- `captures/rcp-hostid-prefix-0080-a0.txt`
- `captures/rcp-hostid-prefix-8000-a0.txt`
- `captures/rcp-hostid-a0-0080.txt`
- `captures/rcp-hostid-a0-20d0.txt`
- `captures/rcp-hostid-a0-4030.txt`
- `captures/rcp-hostid-a0-6030.txt`
- `captures/rcp-hostid-announce-90-then-a0.txt`
- `captures/rcp-hostid-announce-9f-then-a0.txt`
- `captures/rcp-hostid-announce-af-then-a0.txt`
- `captures/rcp-hostid-announce-b7-then-a0.txt`
- `captures/rcp-hostid-announce-bb-then-a0.txt`
- `captures/rcp-hostid-announce-ff-then-a0.txt`
- `captures/rcp-hostid-capblock-a-family.txt`
- `captures/rcp-hostid-capblock-b-family.txt`
- `captures/rcp-hostid-repeat-a0.txt`
- `captures/rcp-hostid-repeat-b0-b1.txt`

Not run / no capture present:

- `captures/rcp-hostid-prefix-0780-a0.txt`

#### HI1: Prefix Variation On `A0`

Observed result:

| Query frame after primer | Result |
| --- | --- |
| `00 00 A0 00 80 7A` | conflicting captures: one run returned `07 80 E8 40 30 45`, another returned `07 80 68 40 30 C5` |
| `00 80 A0 00 80 FA` | heartbeat only |
| `80 00 A0 00 80 FA` | heartbeat only |
| `07 80 A0 00 80 FD` | not run |

Read:

- Nonzero host prefix bytes did not help. The tested `00 80` and `80 00`
  prefixes suppressed the `A0` response entirely.
- The plain `00 00` prefix remains the only confirmed working host prefix for
  `A0`, although the returned block still varies across runs.

#### HI2: State/Value Variation On `A0`

Observed result:

| Query frame after primer | Observed RCP response |
| --- | --- |
| `00 00 A0 00 80 7A` | `07 80 68 40 30 C5` repeated |
| `00 00 A0 20 D0 0A` | `07 80 E8 48 3A 47` repeated |
| `00 00 A0 40 30 8A` | `07 80 68 58 26 CB` repeated |
| `00 00 A0 60 30 AA` | `07 80 68 58 26 CB` repeated |

Read:

- This is the strongest new lead in the set.
- The `A0` response is not fixed: the host `state/value` fields clearly affect
  the returned block.
- That strongly supports the idea that these fields are carrying host-presented
  status, selector, or capability information, not just filler.

#### HI3: Primer -> Host-Announce -> `A0`

Observed result:

| Sequence | Middle-frame result | Later `A0` result |
| --- | --- | --- |
| `00 -> 90 -> A0` | `07 80 64 40 30 C9` repeated | no clean `A0`; only one more `07 80 64 40 30 C9` then heartbeat |
| `00 -> 9F -> A0` | heartbeat only after `9F` | heartbeat only after `A0`; a prior anomaly `07 80 40 40 30 ED` appeared immediately after the primer |
| `00 -> AF -> A0` | `07 80 0D 04 AB 7F` visible with a leading heartbeat fragment | heartbeat only after `A0` |
| `00 -> B7 -> A0` | `07 80 1B 08 D6 18` repeated | heartbeat only after `A0` |
| `00 -> BB -> A0` | `07 80 37 10 2C D6` repeated | no clean `A0`; only one more `07 80 37 10 2C D6` then heartbeat |
| `00 -> FF -> A0` | heartbeat only | heartbeat only |

Read:

- The "announce" bytes behaved more like readable/query commands than like a
  host identity banner the panel accepts and then builds on.
- In most runs, the middle frame consumed the one-shot response opportunity and
  the following `A0` did not produce its own block.
- So far this argues against a simple three-step handshake of
  `primer -> host identity -> query`.

#### HI4: Capability-Poll Block

Observed result:

| Block | Result |
| --- | --- |
| `00 -> A0 -> A1 -> A4 -> A5` | only `A0` responded (`07 80 68 40 30 C5`); `A1`, `A4`, and `A5` were heartbeat only |
| `00 -> B0 -> B1 -> B8 -> BC` | only `B0` responded (`07 80 EC 40 30 41` with a leading heartbeat fragment); `B1`, `B8`, and `BC` were heartbeat only |

Read:

- A burst of related readable queries did not unlock later responses in the same
  startup pass.
- The one-shot model still dominates: first successful readable query responds,
  later ones in the burst are suppressed.

#### HI5: Repeated Poll Group

Observed result:

| Repeated group | Result |
| --- | --- |
| `00 -> A0`, repeated 3 times | only group 1 produced `07 80 68 40 30 C5`; groups 2 and 3 were heartbeat only |
| `00 -> B0 -> B1`, repeated 3 times | only group 1 `B0` produced a response; later groups were heartbeat only and `B1` never responded |

Read:

- Periodic polling without a power cycle did not open a sustained session.
- The panel still behaves like it offers one early readable response block, then
  falls back to heartbeat-only behavior.

Overall interpretation:

- The cleanest new evidence is that host `state/value` fields matter a lot for
  at least the `A0` family.
- The tested nonzero prefixes do not look like a missing CCU identity by
  themselves.
- Candidate "announce" bytes mostly act like ordinary readable/query selectors,
  not like a reusable host identity stage.
- Capability-poll bursts and repeated poll groups did not create a multi-query
  session.
- Best current model: the startup exchange probably does involve host-presented
  status or selector bits, but the currently tested sequences still land in a
  one-shot query regime rather than an active maintained session.

## Negative-Space Test Ladder

Goal:

- Find out what the panel quietly ignores versus what changes its internal
  parser/session state.
- Check whether repetitive "host heartbeat" traffic alone changes anything.
- Recheck whether a known readable query needs silence afterward, or whether the
  act of repetition itself suppresses later behavior.
- See whether a malformed checksum has any special effect beyond the already
  known `CONNECT NOT ACT` parser-visible state.

Recommended observation:

- Power-cycle before each test.
- Note any LCD change, especially whether it remains blank/idle or reaches the
  usual `CONNECT NOT ACT`.
- Note any visible LED change, even if the serial log looks ordinary.

### NS1: Assumed Host Heartbeat Only

Send only the current assumed host heartbeat frame repeatedly for a longer
window, with no follow-up query.

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 00 00 80 DA" --repeat 80 --interval 0.25 --delay 3 --after 3 --frame-size 0 --log captures/rcp-negative-heartbeat-only.txt
```

Read goal:

- If this alone eventually changes visible state, cadence may matter more than
  command diversity.
- If it remains heartbeat-only and visually inert, the primer/heartbeat frame is
  probably necessary but not sufficient.

### 2026-05-13 NS1 Partial Result

User observation from two power-cycled runs:

- During the entire `NS1` script run, the panel did **not** enter
  `CONNECT NOT ACT` / `CONNECTION NOT ACT`.
- About 2 seconds after the repeating heartbeat-only script stopped, the panel
  entered `CONNECT NOT ACT`.
- Manually interrupting the script showed the same pattern: while the stream was
  active, no `CONNECT NOT ACT`; about 2 seconds after transmission ceased, the
  panel entered `CONNECT NOT ACT`.
- Once the panel was already in `CONNECT NOT ACT`, restarting the heartbeat-only
  script did not immediately clear that state.

Interpretation:

- Continuous transmission of the assumed host heartbeat appears to hold the
  panel out of the `CONNECT NOT ACT` display state.
- The transition into `CONNECT NOT ACT` now looks less like "bad packet was
  received" and more like "host traffic stopped and a connection/activity timer
  expired."
- That suggests the panel may treat regular host traffic as evidence of a live
  link, even when the session is not fully active.
- It also suggests `CONNECT NOT ACT` may be a post-timeout idle/failure state
  rather than an immediate parse-error state.
- The fact that restarting the stream does not snap the LCD back out of
  `CONNECT NOT ACT` implies the entry condition and exit condition are probably
  different. Keeping traffic alive may prevent the state, but leaving the state
  may require a separate activation/ack/session step.

Next refinement worth doing:

- Repeat `NS1` with different transmit intervals such as `100 ms`, `500 ms`,
  and `1.5 s` to estimate the timeout threshold for preventing
  `CONNECT NOT ACT`.

### NS2: Primer Only, Short Burst Groups

Use the sequence probe to send the same primer in repeated groups, with a gap
between groups, to see whether grouped startup chatter matters differently than
an uninterrupted stream.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --repeat 6 --repeat-interval 1.0 --read-after-frame 0.6 --read-after-group 0.6 --log captures/rcp-negative-primer-groups.txt
```

Read goal:

- If a later group produces anything non-heartbeat, the panel may have a boot
  window or cadence threshold.
- If every group is ignored, plain primer repetition is probably dead air from
  the panel's point of view.

### NS3: Known Readable Query Once, Then Silence

Send a known readable query once after the canonical primer and then do nothing
else for a longer read window.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --read-after-frame 1.2 --read-after-group 5.0 --log captures/rcp-negative-a0-once-then-silence.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --read-after-frame 1.2 --read-after-group 5.0 --log captures/rcp-negative-b0-once-then-silence.txt
```

Read goal:

- If the panel emits extra delayed frames after the initial block, it may be
  expecting a host response or entering a timed wait state.
- If it cleanly returns to heartbeat-only, the one-shot read probably ends the
  interaction by itself.

### NS4: Known Readable Query Repeated

Send the same readable query repeatedly after a primer, without power-cycling,
to separate "one-shot per boot" from "suppressed only by mixed traffic."

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --repeat 5 --repeat-interval 1.0 --read-after-frame 1.0 --read-after-group 0.8 --log captures/rcp-negative-a0-repeat.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --repeat 5 --repeat-interval 1.0 --read-after-frame 1.0 --read-after-group 0.8 --log captures/rcp-negative-b0-repeat.txt
```

Read goal:

- If only the first group responds, that strengthens the one-shot-per-boot
  model.
- If a later group responds again, repeated identical polling may be more valid
  than mixed-family polling.

### NS5: Malformed Checksum Once

Send one malformed checksum frame with the same shape as the assumed heartbeat,
then observe whether the panel does anything beyond ordinary heartbeat and the
known parser-visible display state.

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 00 00 80 00" --repeat 1 --delay 3 --after 8 --frame-size 0 --log captures/rcp-negative-bad-heartbeat-once.txt
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 A0 00 80 00" --repeat 1 --delay 3 --after 8 --frame-size 0 --log captures/rcp-negative-bad-a0-once.txt
```

Read goal:

- If malformed frames cause no special serial result beyond heartbeat, checksum
  errors are probably tolerated only enough to flip link-visible state.
- If malformed `A0` produces a different visible or serial effect than malformed
  heartbeat, the parser may be doing more command-byte inspection before
  checksum rejection than expected.

### NS6: Deliberate Silence Control

This is a no-transmit control to compare against all of the above. Just watch RX
with no host traffic.

```powershell
python scripts/serial_button_response_test.py --port COM5 --duration 20
```

Read goal:

- Confirm the natural idle pattern and visible state with zero host traffic in
  the exact same bench setup.

Recommended order:

1. `NS6` silence control
2. `NS1` heartbeat only
3. `NS3` query once then silence
4. `NS4` repeated same query
5. `NS5` malformed checksum once
6. `NS2` primer groups

### 2026-05-13 Negative-Space Result

Captures:

- `captures/rcp-negative-heartbeat-only.txt`
- `captures/rcp-negative-heartbeat-100ms.txt`
- `captures/rcp-negative-heartbeat-500ms.txt`
- `captures/rcp-negative-heartbeat-1500ms.txt`
- `captures/rcp-negative-primer-groups.txt`
- `captures/rcp-negative-a0-once-then-silence.txt`
- `captures/rcp-negative-b0-once-then-silence.txt`
- `captures/rcp-negative-a0-repeat.txt`
- `captures/rcp-negative-b0-repeat.txt`
- `captures/rcp-negative-bad-heartbeat-once.txt`
- `captures/rcp-negative-bad-a0-once.txt`

#### NS1: Assumed Host Heartbeat Only

Observed serial result:

- The repeated host heartbeat usually left RX at ordinary heartbeat, but some
  runs produced a short early structured response burst near the second host
  heartbeat:
  - `07 80 40 60 30 CD`
  - `07 80 40 40 30 ED`
  - `07 80 C0 40 30 6D`
- These bursts were transient. After them, the link returned to ordinary
  heartbeat RX while the host heartbeat stream continued.

Observed LCD/timing result from user notes:

- `100 ms` interval: `CONNECT NOT ACT` appeared near the end before the script
  fully finished.
- `500 ms` interval: `CONNECT NOT ACT` appeared once the script finished.
- `1500 ms` interval: the panel timed out at the very end, just before the
  script finished.
- Earlier manual observation also still stands: while the heartbeat stream is
  actively running, the panel can remain out of `CONNECT NOT ACT`, then fall
  into it roughly 2 seconds after traffic stops.

Read:

- Regular heartbeat traffic clearly affects the panel's visible connection
  timeout behavior.
- The panel is not treating `00 00 00 00 80 DA` as a full activation command,
  but it does appear to treat it as meaningful host-presence traffic.
- The brief `0x40` / `0xC0` response bursts suggest some heartbeat cadences can
  momentarily steer the panel into a different readable state family even
  without an explicit query.

#### NS2: Primer Groups

Observed result:

- Repeated single-frame primer groups produced only heartbeat-compatible RX.
- No anomalies were logged.

Read:

- Sparse primer pings alone do not seem to provoke the panel into a readable
  state.
- Grouped startup chatter is weaker than a continuous heartbeat stream.

#### NS3: Known Query Once, Then Silence

Observed result:

| Sequence | Immediate response | Tail behavior |
| --- | --- | --- |
| `00 -> A0`, then silence | `07 80 68 40 30 C5` repeated | a short additional `07 80 68 40 30 C5` tail, then heartbeat only |
| `00 -> B0`, then silence | `07 80 6C 40 30 C1` repeated | a short additional `07 80 6C 40 30 C1` tail, then heartbeat only |

Read:

- A successful one-shot readable query continues to drain out a short burst for
  a moment even after host transmission stops.
- After that short burst, the panel returns cleanly to ordinary heartbeat-only
  behavior.

#### NS4: Known Query Repeated

Observed result:

| Repeated sequence | Result |
| --- | --- |
| `00 -> A0`, repeated 5 times | only group 1 produced `07 80 68 40 30 C5`; groups 2-5 were heartbeat only |
| `00 -> B0`, repeated 5 times | only group 1 produced `07 80 6C 40 30 C1`; groups 2-5 were heartbeat only |

Read:

- Repeating the exact same known-good readable query does **not** make it
  reusable.
- This strengthens the one-shot-per-boot model for these query families.

#### NS5: Malformed Checksum Once

Observed result:

- A malformed heartbeat-shaped frame `00 00 00 00 80 00` produced only ordinary
  heartbeat RX afterward.
- A malformed `A0`-shaped frame `00 00 A0 00 80 00` also produced only
  ordinary heartbeat RX afterward.

Read:

- Malformed frames do not seem to trigger a special serial reaction on their
  own.
- Whatever visible `CONNECT NOT ACT` behavior malformed frames may cause on the
  LCD, it is not accompanied by a distinctive RX response block in these runs.

Overall interpretation:

- Silence and malformed traffic are serially boring.
- Successful readable queries still behave as one-shot bursts followed by a
  return to heartbeat.
- Repeating those same queries still does not reopen them.
- Continuous heartbeat traffic is the one negative-space case that clearly
  changes panel behavior:
  - it can delay or suppress entry into `CONNECT NOT ACT`
  - it can sometimes provoke brief `0x40` / `0xC0` response-family bursts
    without any explicit query command

## Heartbeat-Maintained Reuse Test

Goal:

- Check whether keeping a host heartbeat alive between query attempts makes the
  one-shot readable blocks reusable without power-cycling.

Working idea:

- Earlier repeat tests used `primer -> query` groups with idle gaps.
- The new negative-space tests suggest steady heartbeat traffic may hold the
  panel in a more connection-aware state.
- So the next question is whether interleaving regular heartbeats between query
  attempts can reopen `A0` or `B0`.

### HR1: `A0` With Heartbeat Maintenance

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 4 --frame-interval 0.25 --read-after-frame 0.35 --read-after-group 0.5 --log captures/rcp-heartbeat-maintained-a0.txt
```

### HR2: `B0` With Heartbeat Maintenance

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 4 --frame-interval 0.25 --read-after-frame 0.35 --read-after-group 0.5 --log captures/rcp-heartbeat-maintained-b0.txt
```

### HR3: Denser Heartbeat Maintenance Around `A0`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 4 --frame-interval 0.10 --read-after-frame 0.25 --read-after-group 0.5 --log captures/rcp-heartbeat-maintained-a0-dense.txt
```

What to watch for:

- group 1 only responds:
  heartbeat maintenance is not enough; the latch still dominates
- later groups respond with the same readable block:
  heartbeat maintenance partially reopens the one-shot query path
- later groups do not return `A0`/`B0` but do return `0x40`/`0xC0` blocks:
  heartbeat cadence may be moving the panel into a different selector/state
  family instead of truly reusing the original query

### 2026-05-13 Heartbeat-Maintained Reuse Result

Captures:

- `captures/rcp-heartbeat-maintained-a0.txt`
- `captures/rcp-heartbeat-maintained-b0.txt`
- `captures/rcp-heartbeat-maintained-a0-dense.txt`

User LCD observation:

- In all three heartbeat-maintained runs, the panel never entered
  `CONNECT NOT ACT` / `CONNECTION NOT ACT` while the script was running.

#### HR1: `A0` With Heartbeat Maintenance

Observed result:

- Group 1:
  - `A0` produced `07 80 68 40 30 C5`
  - the next heartbeat frame drained the tail of that same `A0` burst
- Groups 2-4:
  - `A0` produced heartbeat-compatible RX only
  - surrounding heartbeat frames also stayed heartbeat-only

Read:

- Keeping heartbeat traffic alive after the first `A0` response did not make
  `A0` reusable.
- It did, however, keep the panel in the non-`CONNECT NOT ACT` display state.

#### HR2: `B0` With Heartbeat Maintenance

Observed result:

- Group 1:
  - `B0` produced one readable block `07 80 6C 40 30 C1`
- Groups 2-4:
  - `B0` produced heartbeat-compatible RX only
  - surrounding heartbeat frames stayed heartbeat-only

Read:

- Same outcome as `A0`: heartbeat maintenance did not reopen the one-shot query
  path for `B0`.

#### HR3: Denser Heartbeat Maintenance Around `A0`

Observed result:

- Group 1:
  - the second heartbeat frame, before `A0`, provoked `07 80 40 40 30 ED`
  - the following `A0` frame itself produced only heartbeat-compatible RX
- Groups 2-4:
  - no `A0` response
  - surrounding heartbeat frames stayed heartbeat-compatible

Read:

- Denser heartbeat traffic can push the panel into the transient `0x40` family
  state before the `A0` query arrives.
- In that case, the heartbeat-induced `0x40` response appears to consume the
  one-shot response opportunity and suppress the `A0` block.

Overall interpretation:

- Heartbeat maintenance is strong enough to affect the panel's visible
  connection state, but not strong enough by itself to make `A0`/`B0`
  reusable.
- The latch/one-shot behavior still dominates the readable query families.
- Dense heartbeat traffic can compete with or displace a later explicit query by
  provoking a transient `0x40`-family response first.
- Best current model:
  - heartbeat cadence helps hold a "live host present" condition
  - a separate selector/query opportunity still exists only once per boot or
    once per internal state phase
  - some heartbeat cadences can spend that opportunity on `0x40`/`0xC0` family
    responses instead of `A0`/`B0`

Next branch worth testing:

- intentionally provoke the transient heartbeat-induced `0x40`/`0xC0` family,
  then immediately query nearby commands to see whether that state exposes a new
  readable page
- or combine heartbeat maintenance with the synthetic CALL path, to test
  whether "host present" plus an event-path trigger behaves differently than
  "host present" plus a plain table query

## Camera-Info-Stream Hypothesis Ladder

Goal:

- Test the idea that the panel wants more than a plain link heartbeat.
- Treat one frame family as "host present" and another as possible
  camera-state/camera-info traffic.
- Check whether the transient heartbeat-induced `0x40` / `0xC0` family is a
  different readable page or context, not just noise.

Working model:

1. host heartbeat says "I am here"
2. a second family says "here is current camera state"
3. only then does the panel become fully active

### CI1: Provoke `0x40` Family, Then Query `B0`

Use the same dense double-heartbeat pattern that previously provoked
`07 80 40 40 30 ED`, then immediately ask for a known `B0` block.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 4 --frame-interval 0.10 --read-after-frame 0.25 --read-after-group 0.5 --log captures/rcp-camerainfo-40-then-b0.txt
```

Question:

- Does the `0x40` family suppress `B0`, alter `B0`, or allow a different `B0`
  response family than the normal `07 80 6C 40 30 C1`?

### CI2: Provoke `0x40` Family, Then Query `B5`

Same idea, but use the strongest discovery-like block we have.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 B5 00 80 6F" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 4 --frame-interval 0.10 --read-after-frame 0.25 --read-after-group 0.5 --log captures/rcp-camerainfo-40-then-b5.txt
```

Question:

- If `B5` is really a discovery/status block, does entering the heartbeat-driven
  `0x40` state make it return a different page, or consume it entirely?

### CI3: Probe Commands Aligned With The Heartbeat-Induced Family

Try a simple `00 -> 40` and `00 -> C0` query again, but now with a small
heartbeat run-up first instead of a bare primer.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 40 00 80 9A" --read-after-frame 0.4 --read-after-group 1.0 --log captures/rcp-camerainfo-preheart-40.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 C0 00 80 1A" --read-after-frame 0.4 --read-after-group 1.0 --log captures/rcp-camerainfo-preheart-c0.txt
```

Question:

- Do these commands return something closer to the transient `0x40` / `0xC0`
  response families when the panel has just seen two host heartbeats?

### CI4: Sustained Mixed Stream, Status-Block Style

Instead of repeating one query, rotate through a small set of known readable
blocks while keeping heartbeat in the stream.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --frame "00 00 00 00 80 DA" --frame "00 00 B5 00 80 6F" --repeat 3 --frame-interval 0.20 --read-after-frame 0.25 --read-after-group 0.8 --log captures/rcp-camerainfo-mixed-a0-b0-b5.txt
```

Question:

- Does a rotating "camera info block" stream behave differently from hammering a
  single one-shot query?

### CI5: Sustained Mixed Stream, Heartbeat-Driven Family

Rotate heartbeat around `0x40`-family and normal query candidates to see
whether the panel prefers a page/state rhythm instead of a static query.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 40 00 80 9A" --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --frame "00 00 00 00 80 DA" --frame "00 00 C0 00 80 1A" --repeat 3 --frame-interval 0.20 --read-after-frame 0.25 --read-after-group 0.8 --log captures/rcp-camerainfo-mixed-40-b0-c0.txt
```

Question:

- Does mixing the heartbeat-aligned family with a known readable block expose a
  reusable page, a new response family, or any visible panel state change?

### CI6: Sparse Heartbeat Run-Up, Then `A0`

Try the same idea as the dense `0x40` trigger, but with slower heartbeat spacing
that previously nudged the `0xC0` family.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --repeat 3 --frame-interval 1.50 --read-after-frame 0.45 --read-after-group 1.0 --log captures/rcp-camerainfo-c0-then-a0.txt
```

Question:

- Does a slower heartbeat-derived state family behave differently from the dense
  `0x40` case?

What to watch for:

- the panel stays out of `CONNECT NOT ACT` for the whole run
- a known readable block changes family or value fields
- a later query responds where it previously would not
- the transient `0x40` / `0xC0` family appears immediately before a different
  stable block
- any lamp or display change beyond the usual timeout behavior

Recommended order:

1. `CI1`
2. `CI2`
3. `CI4`
4. `CI5`
5. `CI6`

### 2026-05-13 Camera-Info-Stream Result

Captures:

- `captures/rcp-camerainfo-40-then-b0.txt`
- `captures/rcp-camerainfo-40-then-b5.txt`
- `captures/rcp-camerainfo-preheart-40.txt`
- `captures/rcp-camerainfo-preheart-c0.txt`
- `captures/rcp-camerainfo-mixed-a0-b0-b5.txt`
- `captures/rcp-camerainfo-mixed-40-b0-c0.txt`
- `captures/rcp-camerainfo-c0-then-a0.txt`

User observation:

- In `CI6`, the panel appeared to enter `CONNECT NOT ACT` and then emit a
  single latch/readable response afterward.

#### CI1: Provoke `0x40`/`0xC0`, Then Query `B0`

Observed result:

- Group 1:
  - the second heartbeat produced `07 80 C0 40 30 6D`
  - the following `B0` query produced only heartbeat-compatible RX
- Groups 2-4:
  - no structured `B0` response
  - all later frames stayed heartbeat-compatible

Read:

- The heartbeat-induced `0xC0` family did not open a new `B0` page.
- It appears to consume or displace the normal one-shot response opportunity.

#### CI2: Provoke `0x40`, Then Query `B5`

Observed result:

- Group 1:
  - the second heartbeat produced `07 80 40 40 30 ED`
  - the following `B5` query produced only heartbeat-compatible RX
- Groups 2-4:
  - `B5` remained heartbeat-compatible only

Read:

- Same story as `CI1`: the heartbeat-driven family does not seem to lead into a
  richer `B5` discovery page.
- It suppresses or replaces it.

#### CI3: Probe Commands Aligned With The Heartbeat-Induced Family

Observed result:

| Sequence | Result |
| --- | --- |
| double heartbeat, then `00 -> 40` | heartbeat-induced `07 80 40 40 30 ED`, then `00 -> 40` gave heartbeat only |
| double heartbeat, then `00 -> C0` | heartbeat-induced `07 80 40 40 30 ED`, then `00 -> C0` only drained more `07 80 40 40 30 ED` plus heartbeat |

Read:

- The pre-heartbeat `0x40` state does not make the explicit `40` or `C0`
  commands bloom into a clear new readable block.
- The explicit commands mostly arrive after the transient state is already in
  progress.

#### CI4: Sustained Mixed Stream, Status-Block Style

Observed result:

- Group 1:
  - `A0` produced `07 80 68 40 30 C5`
  - later `B0` and `B5` in the same mixed stream were heartbeat only
- Groups 2-3:
  - `A0`, `B0`, and `B5` were all heartbeat-compatible only

Read:

- A rotating "camera info block" stream did not keep opening new readable
  status pages.
- The first successful block still wins, then the rest are suppressed.

#### CI5: Sustained Mixed Stream, Heartbeat-Driven Family

Observed result:

- Group 1:
  - `00 -> 40` produced `07 80 50 40 30 FD`
  - later `B0` and `C0` in the same stream were heartbeat only
- Groups 2-3:
  - all later frames were heartbeat-compatible only

Read:

- This confirms `00 -> 40` is a stable explicit query family in its own right.
- But, again, the first successful readable family suppresses later queries in
  the same stream.

#### CI6: Sparse Heartbeat Run-Up, Then `A0`

Observed result:

- Group 1:
  - two widely spaced heartbeat frames stayed heartbeat-compatible
  - the later `A0` produced a delayed `07 80 68 40 30 C5` burst
- Groups 2-3:
  - the same pattern returned heartbeat-compatible RX only

Read:

- This matches the user's observation well: a slow run-up can leave the panel
  visibly in `CONNECT NOT ACT`, yet a later single readable/latch response can
  still occur afterward.
- That suggests the visible display state and the one-shot readable window are
  related, but not identical.
- A panel that has already fallen into `CONNECT NOT ACT` may still accept one
  later readable query under the right timing.

Overall interpretation:

- The "camera info stream" hypothesis is not wrong, but this particular test set
  did not show a sustained multi-block camera-state feed.
- The heartbeat-induced `0x40` / `0xC0` family is real, but it mostly acts like
  a competing one-shot response family, not a gateway into a larger page.
- Mixed streams still collapse into "first successful block wins."
- The cleanest new points are:
  - `00 -> 40` is a real explicit readable family: `07 80 50 40 30 FD`
  - heartbeat cadence can provoke `0x40` / `0xC0` families without an explicit
    query
  - a visible `CONNECT NOT ACT` state does not completely forbid one later
    readable/latch response

Next branch worth testing:

- isolate the delayed-after-`CONNECT NOT ACT` case from `CI6` with a small
  timing ladder around the final `A0`
- compare `A0`, `B0`, and `40` after a deliberate wait-until-`CONNECT NOT ACT`
  phase
- test whether the `0x40` / `0xC0` families are better thought of as alternate
  readable pages rather than camera-info keepalives

## `CONNECT NOT ACT` vs Latch-State Decoupling Test

Goal:

- Prove that the visible `CONNECT NOT ACT` state and the one-shot readable/latch
  state are related, but not the same thing.
- Show one case where the latch is spent even though the panel does **not**
  enter `CONNECT NOT ACT`.
- Show another case where the panel **does** enter `CONNECT NOT ACT` and can
  still produce one later readable response.

Interpretation target:

- If both paths happen, the LCD state is not a direct indicator of whether the
  one-shot readable window is still available.

### DL1: Latch Spent While `CONNECT NOT ACT` Stays Away

This is the "no visible timeout, but latch already consumed" proof.

Watch the LCD throughout. The expected result is that the panel stays out of
`CONNECT NOT ACT` while running.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 3 --frame-interval 0.25 --read-after-frame 0.35 --read-after-group 0.5 --log captures/rcp-decouple-no-connectnotact-a0.txt
```

What proves decoupling:

- group 1: `A0` responds
- later groups: `A0` does not respond
- LCD never enters `CONNECT NOT ACT` during the run

That shows the latch can be spent even while the display does not show the
timeout/not-active state.

### DL2: Deliberate `CONNECT NOT ACT`, Then Late `A0`

This is the "visible timeout, but one late readable response still exists"
proof.

Method:

- power-cycle
- watch for the panel to show `CONNECT NOT ACT` between the first heartbeat and
  the later `A0`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame-interval 2.50 --read-after-frame 0.30 --read-after-group 1.0 --log captures/rcp-decouple-connectnotact-then-a0.txt
```

What proves decoupling:

- the panel visibly enters `CONNECT NOT ACT`
- the later `A0` still returns `07 80 68 40 30 C5`

That shows the visible `CONNECT NOT ACT` state does not automatically mean the
readable/latch opportunity is gone.

### DL3: Deliberate `CONNECT NOT ACT`, Then Two Late `A0` Queries

This tightens `DL2` by checking whether the first late query can consume the
remaining one-shot readable window while the LCD state stays the same.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame "00 00 A0 00 80 7A" --frame-interval 2.50 --read-after-frame 0.30 --read-after-group 1.0 --log captures/rcp-decouple-connectnotact-then-a0-a0.txt
```

What proves decoupling:

- panel enters `CONNECT NOT ACT`
- first late `A0` responds
- second late `A0` does not
- LCD remains in the same visible state

That shows the latch/readable state can change while the display state does not.

### DL4: Compare Late `A0`, `B0`, and `40` After `CONNECT NOT ACT`

This checks whether only some readable families survive the visible timeout
state.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --frame-interval 2.50 --read-after-frame 0.30 --read-after-group 1.0 --log captures/rcp-decouple-connectnotact-then-b0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 40 00 80 9A" --frame-interval 2.50 --read-after-frame 0.30 --read-after-group 1.0 --log captures/rcp-decouple-connectnotact-then-40.txt
```

Useful outcomes:

- only `A0` responds late:
  the post-timeout window may be family-specific
- `A0`, `B0`, and `40` all respond late:
  the timeout state is clearly not the same as the latch state
- none respond late:
  the `CI6` late response was timing-sensitive and needs a tighter delay ladder

### DL5: Tight Delay Ladder Around Late `A0`

If `DL2` or `DL3` looks close but inconsistent, bracket the timeout edge.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame-interval 2.00 --read-after-frame 0.30 --read-after-group 1.0 --log captures/rcp-decouple-delay-2000ms-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame-interval 2.25 --read-after-frame 0.30 --read-after-group 1.0 --log captures/rcp-decouple-delay-2250ms-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame-interval 2.50 --read-after-frame 0.30 --read-after-group 1.0 --log captures/rcp-decouple-delay-2500ms-a0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame-interval 2.75 --read-after-frame 0.30 --read-after-group 1.0 --log captures/rcp-decouple-delay-2750ms-a0.txt
```

Recommended order:

1. `DL1`
2. `DL2`
3. `DL3`
4. `DL4`
5. `DL5` only if `DL2`/`DL3` are close but inconsistent

### 2026-05-13 `CONNECT NOT ACT` vs Latch-State Decoupling Result

Captures:

- `captures/rcp-decouple-no-connectnotact-a0.txt`
- `captures/rcp-decouple-connectnotact-then-a0.txt`
- `captures/rcp-decouple-connectnotact-then-a0-a0.txt`

User LCD observation:

- `DL1`: the panel never presented `CONNECT NOT ACT` while the script was
  running.
- `DL2` and `DL3`: the panel presented `CONNECT NOT ACT` at the start of the
  script.

#### DL1: Latch Spent While `CONNECT NOT ACT` Stays Away

Observed result:

- Group 1:
  - `A0` produced a readable block, but this run returned
    `07 80 E8 40 30 45` rather than `07 80 68 40 30 C5`
- Groups 2-3:
  - `A0` produced heartbeat-compatible RX only
- LCD:
  - no `CONNECT NOT ACT` while the script was active

Read:

- This is strong evidence for decoupling in one direction:
  the readable/latch state can be consumed while the panel does **not** show
  `CONNECT NOT ACT`.
- It also reinforces that the `A0` family can vary between at least `68` and
  `E8` forms depending on context/timing.

#### DL2: Deliberate `CONNECT NOT ACT`, Then Late `A0`

Observed result:

- The panel showed `CONNECT NOT ACT` early in the run.
- The later `A0` query produced heartbeat-compatible RX only.
- No delayed `A0` readable block appeared at this exact timing.

Read:

- At a `2.50 s` gap in this two-frame form, visible `CONNECT NOT ACT` did not
  guarantee a late readable response.
- This weakens the simple "enter `CONNECT NOT ACT`, then `A0` always still
  works once" idea.

#### DL3: Deliberate `CONNECT NOT ACT`, Then Two Late `A0` Queries

Observed result:

- The panel showed `CONNECT NOT ACT` early in the run.
- Both later `A0` queries produced heartbeat-compatible RX only.
- No readable block appeared on either late `A0`.

Read:

- This does not provide the hoped-for second half of the decoupling proof.
- Instead, it says that once the panel has visibly entered `CONNECT NOT ACT` in
  this specific timing pattern, the late `A0` opportunity may already be gone or
  may require narrower timing than `2.50 s`.

Overall interpretation:

- We now have solid evidence for one half of the claim:
  `CONNECT NOT ACT` is **not required** for the latch/readable state to be
  consumed.
- We do **not** yet have equally solid evidence for the other half at this
  timing:
  a visible `CONNECT NOT ACT` state did not still yield a late `A0` response in
  `DL2` or `DL3`.
- So the current safest statement is:
  the LCD state and the latch/readable state are not directly coupled, but the
  exact post-timeout behavior is more timing-sensitive than the earlier `CI6`
  observation first suggested.

Best next move:

- run `DL4` and then a tight `DL5` delay bracket
- especially around `2.00 s`, `2.25 s`, `2.50 s`, and `2.75 s`
- because the difference between the earlier `CI6` late hit and these no-hit
  decoupling runs is now most likely timing, framing shape, or both

## Heartbeat-Family Echo Probes

Goal:

- Test whether the newer heartbeat-induced response families behave more like
  camera-state blocks that the host is expected to mirror or acknowledge.
- Compare exact echo versus host-shaped mirror for the same apparent payload.

Best current echo candidates:

- heartbeat-induced:
  - `07 80 40 40 30 ED`
  - `07 80 40 60 30 CD`
  - `07 80 C0 40 30 6D`
- explicit `00 -> 40` family:
  - `07 80 50 40 30 FD`
- context-sensitive `A0` variant:
  - `07 80 E8 40 30 45`

Host-shaped mirrors:

- `00 00 40 40 30 6A`
- `00 00 40 60 30 4A`
- `00 00 C0 40 30 EA`
- `00 00 50 40 30 7A`
- `00 00 E8 40 30 C2`

### HE1: Exact Echo Of `07 80 40 40 30 ED`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "07 80 40 40 30 ED" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 3 --frame-interval 0.10 --read-after-frame 0.25 --read-after-group 0.5 --log captures/rcp-heartbeat-echo-exact-404030ed.txt
```

### HE2: Host-Shaped Mirror Of `07 80 40 40 30 ED`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 40 40 30 6A" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 3 --frame-interval 0.10 --read-after-frame 0.25 --read-after-group 0.5 --log captures/rcp-heartbeat-echo-host-404030.txt
```

### HE3: Exact Echo Of `07 80 C0 40 30 6D`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "07 80 C0 40 30 6D" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 3 --frame-interval 0.10 --read-after-frame 0.25 --read-after-group 0.5 --log captures/rcp-heartbeat-echo-exact-c040306d.txt
```

### HE4: Host-Shaped Mirror Of `07 80 C0 40 30 6D`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 C0 40 30 EA" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 3 --frame-interval 0.10 --read-after-frame 0.25 --read-after-group 0.5 --log captures/rcp-heartbeat-echo-host-c04030.txt
```

### HE5: Exact Echo Of Explicit `00 -> 40` Response

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 40 00 80 9A" --frame "07 80 50 40 30 FD" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.6 --log captures/rcp-heartbeat-echo-exact-504030fd.txt
```

### HE6: Host-Shaped Mirror Of Explicit `00 -> 40` Response

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 40 00 80 9A" --frame "00 00 50 40 30 7A" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.6 --log captures/rcp-heartbeat-echo-host-504030.txt
```

### HE7: Echo The `A0` Variant From DL1

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "07 80 E8 40 30 45" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.6 --log captures/rcp-heartbeat-echo-exact-e8403045.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.6 --log captures/rcp-heartbeat-echo-host-e84030.txt
```

What would count as a hit:

- any new non-heartbeat RCP frame after the echo/mirror
- the panel leaving or changing behavior around `CONNECT NOT ACT`
- a previously one-shot query family becoming readable again
- the echo path producing a repeatable family-specific response

Recommended order:

1. `HE2`
2. `HE4`
3. `HE1`
4. `HE3`
5. `HE6`
6. `HE7`

### 2026-05-13 Heartbeat-Family Echo Result

Captures:

- `captures/rcp-heartbeat-echo-host-404030.txt`
- `captures/rcp-heartbeat-echo-host-c04030.txt`
- `captures/rcp-heartbeat-echo-exact-404030ed.txt`
- `captures/rcp-heartbeat-echo-exact-c040306d.txt`
- `captures/rcp-heartbeat-echo-host-504030.txt`
- `captures/rcp-heartbeat-echo-exact-504030fd.txt`
- `captures/rcp-heartbeat-echo-exact-e8403045.txt`
- `captures/rcp-heartbeat-echo-host-e84030.txt`

#### HE1 / HE2 / HE3 / HE4: Echoing The Heartbeat-Induced `0x40` / `0xC0` Families

Observed result:

- `07 80 40 40 30 ED` exact echo: no new structured response after the echo.
- `00 00 40 40 30 6A` host-shaped mirror: no new structured response after the
  mirror.
- `07 80 C0 40 30 6D` exact echo: no new structured response after the echo.
- `00 00 C0 40 30 EA` host-shaped mirror: no new structured response after the
  mirror.

Read:

- The heartbeat-induced `0x40` / `0xC0` families do not currently look like
  payloads the panel expects to receive back as an echo or mirror.
- They behave more like one-way readable/context responses than a bidirectional
  acknowledge-and-continue exchange.

#### HE5 / HE6: Echoing The Explicit `00 -> 40` Response Family

Observed result:

- `00 -> 40` again produced the known readable block `07 80 50 40 30 FD`.
- Exact echo of that block, `07 80 50 40 30 FD`, produced no new structured
  response.
- Host-shaped mirror, `00 00 50 40 30 7A`, also produced no new structured
  response.
- In the host-shaped mirror run, a later plain heartbeat again provoked the
  transient `07 80 40 40 30 ED` family rather than any direct reply to the
  mirror.

Read:

- The explicit `00 -> 40` family also does not behave like something the panel
  wants echoed back in either exact or host-shaped form.
- The reappearance of `07 80 40 40 30 ED` after the mirror reinforces that the
  heartbeat/context state machine is still active in the background and can
  dominate what happens next.

#### HE7: Echoing The `A0` Variant From DL1

Observed result:

Two distinct outcomes appeared here:

- Exact echo path:
  - `00 -> A0` did not log its usual readable block in the immediate window
  - sending exact `07 80 E8 40 30 45` produced a new unexpected response:
    `07 C0 7A 50 A6 11`
- Host-shaped mirror path:
  - `00 00 E8 40 30 C2` produced another new structured response:
    `07 80 FA 50 26 51`

Read:

- This is the first heartbeat-family echo probe set that produced genuinely new
  structured responses rather than just heartbeat.
- The `E8`/`FA` area now looks much more interesting than the `40`/`C0` exact
  echo families.
- We do not yet know whether:
  - `07 C0 7A 50 A6 11` is a valid stable family or a context-fragmented reply
  - `07 80 FA 50 26 51` is a proper readable page, an error/status block, or a
    transformed reply to the `E8`-family mirror
- But these are strong enough to treat as new leads rather than noise.

Overall interpretation:

- Most heartbeat-family responses do **not** behave like simple values the host
  should echo back.
- The exception is the `A0`-variant / `E8` area, where echoing or host-mirroring
  produced fresh structured responses.
- So the most promising echo branch is no longer generic `40`/`C0` heartbeat
  mirroring, but focused follow-up around:
  - `07 80 E8 40 30 45`
  - `07 80 FA 50 26 51`
  - `07 C0 7A 50 A6 11`

Next branch worth testing:

- reproduce the `E8`-mirror path cleanly and see whether `FA` and `7A` are
  stable
- try host-shaped and exact echoes of `07 80 FA 50 26 51`
- probe whether `FA` / `7A` correspond to nearby explicit host queries such as
  `00 -> FA` or `00 -> 7A`

### HE8: Exact Echo Of `07 80 FA 50 26 51`

Recreate the `E8` host-mirror path that previously produced
`07 80 FA 50 26 51`, then immediately send the exact `FA` frame back.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E8 40 30 C2" --frame "07 80 FA 50 26 51" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-exact-fa502651.txt
```

### HE9: Host-Shaped Mirror Of `07 80 FA 50 26 51`

Host-shaped checksum for `00 00 FA 50 26 ??` is `D6`.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E8 40 30 C2" --frame "00 00 FA 50 26 D6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-host-fa5026.txt
```

What would count as a hit:

- any fresh structured response after the `FA` echo or mirror
- a repeat of `07 80 FA 50 26 51` or `07 C0 7A 50 A6 11`
- any new family in the `50` / `7A` / `FA` region
- visible panel behavior changing beyond the usual `CONNECT NOT ACT`

### 2026-05-13 `FA` Echo Follow-Up Result

Captures:

- `captures/rcp-heartbeat-echo-exact-fa502651.txt`
- `captures/rcp-heartbeat-echo-host-fa5026.txt`

Result summary:

- Both runs behaved the same way.
- In group 1, the host-shaped `E8` step
  `00 00 E8 40 30 C2` produced a new structured family:
  `07 80 7A 50 26 D1`
- That `7A` family appeared twice immediately after the `E8` host-mirror step in
  both runs.
- The following `FA` step, whether exact echo `07 80 FA 50 26 51` or
  host-shaped mirror `00 00 FA 50 26 D6`, did not produce a fresh distinct
  reply. The receive window only showed one more `07 80 7A 50 26 D1` followed by
  heartbeat.
- In group 2, both runs were heartbeat-only after the same sequence.

Interpretation:

- The meaningful branch point still appears to be the host-shaped `E8` frame,
  not the subsequent `FA` echo/mirror.
- `07 80 FA 50 26 51` is not yet behaving like a value the host should directly
  answer to advance the exchange.
- `07 80 7A 50 26 D1` is now a stronger candidate for a real sibling response
  family in this branch than `07 C0 7A 50 A6 11`, because it reproduced in both
  HE8 and HE9 group-1 runs.
- The fact that HE8 and HE9 were serially identical suggests the panel likely
  emitted a delayed/queued `7A`-family response from the `E8` host-mirror step,
  rather than parsing the `FA` follow-up as a meaningful new command.

Best current model of this branch:

- `00 00 E8 40 30 C2` can lead into a `7A` / `FA` response family.
- `07 80 FA 50 26 51` may be one branch member, but answering it directly did
  not produce a second-stage exchange.
- The next useful probe should probably target `07 80 7A 50 26 D1` directly, or
  compare exact-vs-host handling of `7A` rather than `FA`.

### HE10: Exact Echo Of `07 80 7A 50 26 D1`

Recreate the host-shaped `E8` path that now reproducibly yields
`07 80 7A 50 26 D1`, then immediately send that exact `7A` frame back.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E8 40 30 C2" --frame "07 80 7A 50 26 D1" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-exact-7a5026d1.txt
```

### HE11: Host-Shaped Mirror Of `07 80 7A 50 26 D1`

Host-shaped checksum for `00 00 7A 50 26 ??` is `56`.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-host-7a5026.txt
```

What would count as a hit:

- any new non-heartbeat response after the `7A` echo or mirror
- a stable second-stage family after `07 80 7A 50 26 D1`
- recurrence of `FA`, `7A`, or another new `50`-region family
- any visible panel behavior change beyond the usual `CONNECT NOT ACT`

### 2026-05-13 `7A` Echo Follow-Up Result

Captures:

- `captures/rcp-heartbeat-echo-exact-7a5026d1.txt`
- `captures/rcp-heartbeat-echo-host-7a5026.txt`

Result summary:

- Both runs again point to the host-shaped `E8` step as the meaningful branch
  trigger.
- In group 1, `00 00 E8 40 30 C2` reproduced `07 80 7A 50 26 D1` in both tests.
- The exact-echo run was the cleanest:
  - `00 00 E8 40 30 C2` produced one `07 80 7A 50 26 D1`
  - exact echo `07 80 7A 50 26 D1` produced only heartbeat afterwards
- The host-shaped mirror run behaved like the earlier `FA` tests:
  - `00 00 E8 40 30 C2` produced `07 80 7A 50 26 D1` twice
  - host-shaped `00 00 7A 50 26 56` then only drained one more
    `07 80 7A 50 26 D1` followed by heartbeat
- In group 2, both tests were heartbeat-only after the same sequence.

Interpretation:

- `07 80 7A 50 26 D1` is now a confirmed reproducible response family on the
  host-shaped `E8` branch.
- But directly echoing `7A`, whether exact or host-shaped, still did not create
  a second-stage exchange, visible state change, or reusable session behavior.
- This makes `7A` look more like another readable/event family emitted by the
  branch, not yet like the next host command the panel expects.
- The asymmetry is useful:
  - host-shaped `E8` can provoke `7A`
  - `7A` itself does not obviously provoke anything back

Best current model of the `E8` branch:

- `00 00 E8 40 30 C2` is the active stimulus.
- `07 80 7A 50 26 D1` is the most reproducible downstream response in that
  branch.
- `07 80 FA 50 26 51` and `07 C0 7A 50 A6 11` remain side-family observations,
  but neither has overtaken `7A` as the strongest lead.

### HE12: Nearby Host-Shaped `E8` Neighbors

Test whether the `7A` branch is specific to `E8`, or whether nearby host-shaped
commands in the same region also provoke structured responses.

Checksums:

- `00 00 E7 40 30 CD`
- `00 00 E9 40 30 C3`
- `00 00 EA 40 30 C0`

#### HE12a: Host-Shaped `E7`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E7 40 30 CD" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-neighbor-e7.txt
```

#### HE12b: Host-Shaped `E9`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E9 40 30 C3" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-neighbor-e9.txt
```

#### HE12c: Host-Shaped `EA`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EA 40 30 C0" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-neighbor-ea.txt
```

What would count as a hit:

- any non-heartbeat response after `E7`, `E9`, or `EA`
- recurrence of `07 80 7A 50 26 D1`
- recurrence of `07 80 FA 50 26 51` or `07 C0 7A 50 A6 11`
- any new stable family that suggests `E8` belongs to a broader selector block

### 2026-05-13 `E8` Neighbor Result

Captures:

- `captures/rcp-heartbeat-neighbor-e7.txt`
- `captures/rcp-heartbeat-neighbor-e9.txt`
- `captures/rcp-heartbeat-neighbor-ea.txt`

Result summary:

- `E7`, `E9`, and `EA` do not behave the same way, so this is not a simple
  "everything around `E8` lands in the same branch" region.
- All three effects only appeared in group 1. Group 2 was heartbeat-only in
  every run.

Observed outputs:

| Host step | Structured result |
| --- | --- |
| `00 00 E7 40 30 CD` | later heartbeat provoked `07 80 40 40 30 ED` |
| `00 00 E9 40 30 C3` | produced `07 80 7A 28 D3 5C` |
| `00 00 EA 40 30 C0` | later heartbeat provoked `07 80 C0 40 30 6D` |

Interpretation:

- `E8` still appears special, because it is the one that most cleanly enters the
  reproducible `07 80 7A 50 26 D1` branch.
- `E9` is the first nearby neighbor to produce a clearly related but distinct
  `7A`-family frame:
  - `07 80 7A 28 D3 5C`
- `E7` and `EA` look more like they bias the panel toward known heartbeat-family
  transients (`07 80 40 40 30 ED` and `07 80 C0 40 30 6D`) rather than the
  `7A` / `FA` branch.
- So the emerging picture is not a broad contiguous "E* equals same page"
  block, but a mixed neighborhood where nearby host-shaped values steer the
  panel into different transient/readable families.

Best current follow-up:

- treat `E9` as the strongest neighbor lead
- test exact and host-shaped handling of `07 80 7A 28 D3 5C`
- optionally compare `E8` vs `E9` timing to see whether the differing `7A`
  payloads reflect selector choice or timing/context

### HE13: Exact Echo Of `07 80 7A 28 D3 5C`

Recreate the `E9` neighbor path, then immediately send the exact `7A`-family
response back to the panel.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E9 40 30 C3" --frame "07 80 7A 28 D3 5C" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-exact-7a28d35c.txt
```

### HE14: Host-Shaped Mirror Of `07 80 7A 28 D3 5C`

Host-shaped checksum for `00 00 7A 28 D3 ??` is `DB`.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E9 40 30 C3" --frame "00 00 7A 28 D3 DB" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-host-7a28d3.txt
```

What would count as a hit:

- any non-heartbeat response after the exact or host-shaped `7A 28 D3` frame
- a repeat of `07 80 7A 28 D3 5C`
- crossover into the `E8`-style `07 80 7A 50 26 D1` or `07 80 FA 50 26 51`
  branch
- any new stable family that suggests `E9` is a second entry point into the
  same broader selector surface

### 2026-05-13 `E9` Mirror Result

Captures:

- `captures/rcp-heartbeat-echo-exact-7a28d35c.txt`
- `captures/rcp-heartbeat-echo-host-7a28d3.txt`

Result summary:

- `E9` cleanly reproduced its `7A`-family response in group 1 in both tests:
  - `07 80 7A 28 D3 5C`
- Exact echo `07 80 7A 28 D3 5C` produced only heartbeat afterwards.
- Host-shaped mirror `00 00 7A 28 D3 DB` did not open a new stage either; it
  only drained more copies of `07 80 7A 28 D3 5C` before falling back to
  heartbeat.
- Group 2 was heartbeat-only in both tests.

Interpretation:

- `E9` behaves very much like the `E8` branch structurally:
  - the host-shaped `E9` step is the active stimulus
  - the downstream `7A` response is real and reproducible
  - but directly echoing or mirroring that `7A` response does not advance the
    exchange
- So `E8` and `E9` now look like sibling selector-style entry points into
  related but distinct `7A`-family outputs:
  - `E8` -> `07 80 7A 50 26 D1`
  - `E9` -> `07 80 7A 28 D3 5C`
- This strengthens the idea that the `Ex` region may be selecting pages or
  classes of status rather than carrying the real session-advance command.

Best current next move:

- test more nearby `Ex` values to see whether this is a broader selector strip
- or try explicit host queries for the downstream family bytes (`00 -> 7A`,
  `00 -> FA`) after `E8`/`E9` selection

### HE15: Continue Outward In The `Ex` Neighborhood

Push one step farther out from the now-interesting `E8` / `E9` region and see
whether the selector-like behavior continues, shifts family again, or collapses
back into heartbeat-only behavior.

Checksums:

- `00 00 E6 40 30 CC`
- `00 00 EB 40 30 C1`
- `00 00 EC 40 30 C6`

#### HE15a: Host-Shaped `E6`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E6 40 30 CC" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-neighbor-e6.txt
```

#### HE15b: Host-Shaped `EB`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EB 40 30 C1" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-neighbor-eb.txt
```

#### HE15c: Host-Shaped `EC`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-neighbor-ec.txt
```

What would count as a hit:

- any new non-heartbeat response after `E6`, `EB`, or `EC`
- recurrence of the `E8`-style `07 80 7A 50 26 D1` or `E9`-style
  `07 80 7A 28 D3 5C`
- recurrence of known heartbeat-family transients like `07 80 40 40 30 ED` or
  `07 80 C0 40 30 6D`
- any additional family that strengthens the idea of a selector strip across
  the `Ex` region

### 2026-05-13 Outward `Ex` Result

Captures:

- `captures/rcp-heartbeat-neighbor-e6.txt`
- `captures/rcp-heartbeat-neighbor-eb.txt`
- `captures/rcp-heartbeat-neighbor-ec.txt`

Result summary:

- `E6` and `EB` behaved the same way:
  - no immediate structured reply at the `Ex` step
  - the following heartbeat provoked `07 80 40 40 30 ED`
- `EC` produced a new structured family in group 1:
  - `07 80 7B 50 26 D0`
- As with the other recent selector tests, all interesting behavior was group 1
  only. Group 2 was heartbeat-only.

Observed outputs:

| Host step | Structured result |
| --- | --- |
| `00 00 E6 40 30 CC` | later heartbeat provoked `07 80 40 40 30 ED` |
| `00 00 EB 40 30 C1` | later heartbeat provoked `07 80 40 40 30 ED` |
| `00 00 EC 40 30 C6` | produced `07 80 7B 50 26 D0` |

Interpretation:

- The `Ex` neighborhood is continuing to look like a selector-like strip with
  mixed outcomes, not a uniform command class.
- `E6`, `E7`, and `EB` now cluster together as values that bias into the
  `07 80 40 40 30 ED` heartbeat-family transient.
- `E8` and `E9` open distinct `7A`-family responses:
  - `E8` -> `07 80 7A 50 26 D1`
  - `E9` -> `07 80 7A 28 D3 5C`
- `EC` now extends that pattern one step further with a new sibling:
  - `EC` -> `07 80 7B 50 26 D0`
- `EA` remains its own case steering toward `07 80 C0 40 30 6D`.

Best current follow-up:

- treat `EC` as the next strong lead and test exact/host-shaped handling of
  `07 80 7B 50 26 D0`
- optionally try `ED` next to see whether the `7B` branch continues

### HE16: Exact Echo Of `07 80 7B 50 26 D0`

Recreate the `EC` selector path, then immediately send the exact `7B`-family
response back to the panel.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EC 40 30 C6" --frame "07 80 7B 50 26 D0" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-exact-7b5026d0.txt
```

### HE17: Host-Shaped Mirror Of `07 80 7B 50 26 D0`

Host-shaped checksum for `00 00 7B 50 26 ??` is `57`.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EC 40 30 C6" --frame "00 00 7B 50 26 57" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-host-7b5026.txt
```

What would count as a hit:

- any non-heartbeat response after the exact or host-shaped `7B 50 26` frame
- a repeat of `07 80 7B 50 26 D0`
- crossover into the `7A`-family branches or any new sibling family
- any sign that `EC` is a second-stage exchange rather than just another
  selector-produced readable response

### 2026-05-13 `EC` Mirror Result

Captures:

- `captures/rcp-heartbeat-echo-exact-7b5026d0.txt`
- `captures/rcp-heartbeat-echo-host-7b5026.txt`

Result summary:

- `EC` did not behave exactly like the earlier `E8` / `E9` branches.
- In the host-shaped mirror run:
  - `00 00 EC 40 30 C6` reproduced `07 80 7B 50 26 D0` in group 1
  - host-shaped mirror `00 00 7B 50 26 57` did not advance the exchange; it
    only drained one more `07 80 7B 50 26 D0` and then returned to heartbeat
- In the exact-echo run:
  - the `EC` selector step itself did not expose the `7B` family inside the
    immediate read window
  - but exact echo `07 80 7B 50 26 D0` produced a fresh new family:
    `07 C0 2F 95 09 2E`
  - that new family appeared twice in group 1, then the run returned to
    heartbeat
- Group 2 was heartbeat-only in both runs.

Interpretation:

- `EC` is the first `Ex` selector case so far where the exact downstream echo
  appears to matter more than the host-shaped mirror.
- That makes the `7B` branch more interesting than the `7A` branches:
  - `E8` / `E9` downstream `7A` echoes did not produce a clean second-stage
    family
  - exact `7B` echo produced `07 C0 2F 95 09 2E`
- `07 C0 2F 95 09 2E` is now a strong follow-up target. It may be:
  - a second-stage response
  - a different page/family reached through exact `7B` echo
  - or a context-specific branch member that only appears on the exact-echo
    path

Best current follow-up:

- test exact and host-shaped handling of `07 C0 2F 95 09 2E`
- optionally compare whether exact `EC -> 7B` echo is timing-sensitive, since
  the host-shaped mirror path did not reach the same family

### HE18: Exact Echo Of `07 C0 2F 95 09 2E`

Recreate the `EC -> 7B` exact-echo branch, then immediately send the new
`2F 95 09` family frame back exactly as seen.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EC 40 30 C6" --frame "07 80 7B 50 26 D0" --frame "07 C0 2F 95 09 2E" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-exact-2f95092e.txt
```

### HE19: Host-Shaped Mirror Of `07 C0 2F 95 09 2E`

Host-shaped checksum for `00 00 2F 95 09 ??` is `E9`.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EC 40 30 C6" --frame "07 80 7B 50 26 D0" --frame "00 00 2F 95 09 E9" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-host-2f9509.txt
```

What would count as a hit:

- any non-heartbeat response after the exact or host-shaped `2F 95 09` frame
- a repeat of `07 C0 2F 95 09 2E`
- any fresh family after the `2F` stage that suggests a real chained exchange
- any sign that the `EC -> 7B -> 2F` path is the closest thing yet to a proper
  request/response ladder

### 2026-05-13 `2F` Mirror Result

Captures:

- `captures/rcp-heartbeat-echo-exact-2f95092e.txt`
- `captures/rcp-heartbeat-echo-host-2f9509.txt`

Result summary:

- These runs did **not** extend the `EC -> 7B -> 2F` path into a stable next
  stage.
- Instead, group 1 shifted the `EC` selector response itself into a new sibling
  family:
  - `07 80 FB 50 26 50`
- That happened in both runs, before the `2F` follow-up frame was even sent.
- After that:
  - exact `07 C0 2F 95 09 2E` produced only heartbeat
  - host-shaped `00 00 2F 95 09 E9` also produced only heartbeat
- Group 2 was heartbeat-only in both tests.

Observed group-1 shape:

| Step | Exact-echo run | Host-mirror run |
| --- | --- | --- |
| `00 00 EC 40 30 C6` | `07 80 FB 50 26 50` x2 | `07 80 FB 50 26 50` x3 |
| `07 80 7B 50 26 D0` | heartbeat only | heartbeat only |
| `2F` follow-up | heartbeat only | heartbeat only |

Interpretation:

- We did **not** get a reproducible chained reply to the `2F` stage.
- The more important finding is that the `EC` branch itself is context-sensitive
  and can emit at least two sibling downstream families:
  - `07 80 7B 50 26 D0`
  - `07 80 FB 50 26 50`
- That makes the `EC` branch look more like a selector into a family space than
  a strict linear ladder.
- `07 C0 2F 95 09 2E` is still real from the earlier run, but these follow-ups
  did not confirm it as the next stable step in an ongoing exchange.

Best current model:

- We are getting closer to a *structured* understanding of the RCP:
  certain host-side `Ex` values reliably push it into specific response
  families.
- But we are **not yet** at a stable "conversation" where the panel is clearly
  accepting our last reply and moving to the next deterministic turn.
- Right now the strongest evidence is for:
  - selector-like host entries (`E8`, `E9`, `EC`)
  - family-specific downstream responses (`7A`, `7B`, `FB`)
  - occasional exact-echo sensitivity on some branches
  - but not a fully reproducible multi-turn protocol ladder yet

### HE20: `EC` Timing / Context Split

Try to separate whether `EC` chooses `7B` vs `FB` because of timing around the
selector step, or because of the deeper branch context.

#### HE20a: `EC` With Shorter Spacing

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.10 --read-after-frame 0.25 --read-after-group 0.8 --log captures/rcp-heartbeat-ec-short-spacing.txt
```

#### HE20b: `EC` With Longer Spacing

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.35 --read-after-frame 0.35 --read-after-group 0.8 --log captures/rcp-heartbeat-ec-long-spacing.txt
```

#### HE20c: `EC` Without Leading `A0`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-ec-no-a0.txt
```

What would count as a hit:

- one setup consistently yielding `07 80 7B 50 26 D0`
- another setup consistently yielding `07 80 FB 50 26 50`
- evidence that leading `A0` is part of the selector context rather than just a
  neutral primer

### HE21: Exact Echo Of `07 80 FB 50 26 50`

If `FB` is a real sibling branch, exact echoing it may show whether it behaves
more like `7B` or like the inert `7A` families.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EC 40 30 C6" --frame "07 80 FB 50 26 50" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-exact-fb502650.txt
```

### HE22: Host-Shaped Mirror Of `07 80 FB 50 26 50`

Host-shaped checksum for `00 00 FB 50 26 ??` is `D7`.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 EC 40 30 C6" --frame "00 00 FB 50 26 D7" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-echo-host-fb5026.txt
```

What would count as a hit:

- any non-heartbeat response after exact or host-shaped `FB`
- crossover from `FB` into `2F`, `7B`, or another fresh family
- evidence that `FB` is a meaningful downstream branch rather than just a
  selector-side variant

### 2026-05-13 `EC` Timing And `FB` Result

Captures:

- `captures/rcp-heartbeat-ec-short-spacing.txt`
- `captures/rcp-heartbeat-ec-long-spacing.txt`
- `captures/rcp-heartbeat-ec-no-a0.txt`
- `captures/rcp-heartbeat-echo-exact-fb502650.txt`
- `captures/rcp-heartbeat-echo-host-fb5026.txt`

#### HE20: Timing / Context Split

Observed outcomes:

| Setup | Group-1 result |
| --- | --- |
| short spacing (`0.10 s`) | `07 80 7B 50 26 D0` |
| long spacing (`0.35 s`) | `07 80 7B 50 26 D0` |
| no leading `A0` | `07 80 C0 40 30 6D` |

Interpretation:

- The `A0` lead-in matters a lot for the `EC` branch.
- With `A0` present, both short and long spacing still favored
  `07 80 7B 50 26 D0`.
- Without `A0`, `EC` collapsed back into the known heartbeat-family transient
  `07 80 C0 40 30 6D`.
- So the best current read is that `A0` is part of the selector context for the
  `EC -> 7B/FB` family space, not just a neutral primer.
- Timing still may matter for `7B` vs `FB`, but this batch says context matters
  more strongly than spacing in the tested range.

#### HE21 / HE22: `FB` Echo Handling

Observed outcomes:

| Setup | Group-1 result |
| --- | --- |
| exact `07 80 FB 50 26 50` | `EC` again produced `07 80 7B 50 26 D0`; exact `FB` echo produced no new family |
| host-shaped `00 00 FB 50 26 D7` | `EC` produced `07 80 7B 50 26 D0`; host-shaped `FB` mirror produced no new family |

Interpretation:

- `FB` did not behave like a meaningful next-turn reply target.
- Both exact and host-shaped `FB` handling fell flat after the `EC` selector
  produced a `7B` response in these runs.
- That weakens the idea that `FB` is a stable downstream branch command. It now
  looks more like a sibling family observation that can appear on the `EC`
  branch, but not something the panel predictably wants answered.

Best current `EC` model:

- `A0 + EC` can open a selector-like family space.
- In that family space:
  - `7B` is the most stable downstream response so far
  - `FB` is real, but less stable and not yet actionable
  - without `A0`, `EC` falls back toward heartbeat-family behavior
- This is closer to a controlled state map than where we started, but it is
  still not a stable multi-turn "conversation" ladder.

### HE23: Does `A0` Also Context `E8` And `E9`?

The `EC` branch now strongly suggests that leading `A0` is part of branch
selection context rather than just a neutral primer. These tests ask whether the
same is true for `E8` and `E9`.

#### HE23a: `E8` Without Leading `A0`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-e8-no-a0.txt
```

#### HE23b: `E9` Without Leading `A0`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 E9 40 30 C3" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-e9-no-a0.txt
```

#### HE23c: `A0` Then `E8` Control Repeat

This is just the known-good comparison run so the no-`A0` tests can be read
against a fresh control captured in the same phase of work.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-e8-with-a0-control.txt
```

#### HE23d: `A0` Then `E9` Control Repeat

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 A0 00 80 7A" --frame "00 00 E9 40 30 C3" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-heartbeat-e9-with-a0-control.txt
```

What would count as a hit:

- `E8` or `E9` without `A0` still producing their known branches:
  - `07 80 7A 50 26 D1`
  - `07 80 7A 28 D3 5C`
- `E8` or `E9` without `A0` collapsing into heartbeat-family transients like
  `07 80 40 40 30 ED` or `07 80 C0 40 30 6D`
- a clear difference between no-`A0` and with-`A0` controls that would promote
  `A0` from "possible primer" to "general selector context"

### 2026-05-13 `A0` Context Test For `E8` / `E9`

Captures:

- `captures/rcp-heartbeat-e8-no-a0.txt`
- `captures/rcp-heartbeat-e9-no-a0.txt`
- `captures/rcp-heartbeat-e8-with-a0-control.txt`
- `captures/rcp-heartbeat-e9-with-a0-control.txt`

Observed outcomes:

| Setup | Group-1 result |
| --- | --- |
| `E8` without `A0` | later heartbeat provoked `07 80 40 40 30 ED` |
| `E9` without `A0` | later heartbeat provoked `07 80 40 40 30 ED` |
| `A0 -> E8` control | `07 80 7A 50 26 D1` |
| `A0 -> E9` control | heartbeat only in this repeat |

Interpretation:

- `E8` now clearly matches the `EC` pattern in one important way:
  without leading `A0`, it falls back into heartbeat-family behavior instead of
  opening its `7A` branch.
- `E9` without `A0` also fell back to the same heartbeat-family transient
  `07 80 40 40 30 ED`.
- So `A0` is now more plausibly a broader selector-context opener, not just an
  `EC`-specific quirk.
- However, `A0` is **not** sufficient to make every selector deterministic:
  the `A0 -> E9` control repeat produced only heartbeat in this run, even though
  earlier `A0 -> E9` work did produce `07 80 7A 28 D3 5C`.

Best current read:

- `A0` looks necessary or at least strongly supportive for entering the `E8`
  and `EC` family spaces.
- `E9` remains more timing- or state-sensitive than `E8`; `A0` helps define the
  context, but does not guarantee the `7A 28 D3` branch on every clean repeat.
- This strengthens the state-map model:
  - no `A0` -> selector values tend to collapse into heartbeat-family
    transients
  - with `A0` -> some selector values can open structured family spaces

### HE24: Can Something Other Than `A0` Open The `Ex` Selector Surface?

Goal:

- Determine whether `A0` is the main gate into the `E8` / `E9` / `EC` selector
  surface, or whether other known meaningful host frames can provide similar
  context.

Strategy:

- Keep the selector fixed.
- Change only the leading context frame.
- Use a small set of context candidates that have already shown real protocol
  significance elsewhere:
  - `00 00 A0 00 80 7A` as the known-good context
  - `00 00 90 00 80 4A` because `90` has previously behaved like setup/context
  - `00 00 AF 00 80 75` because `AF` has produced structured responses in other
    sequences
  - bare heartbeat `00 00 00 00 80 DA` as a minimal host-present control

What would count as a hit:

- a non-`A0` context causing `E8`, `E9`, or `EC` to open the same structured
  branch we normally associate with `A0`
- a non-`A0` context causing a different but stable structured family
- all non-`A0` contexts collapsing to heartbeat-family transients, which would
  make `A0` look much more like the main gate

#### HE24a: `90` As Context For `E8`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-90-e8.txt
```

#### HE24b: `90` As Context For `E9`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 40 30 C3" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-90-e9.txt
```

#### HE24c: `90` As Context For `EC`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-90-ec.txt
```

#### HE24d: `AF` As Context For `E8`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 00 80 75" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-af-e8.txt
```

#### HE24e: `AF` As Context For `E9`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 00 80 75" --frame "00 00 E9 40 30 C3" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-af-e9.txt
```

#### HE24f: `AF` As Context For `EC`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 00 80 75" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-af-ec.txt
```

#### HE24g: Bare Heartbeat Context For `E8`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-hb-e8.txt
```

#### HE24h: Bare Heartbeat Context For `E9`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 E9 40 30 C3" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-hb-e9.txt
```

#### HE24i: Bare Heartbeat Context For `EC`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 00 00 80 DA" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-hb-ec.txt
```

Recommended run order:

1. `HE24c` (`90 -> EC`)
2. `HE24f` (`AF -> EC`)
3. `HE24i` (heartbeat -> `EC`)
4. `HE24a` / `HE24b` (`90 -> E8/E9`)
5. `HE24d` / `HE24e` (`AF -> E8/E9`)
6. `HE24g` / `HE24h` (heartbeat -> `E8/E9`)

That order keeps the most stateful selector (`EC`) first, which should tell us
quickly whether `A0` is special or whether there is a broader family of context
openers.

### 2026-05-13 Non-`A0` Context Ladder Result

Captures:

- `captures/rcp-context-90-e8.txt`
- `captures/rcp-context-90-e9.txt`
- `captures/rcp-context-90-ec.txt`
- `captures/rcp-context-af-e8.txt`
- `captures/rcp-context-af-e9.txt`
- `captures/rcp-context-af-ec.txt`
- `captures/rcp-context-hb-e8.txt`
- `captures/rcp-context-hb-e9.txt`
- `captures/rcp-context-hb-ec.txt`

Observed group-1 outcomes:

| Context -> selector | Result |
| --- | --- |
| `90 -> E8` | `07 80 7A 50 26 D1` |
| `90 -> E9` | `07 80 7A 28 D3 5C` |
| `90 -> EC` | `07 80 7B 50 26 D0` |
| `AF -> E8` | `07 80 FA 50 26 51` |
| `AF -> E9` | `07 80 7A 28 D3 5C` |
| `AF -> EC` | `07 80 7B 50 26 D0` |
| `heartbeat -> E8` | `07 80 7A 50 26 D1` |
| `heartbeat -> E9` | `07 80 7A 28 D3 5C` |
| `heartbeat -> EC` | no `7B`; initial heartbeat provoked `07 80 40 40 30 ED`, then `EC` stayed heartbeat-only |

Interpretation:

- `A0` is **not** the only context opener for the `Ex` selector surface.
- `90` can stand in as a strong general context opener for all three tested
  selectors:
  - `E8` -> `7A 50 26`
  - `E9` -> `7A 28 D3`
  - `EC` -> `7B 50 26`
- `AF` can also open meaningful selector branches:
  - `AF -> E9` matched the known `E9` family
  - `AF -> EC` matched the known `EC` family
  - `AF -> E8` shifted to the alternate sibling `FA 50 26` family instead of
    `7A 50 26`
- Bare heartbeat is enough context for `E8` and `E9`, but **not** for `EC`.
  `EC` still appears stricter and more context-sensitive than the `E8/E9`
  branch pair.

Best current read:

- We are not looking at a single hard gate byte.
- We are looking at a broader **context-opener family**:
  - `A0`
  - `90`
  - `AF`
  - and, for at least `E8/E9`, even bare heartbeat context
- `E8` and `E9` seem easier to open than `EC`.
- `EC` may require a stronger or richer context class than plain heartbeat.
- `AF -> E8 -> FA 50 26` is especially interesting because it suggests the
  context frame can influence **which sibling family** the selector opens, not
  just whether it opens anything at all.

### HE25: Do `90` And `AF` Change Downstream Behavior Too?

Goal:

- Check whether `90` and `AF` only change the *first* family opened by a
  selector, or whether they also change what happens when we answer that family.

Method:

- Use a context opener (`90` or `AF`)
- Open a known selector branch (`E8`, `E9`, or `EC`)
- Immediately send the exact downstream family frame that branch produced
- Compare whether the follow-up behavior differs by opener

This keeps the selector and downstream family fixed while changing only the
context opener.

What would count as a hit:

- the same downstream exact echo behaving differently under `90` vs `AF`
- one opener causing a new second-stage family while the other falls flat
- the opener changing whether the branch drains to heartbeat or continues

#### HE25a: `90 -> E8 -> exact 7A 50 26`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "07 80 7A 50 26 D1" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-90-e8-exact-7a5026.txt
```

#### HE25b: `AF -> E8 -> exact FA 50 26`

This uses the family that `AF -> E8` actually opened.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 00 80 75" --frame "00 00 E8 40 30 C2" --frame "07 80 FA 50 26 51" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-af-e8-exact-fa5026.txt
```

#### HE25c: `90 -> E9 -> exact 7A 28 D3`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 40 30 C3" --frame "07 80 7A 28 D3 5C" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-90-e9-exact-7a28d3.txt
```

#### HE25d: `AF -> E9 -> exact 7A 28 D3`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 00 80 75" --frame "00 00 E9 40 30 C3" --frame "07 80 7A 28 D3 5C" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-af-e9-exact-7a28d3.txt
```

#### HE25e: `90 -> EC -> exact 7B 50 26`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 40 30 C6" --frame "07 80 7B 50 26 D0" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-90-ec-exact-7b5026.txt
```

#### HE25f: `AF -> EC -> exact 7B 50 26`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 00 80 75" --frame "00 00 EC 40 30 C6" --frame "07 80 7B 50 26 D0" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-af-ec-exact-7b5026.txt
```

Recommended order:

1. `HE25e` (`90 -> EC -> exact 7B`)
2. `HE25f` (`AF -> EC -> exact 7B`)
3. `HE25c` / `HE25d` (`90/AF -> E9 -> exact 7A 28 D3`)
4. `HE25a` / `HE25b` (`90 -> E8 -> exact 7A 50 26`, `AF -> E8 -> exact FA 50 26`)

That order keeps the most stateful selector first again, while still checking
whether opener choice changes the downstream echo behavior on the simpler `E8`
and `E9` branches.

### 2026-05-13 Opener-Dependent Downstream Behavior Result

Captures:

- `captures/rcp-context-90-e8-exact-7a5026.txt`
- `captures/rcp-context-af-e8-exact-fa5026.txt`
- `captures/rcp-context-90-e9-exact-7a28d3.txt`
- `captures/rcp-context-af-e9-exact-7a28d3.txt`
- `captures/rcp-context-90-ec-exact-7b5026.txt`
- `captures/rcp-context-af-ec-exact-7b5026.txt`

Observed group-1 outcomes:

| Setup | Selector result | Exact-echo follow-up result |
| --- | --- | --- |
| `90 -> E8 -> exact 7A 50 26` | `07 80 7A 58 26 D9` | heartbeat only after sending `07 80 7A 50 26 D1` |
| `AF -> E8 -> exact FA 50 26` | `07 80 7A 50 26 D1` | heartbeat only after sending `07 80 FA 50 26 51` |
| `90 -> E9 -> exact 7A 28 D3` | `07 80 7A 28 D3 5C` | heartbeat only |
| `AF -> E9 -> exact 7A 28 D3` | `07 80 7A 28 D3 5C` | heartbeat only |
| `90 -> EC -> exact 7B 50 26` | `07 80 FB 50 26 50` | heartbeat only after sending `07 80 7B 50 26 D0` |
| `AF -> EC -> exact 7B 50 26` | `07 80 7B 50 26 D0` | heartbeat only |

Interpretation:

- No opener produced a stable second-stage reply to the downstream exact echo.
- So, for now, opener choice seems to affect the **branch entry** much more than
  the **reply-to-the-branch** behavior.
- But opener choice definitely matters at the selector result level:
  - `90 -> E8` shifted the response from the familiar `7A 50 26` family to the
    sibling `7A 58 26 D9`
  - `90 -> EC` shifted the response from `7B 50 26 D0` to the sibling
    `FB 50 26 50`
  - `AF -> EC` preserved the `7B 50 26 D0` branch
- `E9` looks more stable across openers than `E8` and `EC`:
  both `90` and `AF` still opened `07 80 7A 28 D3 5C`

Most important takeaway:

- opener bytes are not just yes/no "permissions" for the selector surface
- they can change **which sibling family** a selector lands in
- but once that family appears, echoing the expected downstream frame still did
  not create a reproducible next turn

Best current model:

- context opener selects or biases a family page
- selector byte chooses within that opened space
- downstream family frames still look more like readable responses than clear
  host prompts

Best next move:

- when a selector result shifts under a different opener, answer the
  **actually observed** downstream frame instead of the previously known sibling
  frame
- the strongest immediate candidates are:
  - `90 -> E8 -> exact 07 80 7A 58 26 D9`
  - `90 -> EC -> exact 07 80 FB 50 26 50`

### HE26: Answer The Actually Observed `90`-Shifted Family

Goal:

- Now that `90` is clearly able to shift the family/page opened by `E8` and
  `EC`, answer the *observed* downstream frame rather than the older sibling
  family we expected from earlier runs.

Hypothesis:

- If opener choice is selecting a page or family, the correct next probe is the
  exact frame from that page, not a nearby family member learned under another
  opener.

What would count as a hit:

- any fresh non-heartbeat family after the exact observed-frame echo
- a reproducible second-stage branch that only appears when the echoed frame
  matches the opener-selected family
- visible evidence that page-matched echoing behaves differently from
  sibling-mismatched echoing

#### HE26a: `90 -> E8 -> exact 7A 58 26`

Under `90`, `E8` produced `07 80 7A 58 26 D9`, so answer that exact frame.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "07 80 7A 58 26 D9" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-90-e8-exact-7a5826.txt
```

#### HE26b: `90 -> EC -> exact FB 50 26`

Under `90`, `EC` produced `07 80 FB 50 26 50`, so answer that exact frame.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 40 30 C6" --frame "07 80 FB 50 26 50" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-context-90-ec-exact-fb5026.txt
```

Recommended order:

1. `HE26b` (`90 -> EC -> exact FB 50 26`)
2. `HE26a` (`90 -> E8 -> exact 7A 58 26`)

`EC` still looks like the more stateful branch, so it has the better chance of
showing whether page-matched echoing matters.

### 2026-05-13 Page-Matched Echo Result

Captures:

- `captures/rcp-context-90-e8-exact-7a5826.txt`
- `captures/rcp-context-90-ec-exact-fb5026.txt`

Observed outcomes:

| Setup | Selector result | Page-matched exact echo result |
| --- | --- | --- |
| `90 -> E8 -> exact 7A 58 26` | actual run produced `07 80 7A 50 26 D1` | sending `07 80 7A 58 26 D9` did not open a new family; the queued `7A 50 26 D1` drained, then heartbeat |
| `90 -> EC -> exact FB 50 26` | `07 80 FB 50 26 50` | sending `07 80 FB 50 26 50` did not open a new family; then heartbeat |

Interpretation:

- Page-matched echoing still did **not** create a stable second-stage exchange.
- For `EC`, this is fairly clean: the observed `FB 50 26` family was echoed
  back exactly and the branch still collapsed to heartbeat.
- For `E8`, the run is interesting in a different way:
  - the earlier `90 -> E8` test had produced sibling `07 80 7A 58 26 D9`
  - this repeat instead produced `07 80 7A 50 26 D1`
  - sending the page-matched `7A 58 26 D9` frame did not redirect or extend the
    branch; the observed `7A 50 26 D1` simply drained out
- So the strongest conclusion is still:
  opener choice clearly biases the family/page the selector lands in, but the
  downstream family frames still do not behave like obvious prompts the host is
  expected to answer.

Best current read:

- We are getting better at steering the RCP into specific family spaces.
- We are **not yet** seeing evidence that matching the page family in our reply
  is enough to advance to a deterministic next turn.
- That keeps the focus on:
  - context opener
  - selector
  - family chosen
  rather than on any confirmed reply ladder after the family appears.

### HE27: Host State/Value Probing Ladder

Goal:

- Test whether the hidden rule lives in the opener `state/value` bytes, the
  selector `state/value` bytes, or both.
- Keep the structure disciplined:
  1. vary opener `state/value` with selector fixed
  2. vary selector `state/value` with opener fixed

This is the first intentionally broad state/value ladder aimed at the
selector-surface model, rather than raw command discovery.

What would count as a hit:

- a state/value change shifting the opened family while command bytes stay fixed
- one state/value pair acting like a stronger opener than another
- a selector state/value pair opening a family that the baseline selector does
  not
- repeated collapse to the same family regardless of state/value, which would
  tell us that the command byte matters more than those fields on that branch

#### Tier 1: Opener State/Value Sweep With Fixed Selectors

Use the same selector each time and vary only the opener payload.

State/value variants:

- opener baseline: `00 80`
- opener alt 1: `20 D0`
- opener alt 2: `40 30`
- opener alt 3: `60 30`

Checksums:

- `90 00 80` -> `00 00 90 00 80 4A`
- `90 20 D0` -> `00 00 90 20 D0 3A`
- `90 40 30` -> `00 00 90 40 30 BA`
- `90 60 30` -> `00 00 90 60 30 9A`
- `AF 00 80` -> `00 00 AF 00 80 75`
- `AF 20 D0` -> `00 00 AF 20 D0 05`
- `AF 40 30` -> `00 00 AF 40 30 85`
- `AF 60 30` -> `00 00 AF 60 30 A5`

##### HE27a: `90` payload sweep into `E8`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-90-e8-0080.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 20 D0 3A" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-90-e8-20d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 40 30 BA" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-90-e8-4030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 60 30 9A" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-90-e8-6030.txt
```

##### HE27b: `90` payload sweep into `EC`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-90-ec-0080.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 20 D0 3A" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-90-ec-20d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 40 30 BA" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-90-ec-4030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 60 30 9A" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-90-ec-6030.txt
```

##### HE27c: `AF` payload sweep into `E8`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 00 80 75" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-af-e8-0080.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 20 D0 05" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-af-e8-20d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 40 30 85" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-af-e8-4030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 60 30 A5" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-af-e8-6030.txt
```

##### HE27d: `AF` payload sweep into `EC`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 00 80 75" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-af-ec-0080.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 20 D0 05" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-af-ec-20d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 40 30 85" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-af-ec-4030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 AF 60 30 A5" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-af-ec-6030.txt
```

#### Tier 2: Selector State/Value Sweep With Fixed Openers

Now hold the opener steady and vary only selector `state/value`.

Selector checksums:

- `E8 00 80` -> `00 00 E8 00 80 32`
- `E8 20 D0` -> `00 00 E8 20 D0 42`
- `E8 40 30` -> `00 00 E8 40 30 C2`
- `E8 60 30` -> `00 00 E8 60 30 E2`
- `E9 00 80` -> `00 00 E9 00 80 33`
- `E9 20 D0` -> `00 00 E9 20 D0 43`
- `E9 40 30` -> `00 00 E9 40 30 C3`
- `E9 60 30` -> `00 00 E9 60 30 E3`
- `EC 00 80` -> `00 00 EC 00 80 36`
- `EC 20 D0` -> `00 00 EC 20 D0 46`
- `EC 40 30` -> `00 00 EC 40 30 C6`
- `EC 60 30` -> `00 00 EC 60 30 E6`

##### HE27e: `90` opener with selector payload sweep on `E8`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 00 80 32" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-e8-0080.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 20 D0 42" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-e8-20d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-e8-4030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 60 30 E2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-e8-6030.txt
```

##### HE27f: `90` opener with selector payload sweep on `E9`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 00 80 33" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-e9-0080.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 20 D0 43" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-e9-20d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 40 30 C3" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-e9-4030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 60 30 E3" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-e9-6030.txt
```

##### HE27g: `90` opener with selector payload sweep on `EC`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 00 80 36" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-ec-0080.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 20 D0 46" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-ec-20d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 40 30 C6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-ec-4030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 60 30 E6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-statevalue-sel-ec-6030.txt
```

Recommended order:

1. `HE27b` (`90` payload sweep into `EC`)
2. `HE27d` (`AF` payload sweep into `EC`)
3. `HE27g` (`90` opener with selector payload sweep on `EC`)
4. `HE27a` (`90` payload sweep into `E8`)
5. `HE27c` (`AF` payload sweep into `E8`)
6. `HE27e` / `HE27f` (`90` opener with selector payload sweep on `E8` / `E9`)

That keeps the most stateful branch (`EC`) in front while still giving us a
comprehensive way to separate opener payload effects from selector payload
effects.

### 2026-05-13 State/Value Probing Ladder Result

This ladder gave the clearest evidence so far that the `state/value` bytes are
meaningful and can steer family selection.

#### Tier 1: Opener Payload Sweep

##### `90` opener into `E8`

| Opener payload | Result |
| --- | --- |
| `90 00 80` | `07 80 7A 50 26 D1` |
| `90 20 D0` | `07 80 7A 50 26 D1` |
| `90 40 30` | `07 80 7A 50 26 D1` |
| `90 60 30` | `07 80 FA 50 26 51` |

Read:

- `90` opener payload is meaningful on `E8`.
- `60 30` flips the branch from `7A 50 26` into sibling `FA 50 26`.

##### `90` opener into `EC`

| Opener payload | Result |
| --- | --- |
| `90 00 80` | `07 80 7B 58 26 D8` |
| `90 20 D0` | `07 80 FB 50 26 50` |
| `90 40 30` | `07 80 7B 50 26 D0` |
| `90 60 30` | `07 80 7B 50 26 D0` |

Read:

- `90` opener payload clearly changes which sibling family/page `EC` opens.
- `00 80` gives a shifted `7B 58 26` variant.
- `20 D0` flips to `FB 50 26`.
- `40 30` and `60 30` both land on `7B 50 26`.

##### `AF` opener into `E8`

| Opener payload | Result |
| --- | --- |
| `AF 00 80` | `07 80 7A 50 26 D1` |
| `AF 20 D0` | `07 80 C0 40 30 6D` |
| `AF 40 30` | `07 80 7A 50 26 D1` |
| `AF 60 30` | `07 80 40 40 30 ED` |

Read:

- `AF` opener payload can either preserve the `E8 -> 7A` branch or collapse it
  back into heartbeat-family transients.
- `20 D0` and `60 30` appear hostile or misdirecting on this branch.

##### `AF` opener into `EC`

| Opener payload | Result |
| --- | --- |
| `AF 00 80` | `07 80 FB 50 26 50` |
| `AF 20 D0` | later heartbeat-family `07 80 40 40 30 ED` |
| `AF 40 30` | later heartbeat-family `07 80 40 40 30 ED` |
| `AF 60 30` | later heartbeat-family `07 80 40 40 30 ED` |

Read:

- `AF` opener payload is even more selective on `EC`.
- Only the baseline `00 80` payload opened a structured family.
- `20 D0`, `40 30`, and `60 30` all collapsed into heartbeat-family behavior.

#### Tier 2: Selector Payload Sweep Under Fixed `90 00 80`

##### `E8` selector payload

| Selector payload | Result |
| --- | --- |
| `E8 00 80` | `07 80 7A 40 30 D7` |
| `E8 20 D0` | `07 80 7A 48 3A D5` |
| `E8 40 30` | `07 80 7A 50 26 D1` |
| `E8 60 30` | `07 80 7A 58 26 D9` |

##### `E9` selector payload

| Selector payload | Result |
| --- | --- |
| `E9 00 80` | `07 80 7A 20 D8 5F` |
| `E9 20 D0` | `07 80 7A 24 FD 7E` |
| `E9 40 30` | `07 80 7A 28 D3 5C` |
| `E9 60 30` | `07 80 7A 2C D3 58` |

##### `EC` selector payload

| Selector payload | Result |
| --- | --- |
| `EC 00 80` | `07 80 FB 40 30 56` |
| `EC 20 D0` | `07 80 E4 40 30 49` |
| `EC 40 30` | `07 80 FB 50 26 50` |
| `EC 60 30` | `07 80 7B 58 26 D8` |

Interpretation:

- Selector `state/value` bytes are strongly meaningful.
- On `E8` and `E9`, the response family byte stayed at `7A`, while the
  following bytes changed systematically with selector payload.
- On `EC`, selector payload changes were even stronger:
  the family itself shifted among `FB`, `E4`, and `7B`.
- This is the best current evidence that:
  - command byte picks a broad selector region
  - `state/value` bytes pick a page, subtype, or data class within that region

Best current model after HE27:

- opener command matters
- opener payload matters
- selector command matters
- selector payload matters
- the protocol surface is more parameterized than we first thought
- but we still do not have a stable proof that the downstream family frames are
  host prompts rather than readable response blocks

### HE28: Off-Grid Pair Test

Goal:

- Test whether the two payload bytes behave more like an accepted **pair code**
  than two semantically independent fields.

Idea:

- We already know these combinations are meaningful on several branches:
  - `00 80`
  - `20 D0`
  - `40 30`
  - `60 30`
- Now try mixed combinations that were **not** part of the known-good set:
  - `00 30`
  - `20 30`
  - `40 D0`
  - `60 80`

What would count as a hit:

- mixed pairs behaving just like nearby known-good pairs, which would support
  more independent byte meanings
- mixed pairs mostly collapsing to heartbeat/transients or producing odd
  outliers, which would support the "2-byte code" model
- one branch accepting mixed pairs while another rejects them, which would mean
  pairing rules are selector-specific

Checksums:

- `E8 00 30` -> `00 00 E8 00 30 82`
- `E8 20 30` -> `00 00 E8 20 30 A2`
- `E8 40 D0` -> `00 00 E8 40 D0 22`
- `E8 60 80` -> `00 00 E8 60 80 52`
- `E9 00 30` -> `00 00 E9 00 30 83`
- `E9 20 30` -> `00 00 E9 20 30 A3`
- `E9 40 D0` -> `00 00 E9 40 D0 23`
- `E9 60 80` -> `00 00 E9 60 80 53`
- `EC 00 30` -> `00 00 EC 00 30 86`
- `EC 20 30` -> `00 00 EC 20 30 A6`
- `EC 40 D0` -> `00 00 EC 40 D0 26`
- `EC 60 80` -> `00 00 EC 60 80 56`

Keep the opener fixed to the strongest baseline opener for each local map:

- use `90 00 80` before `E8`, `E9`, and `EC`

#### HE28a: Off-grid selector pairs on `E8`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 00 30 82" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-e8-0030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 20 30 A2" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-e8-2030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 D0 22" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-e8-40d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E8 60 80 52" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-e8-6080.txt
```

#### HE28b: Off-grid selector pairs on `E9`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 00 30 83" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-e9-0030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 20 30 A3" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-e9-2030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 40 D0 23" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-e9-40d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 E9 60 80 53" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-e9-6080.txt
```

#### HE28c: Off-grid selector pairs on `EC`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 00 30 86" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-ec-0030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 20 30 A6" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-ec-2030.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 40 D0 26" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-ec-40d0.txt
python scripts/serial_sequence_probe.py --port COM5 --prompt --frame "00 00 90 00 80 4A" --frame "00 00 EC 60 80 56" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-pairtest-ec-6080.txt
```

Recommended order:

1. `HE28c` (`EC` off-grid pairs)
2. `HE28a` (`E8` off-grid pairs)
3. `HE28b` (`E9` off-grid pairs)

That keeps the most expressive selector first, where the pair-code hypothesis is
most likely to show itself clearly.

### 2026-05-13 Off-Grid Pair Result

This ladder weakens the strict "only a few accepted 2-byte pair codes" model.

#### `E8` off-grid pairs under `90 00 80`

| Selector payload | Result |
| --- | --- |
| `E8 00 30` | `07 80 7A 40 26 C1` |
| `E8 20 30` | `07 80 FA 48 26 49` |
| `E8 40 D0` | `07 80 7A 50 3A CD` |
| `E8 60 80` | `07 80 7A 58 30 CF` |

#### `E9` off-grid pairs under `90 00 80`

| Selector payload | Result |
| --- | --- |
| `E9 00 30` | `07 80 7A 20 D3 54` |
| `E9 20 30` | `07 80 7A 24 D3 50` |
| `E9 40 D0` | `07 80 7A 28 DD 52` |
| `E9 60 80` | `07 80 7A 2C D8 53` |

#### `EC` off-grid pairs under `90 00 80`

| Selector payload | Result |
| --- | --- |
| `EC 00 30` | `07 80 7B 40 26 C0` |
| `EC 20 30` | `07 80 7B 4C 26 CC` |
| `EC 40 D0` | `07 80 FB 50 3A 4C` |
| `EC 60 80` | `07 80 7B 58 30 CE` |

Interpretation:

- The RCP did **not** reject the off-grid mixed combinations.
- Instead, it returned structured, checksum-valid sibling families in a highly
  systematic way.
- That makes the strongest current model:
  - these are still two separate bytes on the wire
  - and their meanings appear at least partly compositional, not just a small
    fixed whitelist of accepted pair codes
- In other words, the "strict 2-byte enum only" hypothesis is weaker now.
- A more likely model is:
  - selector command chooses a region
  - first payload byte strongly influences one axis/page
  - second payload byte also influences the returned subtype/data bytes
  - some selectors, especially `EC`, can still flip whole sibling families (`7B`
    vs `FB`) depending on the combination

Best current takeaway:

- the `state/value` bytes are meaningful
- they are not behaving like arbitrary filler
- but they also are not behaving like only four hardcoded legal pair tokens
- they look more like a small 2-byte parameter space with selector-specific
  mapping rules

### HE29: Valid-Connection / Camera-Info Stream Fan-Out

Goal:

- Fan back out from one-shot reads and selector branches into **sustained
  host-present plus camera-info-like traffic**.
- Try to answer a more practical question:
  can we send a stream that the panel treats as a real CCU/camera connection,
  even if only enough to change `CONNECT NOT ACT`, light a lamp, or wake a
  readout?

Working idea:

- Plain host heartbeat can hold the panel out of `CONNECT NOT ACT` while it is
  flowing, but it does not by itself produce a stable active session.
- The panel may want **both**:
  - ongoing host presence/cadence
  - and a rotating set of camera/capability/status pages
- We now have several families that are plausible candidates for those pages:
  - discovery surface: `A0`, `B0`, `B5`
  - selector surface: `90 -> E8/E9/EC`
  - host-shaped family mirrors: `7A`, `7B`, `FB`
  - heartbeat-adjacent base families: `40`, `C0`, `50`

What would count as a hit:

- the panel stays out of `CONNECT NOT ACT` **and**
  - a light changes
  - a numeric readout changes
  - the LCD changes to something new
  - or one-shot branches become reusable while the stream is running
- repeated non-heartbeat RX beyond the usual single group-1 burst
- any sign that the panel begins acting "session alive" rather than merely
  "host present"

Note:

- `serial_sequence_probe.py` now supports `--prompt-screen`, so the final
  screen/light observation can live in the same capture log.

Checksums already known:

- heartbeat: `00 00 00 00 80 DA`
- `A0`: `00 00 A0 00 80 7A`
- `B0`: `00 00 B0 00 80 6A`
- `B5`: `00 00 B5 00 80 6F`
- `90`: `00 00 90 00 80 4A`
- `AF`: `00 00 AF 00 80 75`
- `E8 40 30`: `00 00 E8 40 30 C2`
- `E9 40 30`: `00 00 E9 40 30 C3`
- `EC 40 30`: `00 00 EC 40 30 C6`
- host `7A 50 26`: `00 00 7A 50 26 56`
- host `7A 28 D3`: `00 00 7A 28 D3 DB`
- host `7B 50 26`: `00 00 7B 50 26 57`
- host `FB 50 26`: `00 00 FB 50 26 D7`
- host `FA 50 26`: `00 00 FA 50 26 D6`
- host `40 40 30`: `00 00 40 40 30 6A`
- host `C0 40 30`: `00 00 C0 40 30 EA`
- host `50 40 30`: `00 00 50 40 30 7A`

#### HE29a: Discovery carousel under maintained heartbeat

Hypothesis:

- The panel may want periodic readable capability/status pages, not just a lone
  one-shot query at boot.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 A0 00 80 7A" --frame "00 00 00 00 80 DA" --frame "00 00 B0 00 80 6A" --frame "00 00 00 00 80 DA" --frame "00 00 B5 00 80 6F" --repeat 10 --frame-interval 0.12 --read-after-frame 0.06 --repeat-interval 0.15 --read-after-group 0.20 --log captures/rcp-session-fanout-discovery-carousel.txt
```

Watch for:

- later groups reopening `68`, `6C`, or `6D` families
- the panel leaving `CONNECT NOT ACT` while the carousel runs
- any lamp or readout change

#### HE29b: Selector-page carousel under strong `90` context

Hypothesis:

- The selector surface may actually be a rotating camera-info page set, and the
  panel may want repeated page selection rather than isolated one-shot use.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 00 00 80 DA" --frame "00 00 90 00 80 4A" --frame "00 00 E9 40 30 C3" --frame "00 00 00 00 80 DA" --frame "00 00 90 00 80 4A" --frame "00 00 EC 40 30 C6" --repeat 8 --frame-interval 0.12 --read-after-frame 0.06 --repeat-interval 0.15 --read-after-group 0.20 --log captures/rcp-session-fanout-selector-carousel.txt
```

Watch for:

- later groups still opening `7A`, `7B`, `FB`, or `FA` families
- the selector space becoming reusable instead of draining to heartbeat
- any visible wake-up on the panel

#### HE29c: Direct host-shaped family feed

Hypothesis:

- The panel may want host-origin camera-state frames that look more like the
  downstream family blocks than like discovery queries.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 7A 50 26 56" --frame "00 00 00 00 80 DA" --frame "00 00 7A 28 D3 DB" --frame "00 00 00 00 80 DA" --frame "00 00 7B 50 26 57" --frame "00 00 00 00 80 DA" --frame "00 00 FB 50 26 D7" --repeat 10 --frame-interval 0.12 --read-after-frame 0.06 --repeat-interval 0.15 --read-after-group 0.20 --log captures/rcp-session-fanout-direct-family-feed.txt
```

Watch for:

- any visible panel reaction with no preceding opener/selector steps
- whether any of these host-shaped family blocks seem to act like camera values
- whether the panel begins sending a different recurring status frame

#### HE29d: Hybrid select-then-feed stream

Hypothesis:

- The panel may want a selector/page choice immediately followed by page data,
  not just a readback-like query.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --frame "00 00 00 00 80 DA" --frame "00 00 90 00 80 4A" --frame "00 00 E9 40 30 C3" --frame "00 00 7A 28 D3 DB" --frame "00 00 00 00 80 DA" --frame "00 00 90 00 80 4A" --frame "00 00 EC 40 30 C6" --frame "00 00 7B 50 26 57" --repeat 8 --frame-interval 0.12 --read-after-frame 0.06 --repeat-interval 0.15 --read-after-group 0.20 --log captures/rcp-session-fanout-hybrid-90.txt
```

Watch for:

- whether later groups stop collapsing after the first selector-family burst
- whether page-matched host data makes the selector space feel more "live"
- any call lamp, tally, or numeric display change

#### HE29e: `AF`-biased sibling-family stream

Hypothesis:

- `AF` changes which sibling family opens; maybe that opener is closer to a real
  CCU "camera data" context than `90` for some pages.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 AF 00 80 75" --frame "00 00 E8 40 30 C2" --frame "00 00 FA 50 26 D6" --frame "00 00 00 00 80 DA" --frame "00 00 AF 00 80 75" --frame "00 00 EC 40 30 C6" --frame "00 00 7B 50 26 57" --repeat 8 --frame-interval 0.12 --read-after-frame 0.06 --repeat-interval 0.15 --read-after-group 0.20 --log captures/rcp-session-fanout-hybrid-af.txt
```

Watch for:

- whether `AF` keeps `E8` in the `FA` sibling family more reliably
- whether `AF` changes visible panel behavior even if serial RX still looks
  one-shot

#### HE29f: Heartbeat-family base-status feed

Hypothesis:

- The `40` / `C0` / `50` families may be a base camera-status layer rather than
  mere "wrong" answers, and the panel may want them as a background stream.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 40 40 30 6A" --frame "00 00 00 00 80 DA" --frame "00 00 C0 40 30 EA" --frame "00 00 00 00 80 DA" --frame "00 00 50 40 30 7A" --repeat 10 --frame-interval 0.12 --read-after-frame 0.06 --repeat-interval 0.15 --read-after-group 0.20 --log captures/rcp-session-fanout-base-status.txt
```

Watch for:

- whether this suppresses `CONNECT NOT ACT` more convincingly than plain
  heartbeat alone
- whether any lamp or readout reacts even if selector-space branches do not
- whether the panel responds with a recurring non-heartbeat family rather than
  isolated group-1 bursts

Recommended order:

1. `HE29a` discovery carousel
2. `HE29d` hybrid `90` select-then-feed
3. `HE29c` direct host-shaped family feed
4. `HE29f` heartbeat-family base-status feed
5. `HE29b` selector carousel
6. `HE29e` `AF`-biased sibling-family stream

Reasoning:

- `HE29a` and `HE29d` are the two most plausible "real CCU behavior" guesses:
  periodic readable pages, or page-select plus page-data.
- `HE29c` asks whether the panel can be fed camera-state blocks directly.
- `HE29f` is the slightly weirder but still useful branch: maybe the
  heartbeat-adjacent `40/C0/50` families are actually the base layer the panel
  wants before richer pages make sense.

### 2026-05-13 HE29 Fan-Out Results

Front-panel result first:

- No tested HE29 stream changed the visible panel state in a useful way.
- No run produced a new LCD state beyond the previously known inactive
  behavior, and no lamp/readout change was observed.

That said, the wire result was not completely dead.

#### HE29a: Discovery carousel under maintained heartbeat

Log:

- `captures/rcp-session-fanout-discovery-carousel.txt`

Result:

- The sustained `heartbeat -> A0 -> heartbeat -> B0 -> heartbeat -> B5`
  carousel produced only heartbeat-compatible RX for every group.
- None of the older one-shot readable families (`68`, `6C`, `6D`) reopened
  under this maintained cycle.

Read:

- Simple repeated discovery-page polling does **not** look like the missing
  live session traffic.
- It also did not make the one-shot readable surface reusable.

#### HE29b: Selector carousel under strong `90` context

Log:

- `captures/rcp-session-fanout-selector-carousel.txt`

Result:

- Group 1, `90 -> E8`, produced:
  - `07 80 FA 50 26 51`
- `90 -> E9` and `90 -> EC` in the same cycle then stayed heartbeat-only.
- Groups 2-8 were heartbeat-only throughout.

Read:

- Repeating selector-page choice in a rotating carousel still does not sustain a
  richer session.
- The stream appears to spend a single selector-family opening and then collapse
  back into ordinary heartbeat behavior.

#### HE29c: Direct host-shaped family feed

Log:

- `captures/rcp-session-fanout-direct-family-feed.txt`

Result:

- Group 1, host-shaped `00 00 7A 50 26 56` produced a new RCP family:
  - `07 80 2F 95 C9 AE`
- After that:
  - host-shaped `7A 28 D3` produced no structured reply
  - host-shaped `7B 50 26` stayed heartbeat-only
  - host-shaped `FB 50 26` stayed heartbeat-only
- Later groups did not reproduce the `2F 95 C9` family.

Read:

- This is the first HE29 branch where a pure host-shaped family feed produced a
  genuinely new structured response.
- It still behaved one-shot and did **not** wake the panel visually.
- So direct family feed is not enough to create a live connection, but it does
  look protocol-meaningful.

#### HE29d: Hybrid `90` select-then-feed

Log:

- `captures/rcp-session-fanout-hybrid-90.txt`

Result:

- Group 1:
  - `90 -> E8` produced `07 80 7A 50 26 D1`
  - immediate host-shaped `7A 50 26` feed produced no next stage
- The later `E9` and `EC` portions of the same group stayed heartbeat-only.
- Groups 2-8 were heartbeat-only throughout.

Read:

- Page-select plus matching host feed still does not turn the selector space
  into a reusable or "live" stream.
- This weakens the idea that the missing session is simply "select page, then
  send that page's payload."

#### HE29e: `AF`-biased sibling-family stream

Log:

- `captures/rcp-session-fanout-hybrid-af.txt`

Result:

- Group 1:
  - `AF -> E8` produced `07 80 7A 50 26 D1`
  - host-shaped `FA 50 26` feed produced no next stage
- The later `AF -> EC` portion stayed heartbeat-only.
- Groups 2-8 were heartbeat-only throughout.

Read:

- `AF` did not behave like a more realistic camera-data opener in this sustained
  mixed-stream form.
- Even the sibling-family-biased branch still collapsed immediately after the
  first selector-family burst.

#### HE29f: Heartbeat-family base-status feed

Log:

- `captures/rcp-session-fanout-base-status.txt`

Result:

- Group 1, host-shaped `00 00 40 40 30 6A` produced a new RCP family:
  - `07 80 D0 50 26 7B`
- The following host-shaped `C0 40 30` and `50 40 30` frames did not open a new
  visible stage.
- Groups 2-10 were heartbeat-only throughout.

Read:

- This is the second HE29 branch that produced a genuinely new structured
  family.
- So the `40` family is still worth taking seriously as more than a pure
  "wrong" response bucket.
- But, like the `2F 95 C9` branch above, it is still one-shot and did not
  visibly activate the panel.

### HE29 Overall Read

What failed:

- No tested sustained stream woke the panel visually.
- No tested stream cleared the broader "not really connected" condition.
- No tested stream made the selector surface reusable in a stable way.

What still matters:

- Two new structured families appeared when we treated the known response
  families as host-origin traffic instead of just things to mirror:
  - `00 00 7A 50 26 56` -> `07 80 2F 95 C9 AE`
  - `00 00 40 40 30 6A` -> `07 80 D0 50 26 7B`
- That strengthens the idea that some of these families are not just passive
  readback blocks; they can still act as meaningful stimuli from the host side.

Best current read after HE29:

- The missing active session still does **not** look like:
  - plain heartbeat
  - repeated discovery reads
  - repeated selector-page reads
  - naive page-select plus matching host feed
- The next smarter branch is probably to stay narrow around the two new
  host-stimulated response families (`2F 95 C9` and `D0 50 26`) and test
  whether they represent a deeper camera/base-status layer rather than just more
  one-shot siblings.

### HE30: Targeted Follow-Up On `2F 95 C9` And `D0 50 26`

Goal:

- Treat the two new HE29 host-stimulated families as real protocol leads:
  - `07 80 2F 95 C9 AE`
  - `07 80 D0 50 26 7B`
- Work out whether they are:
  - simple one-shot siblings
  - a deeper base-status layer
  - or part of a real next-turn exchange that we have not answered correctly yet

Strategy:

1. Reproduce each family cleanly and answer **that exact observed family**
2. Try the host-shaped mirror of the observed family
3. Compare the new `2F 95 C9` branch to the older `2F 95 09` branch
4. Probe nearby host-side trigger siblings to see whether the branch is narrow
   or family-wide
5. Test whether the new family itself works cold after boot, or only as a
   response branch

Checksums:

- host `2F 95 C9` -> `00 00 2F 95 C9 29`
- host `D0 50 26` -> `00 00 D0 50 26 FC`
- older host `2F 95 09` -> `00 00 2F 95 09 E9`
- host `7A 58 26` -> `00 00 7A 58 26 5E`
- host `7A 50 3A` -> `00 00 7A 50 3A 4A`
- host `40 60 30` -> `00 00 40 60 30 4A`

#### HE30a: Exact and host-shaped answers to `07 80 2F 95 C9 AE`

Hypothesis:

- If `2F 95 C9` is a real second-stage family, answering the exact observed
  frame or its host-shaped mirror may produce a cleaner third step than the old
  `2F 95 09` branch did.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 7A 50 26 56" --frame "07 80 2F 95 C9 AE" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-exact-2f95c9.txt

python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 7A 50 26 56" --frame "00 00 2F 95 C9 29" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-host-2f95c9.txt
```

#### HE30b: Compare new `2F 95 C9` with old `2F 95 09`

Hypothesis:

- The new `2F` branch may be a close sibling of the older `07 C0 2F 95 09 2E`
  branch from `EC -> 7B`.
- If so, sending the older host-shaped `2F 95 09` after provoking the new
  `2F 95 C9` branch may reveal whether the last data byte is page/subtype rather
  than a completely separate family.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 7A 50 26 56" --frame "00 00 2F 95 09 E9" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-host-2f9509-after-2f95c9.txt
```

#### HE30c: Exact and host-shaped answers to `07 80 D0 50 26 7B`

Hypothesis:

- If `D0 50 26` is a deeper base-status family, it may respond differently to
  exact and host-shaped answers than the selector-space `7A/7B/FB` families do.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 40 40 30 6A" --frame "07 80 D0 50 26 7B" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-exact-d05026.txt

python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 40 40 30 6A" --frame "00 00 D0 50 26 FC" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-host-d05026.txt
```

#### HE30d: Trigger-sibling probes around the `2F 95 C9` branch

Hypothesis:

- The new `2F 95 C9` branch may not belong only to host `7A 50 26`.
- Nearby host-side `7A` siblings might land on related `2F` pages if this is a
  small camera-data family rather than a single magic value.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 7A 58 26 5E" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-trigger-7a5826.txt

python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 7A 50 3A 4A" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-trigger-7a503a.txt
```

#### HE30e: Trigger-sibling probes around the `D0 50 26` branch

Hypothesis:

- If `D0 50 26` is tied to a broader base-status surface, nearby host-side
  `40/50/C0` style pages may land on related families.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 50 40 30 7A" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-trigger-504030.txt

python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 00 00 80 DA" --frame "00 00 40 60 30 4A" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-trigger-406030.txt
```

#### HE30f: Cold-start direct tests of the new host-shaped families

Hypothesis:

- If these families are really camera/base-status feed pages, they may work
  directly after boot without needing the earlier provoking host frame.
- If they do **not**, they are more likely to be second-stage response branches.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 2F 95 C9 29" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-cold-2f95c9.txt

python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 D0 50 26 FC" --frame "00 00 00 00 80 DA" --repeat 2 --frame-interval 0.20 --read-after-frame 0.30 --read-after-group 0.8 --log captures/rcp-he30-cold-d05026.txt
```

Recommended order:

1. `HE30a` exact/host `2F 95 C9`
2. `HE30c` exact/host `D0 50 26`
3. `HE30f` cold-start direct tests
4. `HE30b` old-vs-new `2F` comparison
5. `HE30d` and `HE30e` trigger-sibling probes

Reasoning:

- First check whether the new families themselves can be answered meaningfully.
- Then check whether they are direct-feed candidates or only response branches.
- Only after that spend time mapping nearby siblings.

### 2026-05-13 HE30 Results

This ladder did not produce a stable multi-turn exchange, but it did reveal a
stronger family structure around both new HE29 branches.

#### HE30a: Answering `07 80 2F 95 C9 AE`

Logs:

- `captures/rcp-he30-exact-2f95c9.txt`
- `captures/rcp-he30-host-2f95c9.txt`

Result:

- Group 1, host `00 00 7A 50 26 56` again produced:
  - `07 80 2F 95 C9 AE`
- Answering that with either:
  - exact `07 80 2F 95 C9 AE`
  - or host `00 00 2F 95 C9 29`
  produced no new stage.
- Group 2 was heartbeat-only in both runs.

Read:

- `2F 95 C9` is real and reproducible as a one-shot branch under the `7A 50 26`
  host feed.
- But it still does **not** behave like an immediate prompt that the host can
  simply echo back.

#### HE30b: Comparing new `2F 95 C9` to old `2F 95 09`

Log:

- `captures/rcp-he30-host-2f9509-after-2f95c9.txt`

Result:

- This run did **not** reproduce the new `2F 95 C9` branch first.
- Instead, the initial host heartbeat itself provoked:
  - `07 80 C0 40 30 6D`
- After that, host `00 00 7A 50 26 56` and host `00 00 2F 95 09 E9` stayed
  heartbeat-compatible only.

Read:

- This run did not establish a useful direct relationship between `2F 95 C9`
  and the older `2F 95 09` branch.
- It does reinforce that these branches are timing/context-sensitive and can be
  pre-empted by fallback-family behavior.

#### HE30c: Answering `07 80 D0 50 26 7B`

Logs:

- `captures/rcp-he30-exact-d05026.txt`
- `captures/rcp-he30-host-d05026.txt`

Result:

- The originally observed `07 80 D0 50 26 7B` did **not** reproduce exactly.
- Instead, `00 00 40 40 30 6A` produced sibling families:
  - exact-answer run: `07 80 50 78 26 D3`
  - host-answer run: `07 80 50 50 26 FB`
- Answering those with either:
  - exact `07 80 D0 50 26 7B`
  - or host `00 00 D0 50 26 FC`
  did not open a new stage.
- Group 2 was heartbeat-only in both runs.

Read:

- The `40`-triggered branch is real, but the stable thing is not a single
  fixed `D0` family.
- It looks more like a **parameterized `50 xx 26` sibling strip** that can vary
  by context.

#### HE30d: `7A` trigger siblings

Logs:

- `captures/rcp-he30-trigger-7a5826.txt`
- `captures/rcp-he30-trigger-7a503a.txt`

Result:

- `00 00 7A 58 26 5E` produced:
  - `07 80 2F 65 F2 65`
- `00 00 7A 50 3A 4A` produced:
  - `07 80 2F 95 52 35`
- Both appeared in group 1 only.
- Group 2 was heartbeat-only.

Read:

- This is a strong result.
- The `7A` host-fed branch is not a single magic trigger for one fixed
  `2F 95 C9` reply.
- It looks like a **family mapping**:
  host-side `7A ...` variants can produce related `2F ...` response variants.

#### HE30e: `40/50` trigger siblings

Logs:

- `captures/rcp-he30-trigger-504030.txt`
- `captures/rcp-he30-trigger-406030.txt`

Result:

- `00 00 50 40 30 7A` produced:
  - `07 80 D4 50 26 7F`
- `00 00 40 60 30 4A` produced:
  - `07 80 50 58 26 F3`
- Both appeared in group 1 only.
- Group 2 was heartbeat-only.

Read:

- This is the matching strong result on the heartbeat/base-status side.
- The `40/50` host-fed branch also looks like a **family mapping**, not a lone
  one-off response.

#### HE30f: Cold-start direct tests

Logs:

- `captures/rcp-he30-cold-2f95c9.txt`
- `captures/rcp-he30-cold-d05026.txt`

Result:

- Cold host `00 00 2F 95 C9 29` did not reproduce the `2F` branch directly.
- Instead, on the second group tail it drifted into:
  - `07 80 40 40 30 ED`
- Cold host `00 00 D0 50 26 FC` did not reproduce the `D0`/`50` branch
  directly.
- Instead, on the second group tail it drifted into:
  - `07 80 C0 40 30 6D`

Read:

- These new families do **not** currently look like valid cold-start camera feed
  pages by themselves.
- They look more like second-stage response families or branch-local pages that
  require earlier context/stimulus.

### HE30 Overall Read

Most important change in our model:

- We now have good evidence for two additional **parameterized response
  surfaces**:
  - host `7A ...` -> RCP `2F ...`
  - host `40/50 ...` -> RCP `50/D4 ...`

Concrete examples:

- `7A 50 26` -> `2F 95 C9`
- `7A 58 26` -> `2F 65 F2`
- `7A 50 3A` -> `2F 95 52`
- `40 40 30` -> `50 78 26` or `50 50 26`
- `50 40 30` -> `D4 50 26`
- `40 60 30` -> `50 58 26`

What this suggests:

- some host-fed family frames are acting more like **query/page selectors**
  within a deeper status space
- and the resulting RCP families are parameterized, not isolated one-offs

What it does **not** yet suggest:

- that we have found the full active-session feed
- that these second-stage families can simply be echoed back to advance the
  conversation
- that they are valid cold-start traffic on their own

Best next move after HE30:

- Stay narrow and **dense-map one family at a time**, especially:
  - the host `7A ...` -> RCP `2F ...` surface
  - the host `40/50 ...` -> RCP `50/D4 ...` surface
- Those now look more promising than continuing to brute-force entirely new
  top-level selectors.

### HE31: Wake-The-Panel Pass

Goal:

- Go back to the practical question:
  can we make the RCP behave more "awake" on the front panel, even if we still
  do not know the full PT2 conversation?
- Specifically watch for:
  - leaving `CONNECT NOT ACT`
  - any lamp change
  - any numeric readout change
  - selector/query branches becoming reusable while the run is active

Why this pass is different:

- We now know a lot of the protocol is structured, but broad mixed streams did
  not wake the panel.
- That suggests the wake condition may depend more on:
  - **boot timing**
  - **consistent page identity**
  - **slower realistic cadence**
  - or a **background base-status layer plus a single active page**
  than on throwing many valid-looking families at it at once.

What we are testing here:

1. very early boot-window traffic
2. one stable page repeated instead of carousels
3. base-status background plus one stable page
4. CALL event path while a stable session-like stream is present

#### HE31a: Early-boot `90 -> E8` repeated page

Hypothesis:

- The panel may only accept wake/session framing very early after power-on.
- `90 -> E8` is one of the cleanest structured selectors we have, so treat it
  like a plausible "current camera page" and present it immediately.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.2 --delay 0.0 --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --repeat 12 --frame-interval 0.15 --read-after-frame 0.08 --repeat-interval 0.35 --read-after-group 0.20 --log captures/rcp-he31-boot-e8-page.txt
```

#### HE31b: Early-boot base-status plus `E8` page

Hypothesis:

- The panel may want a base status layer before it treats a page feed as live.
- Use the strongest current base-status candidates together with the clean `E8`
  page family.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.2 --delay 0.0 --frame "00 00 40 40 30 6A" --frame "00 00 00 00 80 DA" --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --repeat 10 --frame-interval 0.15 --read-after-frame 0.08 --repeat-interval 0.35 --read-after-group 0.20 --log captures/rcp-he31-boot-base-plus-e8.txt
```

#### HE31c: Slow realistic `E8` page cadence

Hypothesis:

- Our earlier fan-outs may have been too dense or too "computer-ish."
- Try a slower, more boring, repeated page cadence as if a CCU is refreshing one
  visible control page.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --repeat 20 --frame-interval 0.25 --read-after-frame 0.10 --repeat-interval 0.80 --read-after-group 0.20 --log captures/rcp-he31-slow-e8-page.txt
```

#### HE31d: Slow realistic base-status only cadence

Hypothesis:

- If the wake condition is mostly "camera is alive and reporting status", the
  base-status side may matter more than the selector side at first.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 40 40 30 6A" --frame "00 00 00 00 80 DA" --frame "00 00 50 40 30 7A" --repeat 20 --frame-interval 0.25 --read-after-frame 0.10 --repeat-interval 0.80 --read-after-group 0.20 --log captures/rcp-he31-slow-base-status.txt
```

#### HE31e: Base-status background plus stable `EC` page

Hypothesis:

- `EC` is stricter than `E8/E9` and may correspond to a more central "connected"
  page.
- Feed a background base-status layer and then one stable `EC` page only.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 40 40 30 6A" --frame "00 00 00 00 80 DA" --frame "00 00 90 00 80 4A" --frame "00 00 EC 40 30 C6" --frame "00 00 7B 50 26 57" --repeat 15 --frame-interval 0.20 --read-after-frame 0.10 --repeat-interval 0.60 --read-after-group 0.20 --log captures/rcp-he31-base-plus-ec.txt
```

#### HE31f: CALL while stable `E8` page is present

Hypothesis:

- The panel may only treat operator events as meaningful when it sees a stable
  enough background page/session context.
- Keep a simple `E8` page stream running, then inject the known synthetic CALL
  pair into the same group.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --frame "00 00 15 80 00 CF" --frame "00 00 15 00 00 4F" --repeat 10 --frame-interval 0.20 --read-after-frame 0.12 --repeat-interval 0.60 --read-after-group 0.25 --log captures/rcp-he31-e8-plus-call.txt
```

#### HE31g: CALL while base-status background is present

Hypothesis:

- Same idea as above, but maybe the event path wants base-status presence more
  than selector-page presence.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 1.0 --frame "00 00 40 40 30 6A" --frame "00 00 00 00 80 DA" --frame "00 00 15 80 00 CF" --frame "00 00 15 00 00 4F" --repeat 10 --frame-interval 0.20 --read-after-frame 0.12 --repeat-interval 0.60 --read-after-group 0.25 --log captures/rcp-he31-base-plus-call.txt
```

Recommended order:

1. `HE31a` early-boot `E8` page
2. `HE31b` early-boot base-status plus `E8`
3. `HE31c` slow `E8` page cadence
4. `HE31e` base-status plus `EC`
5. `HE31f` `E8` plus CALL
6. `HE31d` slow base-status only
7. `HE31g` base-status plus CALL

Reasoning:

- The most likely miss so far is timing and stability, not absence of valid
  bytes.
- So this pass biases toward:
  - early boot
  - one stable page
  - one stable background layer
  - and one meaningful operator event

### 2026-05-13 HE31 Results

Front-panel result:

- No HE31 run visibly woke the panel.
- No useful LCD, lamp, or numeric-display change was recorded.

Note on the `PANEL` lines in some logs:

- A few captures contain the next command line pasted into the `--prompt-screen`
  prompt.
- Those lines are not treated as evidence of panel state.

#### HE31a: Early-boot `E8` page

Log:

- `captures/rcp-he31-boot-e8-page.txt`

Result:

- Group 1 did produce the expected early structured branch:
  - `07 80 FA 50 26 51`
- Groups 2-12 then stayed heartbeat-compatible only.

Read:

- Starting very early after boot did **not** turn the `E8` page stream into a
  reusable or session-like wake path.
- It still behaved like a one-shot branch that immediately collapsed.

#### HE31b: Early-boot base-status plus `E8`

Log:

- `captures/rcp-he31-boot-base-plus-e8.txt`

Result:

- Group 1 briefly produced:
  - `07 80 40 40 30 ED`
- After that, the whole run stayed heartbeat-compatible.
- No later `E8` page family reopened.

Read:

- Adding a base-status layer at boot did **not** help `E8`; it actually biased
  the run toward the known fallback/transient family.

#### HE31c: Slow realistic `E8` page cadence

Log:

- `captures/rcp-he31-slow-e8-page.txt`

Result:

- Group 1 produced the familiar:
  - `07 80 7A 50 26 D1`
- Groups 2-20 then stayed heartbeat-compatible only.

Read:

- A slower, more boring cadence did not make the `E8` page stream feel more
  alive to the panel.
- So the earlier dense fan-outs likely were **not** failing just because they
  were too fast or too "computer-ish."

#### HE31d: Slow base-status only cadence

Log:

- `captures/rcp-he31-slow-base-status.txt`

Result:

- Group 1 produced:
  - `07 80 40 40 30 ED`
- Groups 2-20 then stayed heartbeat-compatible only.

Read:

- Repeating only the base-status side also failed to wake the panel.
- It behaved like a fallback-presence layer, not a true active-session layer.

#### HE31e: Base-status plus stable `EC`

Log:

- `captures/rcp-he31-base-plus-ec.txt`

Result:

- Group 1 produced:
  - `07 80 40 60 30 CD`
- After that, the full run stayed heartbeat-compatible.
- `EC` did **not** reopen a stable `7B` page under this background.

Read:

- Even the stricter `EC` branch did not become more useful under a stable
  base-status background.
- This argues against "just add one richer page on top of base status" as the
  missing wake condition.

#### HE31f: `E8` page plus CALL

Log:

- `captures/rcp-he31-e8-plus-call.txt`

Result:

- Group 1 again showed the early `E8` branch:
  - `07 80 FA 50 26 51`
- The following synthetic CALL pair:
  - `00 00 15 80 00 CF`
  - `00 00 15 00 00 4F`
  produced only heartbeat-compatible traffic.
- No `0x45` CALL-family reply appeared.

Read:

- A stable `E8` page context does **not** make the CALL synthetic event path
  become richer or more session-like.
- In this context, CALL actually looks less interesting than in the earlier
  dedicated CALL work.

#### HE31g: Base-status plus CALL

Log:

- `captures/rcp-he31-base-plus-call.txt`

Result:

- Group 1 produced:
  - `07 80 40 40 30 ED`
- The synthetic CALL pair after that stayed heartbeat-compatible only.
- No `0x45` CALL-family reply appeared.

Read:

- Base-status presence also does **not** make CALL act like a wake/session
  trigger.

### HE31 Overall Read

The wake-oriented pass answers a few important questions pretty cleanly:

- Early boot timing alone is **not** enough.
- One stable repeated page alone is **not** enough.
- Base-status background plus one page is **not** enough.
- CALL injected into those contexts is **not** enough.

Most important practical conclusion:

- The panel still does not look like it is waiting for merely:
  - a believable page stream
  - a believable base-status stream
  - or an operator event inside those streams

So the missing wake condition is probably something more specific, such as:

- a CCU identity/mode declaration we still have not found
- a different class of background page entirely
- or a stricter startup sequence/order than our current page-first models

### HE32: Best-Shot Startup / Handshake Ladder

Goal:

- Try the **most plausible remaining startup shapes** rather than broad
  exploration.
- Bias toward sequences that feel more like a real CCU boot:
  - declare identity or capability once
  - select a page once
  - then maintain a simpler background/value stream

Why these are the best next options:

- HE31 suggests the panel is **not** waking on:
  - simple early boot timing
  - one stable repeated page
  - base-status plus one page
  - CALL inside those contexts
- So the stronger remaining hypothesis is that we are missing a more
  **structured startup order**, not just more valid traffic.

Current best candidate building blocks:

- possible identity / discovery:
  - `00 00 A0 00 80 7A`
  - `00 00 B0 00 80 6A`
  - `00 00 B5 00 80 6F`
- possible opener / page-class:
  - `00 00 90 00 80 4A`
  - `00 00 AF 00 80 75`
- possible maintained page feed:
  - `00 00 E8 40 30 C2`
  - `00 00 7A 50 26 56`
- possible maintained background:
  - `00 00 00 00 80 DA`
  - `00 00 40 40 30 6A`
  - `00 00 50 40 30 7A`

What would count as a hit:

- any visible wake-up on the panel
- selector/query families reopening later in the same run
- a new recurring non-heartbeat family replacing the current idle rhythm
- a run that behaves differently from the usual "group 1 only, then collapse"

#### HE32a: Discovery-first, then `90 -> E8`, then maintained `7A`

Hypothesis:

- The panel may want to see readable identity/capability pages before it accepts
  a page/value stream.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 A0 00 80 7A" --frame "00 00 B0 00 80 6A" --frame "00 00 B5 00 80 6F" --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --frame "00 00 00 00 80 DA" --frame "00 00 7A 50 26 56" --frame "00 00 00 00 80 DA" --frame "00 00 7A 50 26 56" --read-after-frame 0.15 --frame-interval 0.20 --read-after-group 2.0 --log captures/rcp-he32-discovery-then-e8-maintain.txt
```

#### HE32b: `A0 -> 90 -> E8`, then heartbeat-only maintenance

Hypothesis:

- The missing piece may be a one-time selector/opening chain, with heartbeat
  alone sufficient afterward.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 A0 00 80 7A" --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --frame "00 00 00 00 80 DA" --read-after-frame 0.15 --frame-interval 0.20 --read-after-group 3.0 --log captures/rcp-he32-a0-90-e8-heartbeat-tail.txt
```

#### HE32c: `A0 -> 90 -> E8`, then `7A`-only maintenance

Hypothesis:

- Heartbeat may only mean "host present," while `7A 50 26` may be the real page
  value stream the panel wants once opened.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 A0 00 80 7A" --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --frame "00 00 7A 50 26 56" --frame "00 00 7A 50 26 56" --frame "00 00 7A 50 26 56" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 3.0 --log captures/rcp-he32-a0-90-e8-7a-tail.txt
```

#### HE32d: `AF -> 90 -> E8`, then maintained `FA`

Hypothesis:

- `AF` may be a more camera-flavored opener, and `FA 50 26` may be the sibling
  page/value family this context actually wants.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 AF 00 80 75" --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 FA 50 26 D6" --frame "00 00 00 00 80 DA" --frame "00 00 FA 50 26 D6" --frame "00 00 00 00 80 DA" --frame "00 00 FA 50 26 D6" --read-after-frame 0.15 --frame-interval 0.20 --read-after-group 2.0 --log captures/rcp-he32-af-90-e8-fa-tail.txt
```

#### HE32e: `A0 -> AF -> EC`, then maintained `7B`

Hypothesis:

- `EC` is stricter than `E8`; it may need both a general opener and a mode
  opener before the page/value feed makes sense.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 A0 00 80 7A" --frame "00 00 AF 00 80 75" --frame "00 00 EC 40 30 C6" --frame "00 00 7B 50 26 57" --frame "00 00 00 00 80 DA" --frame "00 00 7B 50 26 57" --frame "00 00 00 00 80 DA" --frame "00 00 7B 50 26 57" --read-after-frame 0.15 --frame-interval 0.20 --read-after-group 2.0 --log captures/rcp-he32-a0-af-ec-7b-tail.txt
```

#### HE32f: Repeated `90` identity beacon, then single `E8` page

Hypothesis:

- The panel may want to see a short identity/mode beacon first, before any page
  selection at all.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 90 00 80 4A" --frame "00 00 90 00 80 4A" --frame "00 00 90 00 80 4A" --frame "00 00 E8 40 30 C2" --frame "00 00 7A 50 26 56" --frame "00 00 00 00 80 DA" --frame "00 00 7A 50 26 56" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 2.5 --log captures/rcp-he32-90-beacon-then-e8.txt
```

#### HE32g: Repeated `AF` identity beacon, then single `EC` page

Hypothesis:

- Same idea as above, but aimed at the stricter `EC` branch.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 AF 00 80 75" --frame "00 00 AF 00 80 75" --frame "00 00 AF 00 80 75" --frame "00 00 EC 40 30 C6" --frame "00 00 7B 50 26 57" --frame "00 00 00 00 80 DA" --frame "00 00 7B 50 26 57" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 2.5 --log captures/rcp-he32-af-beacon-then-ec.txt
```

Recommended order:

1. `HE32a` discovery-first then `E8`
2. `HE32b` `A0 -> 90 -> E8` then heartbeat tail
3. `HE32c` `A0 -> 90 -> E8` then `7A` tail
4. `HE32e` `A0 -> AF -> EC` then `7B` tail
5. `HE32f` `90` beacon then `E8`
6. `HE32d` `AF -> 90 -> E8` then `FA` tail
7. `HE32g` `AF` beacon then `EC`

Reasoning:

- These are the strongest remaining handshake-shaped guesses:
  - discovery before page
  - stacked openers rather than alternative openers
  - set-once then maintain-later
  - beacon first, page second

### 2026-05-13 HE32 Results

Front-panel result:

- No HE32 startup/handshake sequence visibly woke the panel.
- No useful LCD, lamp, or readout change was observed.

Serially, though, the ladder was still informative: the panel did not become
"live", but several of the startup-shaped sequences diverted into alternate
one-shot response families instead of opening the expected page streams.

#### HE32a: Discovery-first, then `90 -> E8`, then maintained `7A`

Log:

- `captures/rcp-he32-discovery-then-e8-maintain.txt`

Result:

- `A0` produced no structured reply in this run.
- `B0` produced the known readable block:
  - `07 80 6C 40 30 C1`
- `B5` then produced no new stage.
- The later `90 -> E8 -> 7A` portion stayed heartbeat-compatible only.

Read:

- Discovery-first ordering did **not** turn later page maintenance into a live
  session.
- It mostly behaved like "consume one discovery/readable block, then collapse."

#### HE32b: `A0 -> 90 -> E8`, then heartbeat-only maintenance

Log:

- `captures/rcp-he32-a0-90-e8-heartbeat-tail.txt`

Result:

- `A0 -> 90` produced a new family:
  - `07 80 64 40 30 C9`
- After that, `E8`, `7A`, and the heartbeat tail stayed heartbeat-compatible.

Read:

- This is a useful new lead.
- `A0 -> 90` does not look like a wake path, but it does look like a distinct
  startup/opening branch rather than random failure.

#### HE32c: `A0 -> 90 -> E8`, then `7A`-only maintenance

Log:

- `captures/rcp-he32-a0-90-e8-7a-tail.txt`

Result:

- This reproduced the same startup branch more strongly:
  - `07 80 64 40 30 C9`
- Repeated `7A 50 26` after that did not open a new stage.

Read:

- `64 40 30` now looks real, not a one-off accident.
- It is associated with the `A0 -> 90` stacked-opener idea, not with a live
  maintained page stream.

#### HE32d: `AF -> 90 -> E8`, then maintained `FA`

Log:

- `captures/rcp-he32-af-90-e8-fa-tail.txt`

Result:

- `AF -> 90` also diverted into the same family:
  - `07 80 64 40 30 C9`
- The later `E8` and repeated `FA` host feed stayed heartbeat-compatible only.

Read:

- `64 40 30` is not unique to `A0 -> 90`; `AF -> 90` can also land there.
- That makes it look more like a startup/opening class than a branch-local oddity.

#### HE32e: `A0 -> AF -> EC`, then maintained `7B`

Log:

- `captures/rcp-he32-a0-af-ec-7b-tail.txt`

Result:

- `A0 -> AF` produced another distinct family:
  - `07 80 0D 04 AB 7F`
- `EC` and the repeated `7B` host feed after that stayed heartbeat-compatible.

Read:

- This is another useful startup-shaped diversion:
  `A0 -> AF` appears to open a discovery/status-like family of its own.
- But again, it did **not** enable the later page/value stream.

#### HE32f: Repeated `90` beacon, then single `E8` page

Log:

- `captures/rcp-he32-90-beacon-then-e8.txt`

Result:

- This capture contains two runs.
- Run 1:
  - repeated `90` beacon produced `07 80 64 40 30 C9`
- Run 2:
  - repeated `90` beacon produced `07 80 E4 40 30 49`
- In both runs, the later `E8 -> 7A` portion stayed heartbeat-compatible only.

Read:

- Repeated `90` by itself is not waking the panel, but it is clearly not
  meaningless.
- It can steer the panel into at least two startup/readable families:
  - `64 40 30`
  - `E4 40 30`

#### HE32g: Repeated `AF` beacon, then single `EC` page

Log:

- `captures/rcp-he32-af-beacon-then-ec.txt`

Result:

- Repeated `AF` beacon produced:
  - `07 80 0D 04 EB 3F`
- The later `EC -> 7B` portion stayed heartbeat-compatible only.

Read:

- Repeated `AF` also looks meaningful as a startup beacon, but again only in
  the sense of opening a one-shot family, not in waking the panel into a live
  session.

### HE32 Overall Read

This ladder did not wake the panel, but it did sharpen the handshake model:

- the panel is still **not** waking on our best "declare then maintain" startup
  sequences
- but stacked openers and beacon-like traffic are clearly meaningful

New startup-shaped families observed:

- `A0 -> 90` or `AF -> 90` -> `07 80 64 40 30 C9`
- repeated `90` beacon -> `07 80 64 40 30 C9` or `07 80 E4 40 30 49`
- `A0 -> AF` -> `07 80 0D 04 AB 7F`
- repeated `AF` beacon -> `07 80 0D 04 EB 3F`

Best current interpretation:

- `90` and `AF` are increasingly looking like **mode/class beacons or startup
  selectors**, not just ordinary page openers
- but the missing wake condition is still something beyond those beacon effects
- once one of these startup families is opened, the later page/value stream
  still collapses to heartbeat instead of becoming active

So HE32 rules out another important family of guesses:

- the panel does not appear to wake just because we got the startup **order**
  closer to reality

It may still require:

- a different class of maintained traffic after those beacon stages
- a missing hardware/state signal on another wire
- or a specific startup family we have touched indirectly but not yet maintained

### HE33: Maintain The Startup-Beacon Families

Goal:

- Follow the strongest new HE32 clue directly.
- Treat the startup-beacon families as possible **maintained classes** rather
  than merely one-shot opening responses.

Working idea:

- `90` and `AF` increasingly look like startup beacons or mode/class selectors.
- In HE32 we immediately switched from those beacon effects into `E8`/`EC` page
  traffic.
- That may have been too eager.
- Instead, try:
  - open `64 40 30 C9`, `E4 40 30 49`, `0D 04 AB 7F`, or `0D 04 EB 3F`
  - then maintain **that class**
  - and only optionally add heartbeat alongside it

What would count as a hit:

- the startup family reappears or stays reusable
- a new recurring non-heartbeat family appears later in the same run
- the panel visibly changes state
- `CONNECT NOT ACT` behavior changes while the maintained class is running

Checksums for host-shaped maintained-class frames:

- host `64 40 30` -> `00 00 64 40 30 0E`
- host `E4 40 30` -> `00 00 E4 40 30 8E`
- host `0D 04 AB` -> `00 00 0D 04 AB F8`
- host `0D 04 EB` -> `00 00 0D 04 EB B8`

#### HE33a: Repeated `90` beacon, then maintain `64 40 30`

Hypothesis:

- `64 40 30` may be the real maintained class after `90`, not an intermediate
  page on the way to `E8`.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 90 00 80 4A" --frame "00 00 90 00 80 4A" --frame "00 00 64 40 30 0E" --frame "00 00 64 40 30 0E" --frame "00 00 64 40 30 0E" --frame "00 00 64 40 30 0E" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 3.0 --log captures/rcp-he33-90-then-644030.txt
```

#### HE33b: Repeated `90` beacon, then maintain `E4 40 30`

Hypothesis:

- The alternate `90` beacon branch may want `E4 40 30` as its maintained class.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 90 00 80 4A" --frame "00 00 90 00 80 4A" --frame "00 00 E4 40 30 8E" --frame "00 00 E4 40 30 8E" --frame "00 00 E4 40 30 8E" --frame "00 00 E4 40 30 8E" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 3.0 --log captures/rcp-he33-90-then-e44030.txt
```

#### HE33c: `A0 -> 90`, then maintain `64 40 30`

Hypothesis:

- `A0 -> 90` may be a more deterministic way to enter the `64 40 30` startup
  family than bare repeated `90`.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 A0 00 80 7A" --frame "00 00 90 00 80 4A" --frame "00 00 64 40 30 0E" --frame "00 00 64 40 30 0E" --frame "00 00 64 40 30 0E" --frame "00 00 00 00 80 DA" --frame "00 00 64 40 30 0E" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 3.0 --log captures/rcp-he33-a0-90-then-644030.txt
```

#### HE33d: Repeated `AF` beacon, then maintain `0D 04 EB`

Hypothesis:

- The repeated-`AF` startup branch may want the `0D 04 EB` class maintained.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 AF 00 80 75" --frame "00 00 AF 00 80 75" --frame "00 00 0D 04 EB B8" --frame "00 00 0D 04 EB B8" --frame "00 00 0D 04 EB B8" --frame "00 00 0D 04 EB B8" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 3.0 --log captures/rcp-he33-af-then-0d04eb.txt
```

#### HE33e: `A0 -> AF`, then maintain `0D 04 AB`

Hypothesis:

- `A0 -> AF` may open a slightly different startup/status class than repeated
  `AF`, and it may want `0D 04 AB` instead of `0D 04 EB`.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 A0 00 80 7A" --frame "00 00 AF 00 80 75" --frame "00 00 0D 04 AB F8" --frame "00 00 0D 04 AB F8" --frame "00 00 0D 04 AB F8" --frame "00 00 00 00 80 DA" --frame "00 00 0D 04 AB F8" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 3.0 --log captures/rcp-he33-a0-af-then-0d04ab.txt
```

#### HE33f: `90` startup family with heartbeat interleaved

Hypothesis:

- The maintained class may still need a low-rate host-presence heartbeat beside
  it, even if the page-class itself is the important thing.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 90 00 80 4A" --frame "00 00 90 00 80 4A" --frame "00 00 64 40 30 0E" --frame "00 00 00 00 80 DA" --frame "00 00 64 40 30 0E" --frame "00 00 00 00 80 DA" --frame "00 00 64 40 30 0E" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 3.0 --log captures/rcp-he33-90-644030-with-heartbeat.txt
```

#### HE33g: `AF` startup family with heartbeat interleaved

Hypothesis:

- Same logic as above, but for the `AF`-derived startup family.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 0.5 --frame "00 00 AF 00 80 75" --frame "00 00 AF 00 80 75" --frame "00 00 0D 04 EB B8" --frame "00 00 00 00 80 DA" --frame "00 00 0D 04 EB B8" --frame "00 00 00 00 80 DA" --frame "00 00 0D 04 EB B8" --read-after-frame 0.15 --frame-interval 0.25 --read-after-group 3.0 --log captures/rcp-he33-af-0d04eb-with-heartbeat.txt
```

Recommended order:

1. `HE33a` repeated `90`, then `64 40 30`
2. `HE33c` `A0 -> 90`, then `64 40 30`
3. `HE33d` repeated `AF`, then `0D 04 EB`
4. `HE33e` `A0 -> AF`, then `0D 04 AB`
5. `HE33f` `90` family with heartbeat
6. `HE33g` `AF` family with heartbeat
7. `HE33b` repeated `90`, then `E4 40 30`

Reasoning:

- `64 40 30` looks like the strongest `90`-side startup family so far.
- `0D 04 AB/EB` is the matching `AF`-side family.
- The cleanest question first is simply:
  can maintaining those classes do anything at all?

### HE33 Results

Practical outcome:

- No visible panel wake-up.
- No useful LCD change beyond the already familiar inactive behavior.
- Maintaining the startup-beacon families did **not** turn them into live
  reusable session classes.

Serial outcome:

- The beacon families themselves are real and reproducible.
- But once opened, host-shaped maintenance of those same families mostly just
  drained the initial one-shot response and then fell back to
  heartbeat-compatible traffic.

#### HE33a: repeated `90`, then maintain `64 40 30`

Observed:

- second `90` produced:
  - `07 80 64 40 30 C9`
- first host `64 40 30` produced one more:
  - `07 80 64 40 30 C9`
- later host `64 40 30` frames were heartbeat-compatible only

Interpretation:

- `64 40 30` is a real startup-family surface.
- But simply maintaining host `64 40 30` does not keep that branch open.

#### HE33b: repeated `90`, then maintain `E4 40 30`

Observed:

- repeated `90` still opened:
  - `07 80 64 40 30 C9`
- host `E4 40 30` did **not** pivot it into a stable `E4`-maintained state
- later traffic was heartbeat-compatible only

Interpretation:

- The `90` beacon can still prefer `64` on this run.
- Host `E4 40 30` did not make the alternate `E4` startup family reusable.

#### HE33c: `A0 -> 90`, then maintain `64 40 30`

Observed:

- `A0 -> 90` produced:
  - `07 80 64 40 30 C9`
- later host `64 40 30` frames were heartbeat-compatible only

Interpretation:

- `A0 -> 90` is still a clean opener for the `64` family.
- But, again, the family did not become maintainable as a live background
  class.

#### HE33d: repeated `AF`, then maintain `0D 04 EB`

Observed:

- repeated `AF` produced:
  - `07 80 0D 04 AB 7F`
  - `07 80 0D 04 AB 7F`
- first host `0D 04 EB` drained one more:
  - `07 80 0D 04 AB 7F`
- later host `0D 04 EB` frames were heartbeat-compatible only

Interpretation:

- On this run, repeated `AF` did **not** prefer the older `EB`-suffixed
  startup family; it stayed on the `AB` family instead.
- That makes the `AF` startup side look more slippery than the `90` side.

#### HE33e: `A0 -> AF`, then maintain `0D 04 AB`

Observed:

- `A0 -> AF` produced repeated:
  - `07 80 0D 04 AB 7F`
- host `0D 04 AB` continued to drain more `AB` responses briefly
- then the run collapsed back to heartbeat-compatible traffic

Interpretation:

- `A0 -> AF` remains the cleanest opener for the `AB` startup family.
- It is stronger than the bare repeated-`AF` case, but still not enough to
  create a sustained live session layer.

#### HE33f: `90` family with heartbeat interleaved

Observed:

- repeated `90` produced:
  - `07 80 64 40 30 C9`
  - `07 80 64 40 30 C9`
- host `64 40 30` plus heartbeat still fell back to heartbeat-compatible
  traffic after that

Interpretation:

- Interleaving heartbeat did not make the `64` family maintainable.

#### HE33g: `AF` family with heartbeat interleaved

Observed:

- repeated `AF` produced:
  - `07 80 0D 04 AB 7F`
- host `0D 04 EB` plus heartbeat did not reopen a live `EB`/`AB` maintained
  branch

Interpretation:

- Heartbeat beside the `AF` startup family also did not help.

HE33 conclusion:

- The `90`/`AF` startup-beacon families are real, but they still behave like
  one-shot startup surfaces rather than the missing maintained wake/session
  stream.
- `90` is the cleaner side:
  - it repeatedly lands on `64 40 30`
- `AF` is the sloppier side:
  - it can land on `AB` or `EB`, and on this pass it mostly preferred `AB`
- Maintaining those family classes directly did not wake the panel, did not
  make later traffic reusable, and did not create a stable active state.

### 2026-05-13 CAM POWER Context Retests

Goal:

- Re-test the older `CAM POWER` exact echo inside stronger modern contexts:
  - stable `E8` page context
  - base-status context
- See whether `CAM POWER` starts to behave like a meaningful event once the
  panel already has more believable host/session traffic behind it.

Logs found:

- `captures/rcp-cam-power-e8-context-exact-echo.txt`
- `captures/rcp-cam-power-base-context-exact-echo.txt`

Note:

- I do **not** currently see separate host-shaped context reruns in
  `captures/`. This section only reflects the two exact-echo context captures
  that are present.

#### CAM POWER exact echo inside `E8` page context

Sequence:

- repeating startup page stream:
  - `00 00 90 00 80 4A`
  - `00 00 E8 40 30 C2`
  - `00 00 7A 50 26 56`
- button response:
  - `00 00 07 80 00 DD`

Result:

- The page stream initially produced the known `E8` family:
  - `07 80 7A 50 26 D1`
- During the run, the panel emitted repeated `CAM POWER` event frames:
  - `00 00 07 80 00 DD`
- When the exact echo was finally sent back, no new structured response family
  appeared afterward.
- The run remained on button-event / heartbeat-class behavior with no visible
  wake-up.

Read:

- Stronger page context does **not** make the older exact `CAM POWER` echo turn
  into a session-advancing command.
- It does show that `CAM POWER` traffic can coexist with the `E8` page stream
  without disrupting the panel into a richer state.

#### CAM POWER exact echo inside base-status context

Sequence:

- repeating startup base-status stream:
  - `00 00 40 40 30 6A`
  - `00 00 00 00 80 DA`
  - `00 00 50 40 30 7A`
- button response:
  - `00 00 07 80 00 DD`

Result:

- The first startup cycle briefly showed:
  - `07 80 C0 40 30 6D`
- After that, the run settled into plain heartbeat-compatible traffic.
- No useful new structured response appeared after the base-status context.
- No visible wake-up occurred.

Read:

- Base-status context also does **not** make the exact `CAM POWER` echo behave
  like a meaningful wake/session event.
- Compared with the `E8` context run, this one looks even flatter.

### Context-Retest Read

What changed:

- We re-tested `CAM POWER` exact echo inside stronger session-like backgrounds.

What did **not** change:

- no panel wake-up
- no new session-like serial stage
- no evidence that `CAM POWER` becomes a useful host/CCU handshake element when
  wrapped in `E8` page traffic or base-status traffic

Best current read:

- `CAM POWER` still looks much more like an **outbound panel-origin operator
  event** than a wake/session negotiation hook.
- `CALL` remains the more protocol-interesting operator event family.

### DIP Switch Experiment Ladder

Current known baseline:

- all 8 DIP positions are currently **off**
- switches are grouped as:
  - `S2` = 4 positions
  - `S3` = 4 positions
- together they likely feed MCU pins `43-50`

Goal:

- determine whether the DIP switches change the panel's startup personality,
  serial behavior, or wake/session behavior

### Safety / Method

Use this method for every DIP test:

1. Photograph the current DIP positions before changing anything.
2. Change **one switch only**.
3. Reassemble only as much as needed for safe power-on.
4. Power up the panel.
5. Observe behavior **before connecting serial**.
6. Then run the normal serial baseline checks.
7. Power down fully before changing the next switch.
8. Return to the all-off baseline between non-adjacent tests.

Record for each run:

- exact DIP pattern
- LCD startup text
- whether the active light changes
- whether `CONNECT NOT ACT` appears, and when
- whether idle heartbeat is still `00 00 00 00 80 DA`
- whether known probes still behave the same:
  - `A0`
  - `A0 -> 90`
  - `A0 -> AF`
- any new button behavior in idle state
- any complete silence / obvious baud or protocol change

### Naming Convention

Use a simple notation in notes/logs:

- baseline = `S2=0000 S3=0000`
- example single change:
  - `S2=1000 S3=0000`

If physical numbering is unclear, first pick one fixed convention and stick to
it:

- `S2-1` .. `S2-4`
- `S3-1` .. `S3-4`

### Phase 1: Single-Bit Sweep

Goal:

- find out whether any single DIP bit has an obvious effect on startup,
  protocol, or front-panel behavior

Run these eight tests, always starting from all-off baseline:

1. `S2-1` on, all others off
2. `S2-2` on, all others off
3. `S2-3` on, all others off
4. `S2-4` on, all others off
5. `S3-1` on, all others off
6. `S3-2` on, all others off
7. `S3-3` on, all others off
8. `S3-4` on, all others off

For each test:

- power-cycle
- watch LCD/startup state
- check for idle heartbeat
- try one minimal probe set:
  - idle listen
  - `A0`
  - `A0 -> 90`
  - `A0 -> AF`

What would count as a hit:

- panel boots into a different visible mode
- panel no longer shows `CONNECT NOT ACT`
- heartbeat changes or disappears
- known startup families change:
  - `64 40 30`
  - `0D 04 AB`
  - `0D 04 EB`
  - `E4 40 30`
- buttons become active in idle

### Phase 2: Group Identity Test

Only do this after Phase 1.

Goal:

- see whether one whole DIP bank behaves like an address nibble or mode nibble

Run these four tests:

1. all `S2` on, `S3` all off
2. all `S2` off, all `S3` on
3. all `S2` on, all `S3` on
4. return to all-off baseline and confirm behavior is restored

What this can reveal:

- one bank may control personality while the other controls address
- one bank may do nothing while the other changes the panel sharply
- all-on may expose service/test behavior that single-bit changes do not

### Phase 3: Follow-Up Only If A Bit Matters

If Phase 1 reveals a promising bit, branch carefully:

1. repeat that same switch setting twice to confirm reproducibility
2. test neighboring bits in the same bank
3. test that bit plus one neighboring bit
4. compare:
   - startup text
   - heartbeat
   - `A0 -> 90`
   - `A0 -> AF`
   - `E8` / `EC` selector behavior

### Good First Serial Checks Per DIP Setting

Keep the serial side small at first.

Recommended order:

1. idle listen only
2. repeating heartbeat only
3. `A0`
4. `A0 -> 90`
5. `A0 -> AF`

Only if something changes meaningfully, then test:

- `E8`
- `E9`
- `EC`
- CALL synthetic trigger

### Strong Cautions

- Do not change multiple unknown DIP bits at once in the first pass.
- Do not assume `ON` means logic high; it may actually pull the line low.
- Some switches may only be sampled at cold boot, not warm reset.
- A strange setting may stop normal serial output entirely; that is still a
  useful result.
- If one setting produces a dramatically different boot state, stop and record
  it before going wider.

### DIP Phase 1 Results

Practical visible result:

- Most single-bit DIP settings looked the same on the panel.
- User-reported visible exception:
  - the **first DIP-switch setting** briefly showed a small cursor on the LCD
    for a few seconds
- Otherwise, no single-bit setting visibly woke the panel or cleared
  `CONNECT NOT ACT`.

Serial result:

- The DIP switches are **not** inert.
- They do not appear to change the basic heartbeat personality dramatically, but
  they **do** affect which startup-beacon family opens.

Stable observations across most tested single-bit settings:

- idle heartbeat remained present
- plain `A0` remained heartbeat-compatible only
- `A0 -> AF` usually still opened:
  - `07 80 0D 04 AB 7F`
- `A0 -> 90` usually still opened:
  - `07 80 64 40 30 C9`

Important exceptions:

#### `S2-1`

- user saw a temporary small LCD cursor
- `A0 -> 90` still opened:
  - `07 80 64 40 30 C9`
- but `A0 -> AF` went flat:
  - no RX in the recorded run
- heartbeat-only stimulus produced:
  - `07 80 C0 40 30 6D`

Read:

- `S2-1` appears to change startup/UI behavior and may suppress or disturb the
  `AF`-family startup branch.

#### `S2-3`

- `A0 -> 90` collapsed to heartbeat-only
- `A0 -> AF` still opened:
  - `07 80 0D 04 AB 7F`
- heartbeat-only stimulus produced:
  - `07 80 40 40 30 ED`

Read:

- `S2-3` appears to suppress the `90`/`64` startup-beacon branch while leaving
  the `AF`/`AB` side alive.

#### `S3-1`

- `A0 -> 90` opened a **different** startup family:
  - `07 80 E4 40 30 49`
  instead of the more common:
  - `07 80 64 40 30 C9`
- `A0 -> AF` still opened:
  - `07 80 0D 04 AB 7F`
- heartbeat-only stimulus produced:
  - `07 80 40 40 30 ED`

Read:

- `S3-1` is the clearest proof so far that the DIP bank can steer the panel
  into a different startup-family page rather than merely changing noise or
  timing.

#### Other tested single-bit positions

Settings that still looked broadly "normal" in the first pass:

- `S2-2`
- `S2-4`
- `S3-3`
- `S3-4`

These generally preserved:

- `A0 -> 90` -> `07 80 64 40 30 C9`
- `A0 -> AF` -> `07 80 0D 04 AB 7F`

Heartbeat-only transients varied by setting:

- `S2-2`, `S2-3`, `S2-4`, `S3-1`, `S3-3` tended toward:
  - `07 80 40 40 30 ED`
- `S2-1`, `S3-2`, `S3-4` tended toward:
  - `07 80 C0 40 30 6D`

Missing / not-found logs:

- `captures/dip-s3-1-a0.txt`
- `captures/dip-s3-2-a0-90.txt`

So the current first-pass read should stay slightly cautious around those two
cases.

### DIP Phase 1 Read

Best current interpretation:

- the DIP switches likely do participate in **panel personality / startup mode
  selection**
- they do **not** appear to erase or obviously reconfigure the panel in a
  catastrophic way during normal testing
- they can bias:
  - whether the `90` startup branch opens
  - which `90` family appears (`64` vs `E4`)
  - whether the `AF` branch survives cleanly

Most important next follow-ups:

1. repeat `S3-1` to confirm the `E4` startup-family shift
2. repeat `S2-3` to confirm suppression of the `90` branch
3. repeat `S2-1` to confirm suppression/instability of the `AF` branch and the
   brief LCD cursor

### HE34: Broad "Dot The I's" Command Sweep

Goal:

- run a broad automated sweep of many checksum-valid commands
- log any response that is not explainable as ordinary heartbeat traffic
- do one "clean conscience" pass so we can say we really checked the space

Why this is worth doing:

- we now know the startup-family surface is real and DIP-sensitive
- but we still have not done one final broad, automation-first pass with the
  current tools and classifier

Recommended order:

1. all-off DIP baseline
2. optional repeat under `S3-1` because it opens the `E4` startup family
3. optional repeat under `S2-3` because it suppresses the `90` branch

#### HE34a: Cold direct sweep, all-off baseline

This tests all command bytes `0x00-0xFF` in the observed host frame shape:

- prefix: `00 00`
- state/value: `00 80`

The script:

- logs heartbeat-compatible windows quietly
- logs any anomaly explicitly
- pauses on anomaly so you can power-cycle and continue

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x00-0xFF --states 0x00 --values 0x80 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he34-direct-alloff-00-80.txt
```

#### HE34b: Cold direct sweep, `S3-1` active

Same sweep, but under the DIP setting that shifted the startup-family page from
`64` to `E4`.

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x00-0xFF --states 0x00 --values 0x80 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he34-direct-s3-1-00-80.txt
```

#### HE34c: Cold direct sweep, `S2-3` active

Same sweep, but under the DIP setting that suppressed the `90` branch in the
first pass.

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x00-0xFF --states 0x00 --values 0x80 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he34-direct-s2-3-00-80.txt
```

#### Optional HE34d: Smaller second pass with alternate state/value

If the first pass looks too flat, do a smaller alternate parameter pass:

- commands: `0x00-0xFF`
- state/value: `20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x00-0xFF --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he34-direct-alloff-20-d0.txt
```

Read:

- a broad sweep like this is not expected to magically wake the panel
- what it *can* do is reveal:
  - overlooked command families
  - DIP-sensitive outliers
  - whether the current known families are really the whole visible surface

### HE34 Initial Result: Broad `20 D0` Sweep

Run observed:

- capture file present:
  - `captures/he34-direct-alloff-20-d0-no-pwr.txt`

Important panel-side observation:

- unlike the more ordinary broad sweeps, `CONNECT NOT ACT` reportedly **did not
  appear after the first anomaly**
- instead, the panel stayed out of that state until much later in the run
- on the latest run, the user interrupted with `Ctrl+C` when
  `CONNECT NOT ACT` finally came on screen near the end

That makes this run interesting even before looking at the anomaly bytes:

- the sustained `state/value = 20 D0` traffic appears to hold the panel out of
  the inactive timeout state longer than the simpler baseline sweeps

#### Serial anomaly found so far

The first repeatable anomaly in the `20 D0` sweep occurred at:

- command `0x01`

Observed family:

- `07 80 40 24 DD 64`

Example raw window:

- `00 00 00 80 DA 07 80 40 24 DD 64 07 80 40 24 DD 64 07 80 40 24 DD 64 07 80 40 24 DD 64`

Repeated later as:

- `07 80 40 24 DD 64`
  repeated four times without the leading heartbeat fragment

Read:

- this is a **new `0x40`-family sibling**
- it is not the older fallback/transient:
  - `07 80 40 40 30 ED`
- and it is not the `C0 40 30 6D` branch either

Best current interpretation:

- `20 D0` is not just "another payload pair"
- under a broad sustained sweep, it appears to:
  - expose at least one new structured `0x40`-family page
  - and also change the panel's timeout / inactive-screen behavior

This does **not** prove wake-up or active session, but it does make `20 D0`
look more session-like than the simpler `00 80` broad sweeps.

Best next follow-up from this result:

1. confirm `cmd=0x01, state/value=20 D0` directly in a short targeted test
2. compare `cmd=0x00`, `0x01`, `0x02`, `0x03` under `20 D0`
3. compare whether repeating only `00 00 01 20 D0 AB` delays `CONNECT NOT ACT`
   the same way the broader sweep did

### HE34 Completion Cross-Check

For completion's sake, the three HE34 captures do **not** all carry the same
weight:

- `captures/he34-direct-alloff-00-80.txt`
  - this is the **completed** baseline sweep
  - it ran through the full `0x00-0xFF` command range
  - it ended cleanly with:
    - `FINAL heartbeat-compatible RX: 18 bytes, offset 0, 3 frames + 0 bytes`
    - `Anomalies: 112`
- `captures/he34-direct-alloff-00-80-no-pwr.txt`
  - this is an **interrupted partial rerun**
  - it reproduced the first known baseline anomaly at `cmd=0x01`:
    - `07 80 40 20 D8 65`
  - but it should not be treated as the authoritative baseline map
- `captures/he34-direct-alloff-20-d0-no-pwr.txt`
  - this is also an **interrupted** run
  - but it is still important because it reproduced a distinct first anomaly:
    - `07 80 40 24 DD 64`
  - and the panel-side timeout behavior was observably different

That gives the HE34 branch a cleaner interpretation:

- `00 80` already yields a **broad structured response map**, not just a couple
  of isolated hits
- so `20 D0` is interesting **not because it is the only state/value pair that
  produces anomalies**
- it is interesting because:
  - its first mapped `0x40`-family response differs from the baseline
  - and the panel reportedly stayed out of `CONNECT NOT ACT` much longer during
    the run

So the right next question is:

- not "does `20 D0` produce any anomalies at all?"
- but "does `20 D0` represent a more session-like variant of the same broad
  command surface?"

### HE35: Targeted `20 D0` Follow-Up Ladder

Goal: check whether the interesting part of the `20 D0` sweep is specifically
`cmd=0x01`, whether nearby commands behave similarly, and whether repeating only
that one frame delays `CONNECT NOT ACT` by itself.

#### HE35a: direct compare `cmd=0x00-0x03` under `20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x00-0x03 --states 0x20 --values 0xD0 --settle 3.0 --after-each 1.0 --after 2.0 --pause-on-anomaly --log captures/he35-direct-20d0-cmd00-03.txt
```

What to watch:

- whether `0x01` is still the only anomaly
- whether `0x00`, `0x02`, or `0x03` produce siblings
- whether `CONNECT NOT ACT` appears during the short run

#### HE35b: repeat only `cmd=0x01 @ 20 D0`

Target frame:

- `00 00 01 20 D0 AB`

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 01 20 D0 AB" --repeat 40 --interval 0.50 --delay 3 --after 3 --frame-size 0 --log captures/he35-repeat-cmd01-20d0.txt
```

What to watch:

- whether the panel stays out of `CONNECT NOT ACT` while this one frame repeats
- whether `07 80 40 24 DD 64` appears again
- whether the response is one-shot only or repeats periodically

#### HE35c: repeat only `cmd=0x01 @ 00 80` baseline control

Target frame:

- `00 00 01 00 80 DB`

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 01 00 80 DB" --repeat 40 --interval 0.50 --delay 3 --after 3 --frame-size 0 --log captures/he35-repeat-cmd01-0080-control.txt
```

Purpose:

- compare the same command byte under the older baseline payload
- see whether only `20 D0` delays the timeout-like LCD behavior

#### HE35d: slower repeat of `cmd=0x01 @ 20 D0`

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 01 20 D0 AB" --repeat 20 --interval 1.50 --delay 3 --after 3 --frame-size 0 --log captures/he35-repeat-cmd01-20d0-slow.txt
```

Purpose:

- estimate whether the panel cares about this frame's **content**, or whether it
  only helps when the cadence is dense enough

#### HE35e: neighboring single-frame repeats under `20 D0`

Neighbor frames:

- `00 00 00 20 D0 AA`
- `00 00 02 20 D0 A8`
- `00 00 03 20 D0 A9`

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 00 20 D0 AA" --repeat 20 --interval 0.50 --delay 3 --after 3 --frame-size 0 --log captures/he35-repeat-cmd00-20d0.txt
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 02 20 D0 A8" --repeat 20 --interval 0.50 --delay 3 --after 3 --frame-size 0 --log captures/he35-repeat-cmd02-20d0.txt
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 03 20 D0 A9" --repeat 20 --interval 0.50 --delay 3 --after 3 --frame-size 0 --log captures/he35-repeat-cmd03-20d0.txt
```

Purpose:

- test whether the timeout effect belongs to the whole early `20 D0` region
- or whether `cmd=0x01` is the special case

#### Recommended order

1. `HE35a` direct `0x00-0x03` compare
2. `HE35b` repeat only `cmd=0x01 @ 20 D0`
3. `HE35c` baseline `cmd=0x01 @ 00 80` control
4. `HE35d` slow `cmd=0x01 @ 20 D0`
5. `HE35e` neighboring repeats

Interpretation guide:

- if only `cmd=0x01 @ 20 D0` delays `CONNECT NOT ACT`, that is our best
  session-like micro-lead so far
- if `0x00-0x03 @ 20 D0` all help, then this may be an early-page or
  low-command-region effect rather than a single magic frame
- if the same effect also appears at `cmd=0x01 @ 00 80`, then the LCD behavior
  may be more about traffic density than payload meaning

### HE35 Result: `20 D0` Narrowing Pass

Capture files present:

- `captures/he35-direct-20d0-cmd00-03.txt`
- `captures/he35-repeat-cmd01-20d0.txt`
- `captures/he35-repeat-cmd01-0080-control.txt`

#### HE35a: direct compare `cmd=0x00-0x03` under `20 D0`

Results:

- `cmd=0x00` -> heartbeat only
- `cmd=0x01` -> repeated:
  - `07 80 40 24 DD 64`
- `cmd=0x02` -> heartbeat only
- `cmd=0x03` -> new sibling:
  - `07 80 20 12 97 78`

So `0x01` is **not** the only responsive command in the early `20 D0` region,
but it is still the clearest `0x40`-family member.

#### HE35b: repeat only `cmd=0x01 @ 20 D0`

Target frame:

- `00 00 01 20 D0 AB`

Serial result:

- first useful response appeared on the second send
- observed:
  - `07 80 40 24 DD 64`
  - then one more fragmented repeat of the same family
- after that, the run returned to plain heartbeat even though the frame kept
  repeating

Panel-side observation:

- `CONNECT NOT ACT` reportedly stayed off until the script finished

Read:

- this looks like a **one-shot branch opener with continued timeout suppression**
- the payload still matters after the serial branch is spent, at least at the
  LCD/state-timer level

#### HE35c: repeat only `cmd=0x01 @ 00 80` baseline control

Target frame:

- `00 00 01 00 80 DB`

Serial result:

- first useful response again appeared on the second send
- observed several copies of:
  - `07 80 40 20 D8 65`
- after that, the run also returned to plain heartbeat while the frame kept
  repeating

Panel-side observation:

- `CONNECT NOT ACT` reportedly also stayed off until the script finished

Read:

- this weakens the stricter interpretation that only `20 D0` delays the LCD
  timeout
- repeated `cmd=0x01` traffic under both payloads appears capable of holding
  the panel out of `CONNECT NOT ACT` while the stream is active

Current best interpretation after HE35:

- the distinctive thing about `20 D0` is still the **response family shift**
  from:
  - `07 80 40 20 D8 65`
  to:
  - `07 80 40 24 DD 64`
- but the timeout-holding effect may belong more broadly to sustained repeated
  `cmd=0x01` traffic than to `20 D0` alone
- `cmd=0x03 @ 20 D0` is now worth treating as a second live lead because it
  opened:
  - `07 80 20 12 97 78`

Best next follow-up from HE35:

1. run `HE35d` slow `cmd=0x01 @ 20 D0` to separate content from cadence
2. run `HE35e` neighboring repeats to see whether `0x00`, `0x02`, or `0x03`
   also suppress `CONNECT NOT ACT`
3. consider a matched repeat on `cmd=0x03 @ 20 D0` because it now has its own
   distinct structured family

### HE35 Follow-Up: Slow + Neighbor Repeats

Additional capture files present:

- `captures/he35-repeat-cmd01-20d0-slow.txt`
- `captures/he35-repeat-cmd00-20d0.txt`
- `captures/he35-repeat-cmd02-20d0.txt`
- `captures/he35-repeat-cmd03-20d0.txt`

Panel-side observation from this pass:

- all of these additional runs reportedly kept the LCD in the same "clear"
  state while the script was running

That is important because it means the timeout-holding effect is **not** unique
to repeated `cmd=0x01`.

#### HE35d: slow repeat of `cmd=0x01 @ 20 D0`

Target frame:

- `00 00 01 20 D0 AB`

Serial result:

- second send still opened the same family:
  - `07 80 40 24 DD 64`
- it produced a larger one-shot burst than the faster repeat
- after that, the stream returned to heartbeat for the rest of the run

Read:

- slowing the cadence to `1.5 s` did **not** kill the branch
- so this is not purely a "dense traffic only" effect

#### HE35e-1: repeat only `cmd=0x00 @ 20 D0`

Target frame:

- `00 00 00 20 D0 AA`

Serial result:

- second send opened a new sibling:
  - `07 80 40 48 3A EF`
- then the run fell back to heartbeat

Read:

- `cmd=0x00 @ 20 D0` is not heartbeat-only after all
- it appears to open its own `0x40`-family sibling under repetition

#### HE35e-2: repeat only `cmd=0x02 @ 20 D0`

Target frame:

- `00 00 02 20 D0 A8`

Serial result:

- second send opened:
  - `07 80 20 12 87 68`
- then returned to heartbeat

Read:

- this makes the `0x02/0x03 @ 20 D0` region look like a small structured
  `0x20 0x12 ...` family surface, not a one-off anomaly

#### HE35e-3: repeat only `cmd=0x03 @ 20 D0`

Target frame:

- `00 00 03 20 D0 A9`

Serial result:

- second send opened:
  - `07 80 20 12 97 78`
- then returned to heartbeat

Read:

- `0x03 @ 20 D0` behaves consistently with the direct-compare run
- it is the stronger `0x20 0x12 ...` sibling of the `0x02` branch

Current read after the full HE35 pass:

- the low `0x00-0x03` command region under `20 D0` is now clearly structured:
  - `0x00` -> `07 80 40 48 3A EF`
  - `0x01` -> `07 80 40 24 DD 64`
  - `0x02` -> `07 80 20 12 87 68`
  - `0x03` -> `07 80 20 12 97 78`
- each branch still looks one-shot on the serial side
- but repeated traffic from this small region appears sufficient to hold the LCD
  out of `CONNECT NOT ACT` while the script is active

That suggests a better model:

- these frames may not be "the wake command"
- but they do look like a **low-command session-presence/status surface**
- and different command bytes select neighboring structured families within it

### HE36: Low-Command Mixer + Broadened Keep-Alive Search

Goal: test whether mixing the known low-command `20 D0` frames does more than
repeating one at a time, and broaden the search to nearby commands that might
belong to the same "keep-alive-ish" session-presence surface.

Known live low-command `20 D0` frames so far:

- `00 00 00 20 D0 AA`
- `00 00 01 20 D0 AB`
- `00 00 02 20 D0 A8`
- `00 00 03 20 D0 A9`

#### HE36a: maintained 4-frame mixer, medium cadence

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 00 20 D0 AA" --frame "00 00 01 20 D0 AB" --frame "00 00 02 20 D0 A8" --frame "00 00 03 20 D0 A9" --repeat 10 --frame-interval 0.50 --read-after-frame 0.20 --read-after-group 0.80 --log captures/he36-mixer-20d0-00-03-medium.txt
```

What to watch:

- whether the LCD stays clear / non-`CONNECT NOT ACT`
- whether new structured families appear beyond the known one-shot siblings
- whether the mixed stream looks "more alive" than any single-frame repeat

#### HE36b: maintained 4-frame mixer, slow cadence

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 00 20 D0 AA" --frame "00 00 01 20 D0 AB" --frame "00 00 02 20 D0 A8" --frame "00 00 03 20 D0 A9" --repeat 8 --frame-interval 1.20 --read-after-frame 0.25 --read-after-group 1.20 --log captures/he36-mixer-20d0-00-03-slow.txt
```

Purpose:

- separate "this class matters" from "the panel only likes brisk chatter"

#### HE36c: 2-frame split mixers by family

`0x40`-leaning pair:

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 00 20 D0 AA" --frame "00 00 01 20 D0 AB" --repeat 12 --frame-interval 0.60 --read-after-frame 0.20 --read-after-group 0.80 --log captures/he36-mixer-20d0-40pair.txt
```

`0x20 0x12`-leaning pair:

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 02 20 D0 A8" --frame "00 00 03 20 D0 A9" --repeat 12 --frame-interval 0.60 --read-after-frame 0.20 --read-after-group 0.80 --log captures/he36-mixer-20d0-20pair.txt
```

Purpose:

- see whether one subfamily is better at holding the panel clear
- or whether mixing both subfamilies is what helps

#### HE36d: broadened direct sweep `0x04-0x0F @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x04-0x0F --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he36-direct-20d0-cmd04-0f.txt
```

Purpose:

- look for more neighboring "keep-alive-ish" branches without re-running the
  whole `0x00-0xFF` space
- map whether this structured low-command surface extends further upward

#### HE36e: broader direct sweep `0x10-0x1F @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x10-0x1F --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he36-direct-20d0-cmd10-1f.txt
```

Purpose:

- test whether the same effect is really a "low command region" phenomenon
- or mostly a tight `0x00-0x03` pocket

#### HE36f: single-frame repeats for any new HE36d/e hits

If `HE36d` or `HE36e` turn up new live candidates, repeat them individually with
the same pattern used in HE35:

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "<candidate frame>" --repeat 20 --interval 0.50 --delay 3 --after 3 --frame-size 0 --log captures/he36-repeat-<candidate>.txt
```

Use this only for commands that actually produce a structured family in the
direct sweep.

#### Recommended order

1. `HE36a` 4-frame medium mixer
2. `HE36c` split mixers
3. `HE36b` 4-frame slow mixer
4. `HE36d` broadened `0x04-0x0F` sweep
5. `HE36e` broadened `0x10-0x1F` sweep
6. `HE36f` repeat any new hits individually

Interpretation guide:

- if the mixer holds the LCD clear **and** produces richer serial behavior,
  we may be getting closer to a maintained background stream class
- if the mixer holds the LCD clear but still stays serially one-shot, that
  still supports a "session-presence without full activation" model
- if `0x04-0x1F @ 20 D0` reveals more structured siblings, then this is likely
  a broader maintained low-command surface rather than just a four-command oddity

### HE36 Result: Mixer + Broadened Low-Command Surface

Capture files present:

- `captures/he36-mixer-20d0-00-03-medium.txt`
- `captures/he36-mixer-20d0-00-03-slow.txt`
- `captures/he36-mixer-20d0-40pair.txt`
- `captures/he36-mixer-20d0-20pair.txt`
- `captures/he36-direct-20d0-cmd04-0f.txt`
- `captures/he36-direct-20d0-cmd10-1f.txt`

#### Mixer behavior

The mixer runs were surprisingly consistent:

- they did **not** open deeper multi-turn branches
- they mostly produced the same early one-shot family behavior in group 1
- after that, the stream settled into heartbeat-compatible traffic while the
  script continued

The practical implication is still important:

- the mixed low-command stream behaves like a stable maintained background
  surface
- but not yet like a full session activator

The split-pair mixers behaved the same way:

- `00/01` pair stayed on the `0x40` side
- `02/03` pair stayed on the `0x20 0x12` side
- neither pair triggered a richer second-stage exchange

#### Broadened direct sweep: `0x04-0x0F @ 20 D0`

This region is definitely live and patterned:

- `0x05` -> `07 80 41 24 DD 65`
- `0x07` -> `07 80 10 09 D7 13`
- `0x09` -> `07 80 42 24 DD 66`
- `0x0B` -> `07 80 21 12 17 F9`
- `0x0D` -> `07 80 43 24 DD 67`

#### Broadened direct sweep: `0x10-0x1F @ 20 D0`

The pattern extends cleanly upward:

- `0x11` -> `07 80 44 24 DD 60`
- `0x13` -> `07 80 22 12 97 7A`
- `0x15` -> `07 80 45 24 DD 61`
- `0x17` -> `07 80 11 09 D7 12`
- `0x19` -> `07 80 46 24 DD 62`
- `0x1B` -> `07 80 23 12 17 FB`
- `0x1D` -> `07 80 47 24 DD 63`

Current best structural read:

- the low `20 D0` command region is not just a tiny `0x00-0x03` pocket
- it extends at least through `0x1D` with repeating family structure
- the active commands are mostly the odd ones in the region
- and the responses appear to cluster into at least three sibling families:
  - `07 80 4x 24 DD 6x`
  - `07 80 2x 12 .. ..`
  - `07 80 1x 09 D7 1x`

That is a much stronger hint that we are seeing a real maintained status/control
surface rather than random one-shot exceptions.

### HE37: Ordered Camera-State Cycle Hypothesis

Working idea: if we put our "how would Sony have built this in the 1990s?"
hats on, the panel may be expecting a **fixed scan order** of camera-state pages,
not just valid packets in any order.

That would explain why:

- repeated single frames can hold the LCD out of `CONNECT NOT ACT`
- valid mixed traffic can keep the panel semi-alive
- but we still do not get a full wake-up or richer panel state

The key question here is:

- does an **ordered** low-command `20 D0` cycle behave better than the same
  frames shuffled?

#### HE37a: ascending ordered low-band cycle `0x00-0x0F`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 00 20 D0 AA" --frame "00 00 01 20 D0 AB" --frame "00 00 02 20 D0 A8" --frame "00 00 03 20 D0 A9" --frame "00 00 04 20 D0 AE" --frame "00 00 05 20 D0 AF" --frame "00 00 06 20 D0 AC" --frame "00 00 07 20 D0 AD" --frame "00 00 08 20 D0 A2" --frame "00 00 09 20 D0 A3" --frame "00 00 0A 20 D0 A0" --frame "00 00 0B 20 D0 A1" --frame "00 00 0C 20 D0 A6" --frame "00 00 0D 20 D0 A7" --frame "00 00 0E 20 D0 A4" --frame "00 00 0F 20 D0 A5" --repeat 4 --frame-interval 0.35 --read-after-frame 0.15 --read-after-group 1.0 --log captures/he37-ordered-00-0f.txt
```

Purpose:

- mimic a boring fixed page-scan loop
- see whether simple ascending order looks better than ad hoc mixing

#### HE37b: shuffled control, same membership as `HE37a`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 09 20 D0 A3" --frame "00 00 01 20 D0 AB" --frame "00 00 0D 20 D0 A7" --frame "00 00 05 20 D0 AF" --frame "00 00 03 20 D0 A9" --frame "00 00 0B 20 D0 A1" --frame "00 00 07 20 D0 AD" --frame "00 00 0F 20 D0 A5" --frame "00 00 00 20 D0 AA" --frame "00 00 08 20 D0 A2" --frame "00 00 02 20 D0 A8" --frame "00 00 0A 20 D0 A0" --frame "00 00 04 20 D0 AE" --frame "00 00 0C 20 D0 A6" --frame "00 00 06 20 D0 AC" --frame "00 00 0E 20 D0 A4" --repeat 4 --frame-interval 0.35 --read-after-frame 0.15 --read-after-group 1.0 --log captures/he37-shuffled-00-0f.txt
```

Purpose:

- same traffic, same cadence, different order
- direct control for the "expected sequence" hypothesis

#### HE37c: ascending odd-only cycle `0x01-0x1D`

Active responders so far are mostly the odd commands. This run leans into that.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 01 20 D0 AB" --frame "00 00 03 20 D0 A9" --frame "00 00 05 20 D0 AF" --frame "00 00 07 20 D0 AD" --frame "00 00 09 20 D0 A3" --frame "00 00 0B 20 D0 A1" --frame "00 00 0D 20 D0 A7" --frame "00 00 11 20 D0 BB" --frame "00 00 13 20 D0 B9" --frame "00 00 15 20 D0 BF" --frame "00 00 17 20 D0 BD" --frame "00 00 19 20 D0 B3" --frame "00 00 1B 20 D0 B1" --frame "00 00 1D 20 D0 B7" --repeat 4 --frame-interval 0.35 --read-after-frame 0.15 --read-after-group 1.0 --log captures/he37-ordered-odd-01-1d.txt
```

Purpose:

- test whether the panel only cares about the "live" pages, not the quiet ones

#### HE37d: reversed odd-only cycle

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 1D 20 D0 B7" --frame "00 00 1B 20 D0 B1" --frame "00 00 19 20 D0 B3" --frame "00 00 17 20 D0 BD" --frame "00 00 15 20 D0 BF" --frame "00 00 13 20 D0 B9" --frame "00 00 11 20 D0 BB" --frame "00 00 0D 20 D0 A7" --frame "00 00 0B 20 D0 A1" --frame "00 00 09 20 D0 A3" --frame "00 00 07 20 D0 AD" --frame "00 00 05 20 D0 AF" --frame "00 00 03 20 D0 A9" --frame "00 00 01 20 D0 AB" --repeat 4 --frame-interval 0.35 --read-after-frame 0.15 --read-after-group 1.0 --log captures/he37-reversed-odd-01-1d.txt
```

Purpose:

- same family membership, opposite order
- if order matters, this should behave worse than `HE37c`

#### HE37e: `0x40`-family-focused ordered run

Known `0x40`-leaning branch selectors:

- `0x00`, `0x01`, `0x05`, `0x09`, `0x0D`, `0x11`, `0x15`, `0x19`, `0x1D`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 00 20 D0 AA" --frame "00 00 01 20 D0 AB" --frame "00 00 05 20 D0 AF" --frame "00 00 09 20 D0 A3" --frame "00 00 0D 20 D0 A7" --frame "00 00 11 20 D0 BB" --frame "00 00 15 20 D0 BF" --frame "00 00 19 20 D0 B3" --frame "00 00 1D 20 D0 B7" --repeat 5 --frame-interval 0.40 --read-after-frame 0.15 --read-after-group 1.0 --log captures/he37-ordered-40family.txt
```

Purpose:

- see whether one specific subfamily behaves most like a maintained status page

#### HE37f: `0x20/0x10`-family-focused ordered run

Known non-`0x40` selectors:

- `0x02`, `0x03`, `0x07`, `0x0B`, `0x13`, `0x17`, `0x1B`

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 02 20 D0 A8" --frame "00 00 03 20 D0 A9" --frame "00 00 07 20 D0 AD" --frame "00 00 0B 20 D0 A1" --frame "00 00 13 20 D0 B9" --frame "00 00 17 20 D0 BD" --frame "00 00 1B 20 D0 B1" --repeat 5 --frame-interval 0.40 --read-after-frame 0.15 --read-after-group 1.0 --log captures/he37-ordered-20-10-family.txt
```

Purpose:

- test whether the non-`0x40` siblings are the real ordered data pages

#### HE37g: hold-then-step ladder

This is the most "old broadcast gear" test in the set: hold one page long enough
for the panel to latch it, then step to the next.

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 01 20 D0 AB" --frame "00 00 01 20 D0 AB" --frame "00 00 01 20 D0 AB" --frame "00 00 03 20 D0 A9" --frame "00 00 03 20 D0 A9" --frame "00 00 05 20 D0 AF" --frame "00 00 05 20 D0 AF" --frame "00 00 09 20 D0 A3" --repeat 4 --frame-interval 0.50 --read-after-frame 0.20 --read-after-group 1.2 --log captures/he37-hold-then-step.txt
```

Purpose:

- test whether later pages only "count" after earlier pages have been presented
  for a while

#### Recommended order

1. `HE37a` ordered ascending `0x00-0x0F`
2. `HE37b` shuffled control
3. `HE37c` ordered odd-only
4. `HE37d` reversed odd-only
5. `HE37e` `0x40`-family run
6. `HE37f` `0x20/0x10`-family run
7. `HE37g` hold-then-step

Interpretation guide:

- if ordered beats shuffled, sequence matters
- if ascending odd-only beats reversed odd-only, order matters even more strongly
- if one family-only run behaves best, we may have identified the maintained
  "camera state page" class
- if hold-then-step behaves best, the panel may be sampling pages in sequence
  rather than merely checking for their existence

### HE37 Result: Ordered-Cycle Hypothesis

Capture files present:

- `captures/he37-ordered-00-0f.txt`
- `captures/he37-shuffled-00-0f.txt`
- `captures/he37-ordered-odd-01-1d.txt`
- `captures/he37-reversed-odd-01-1d.txt`
- `captures/he37-ordered-40family.txt`
- `captures/he37-ordered-20-10-family.txt`
- `captures/he37-hold-then-step.txt`

Panel-side observation:

- the full ascending `0x00-0x0F` run reportedly only held the panel clear for
  the first group, then lost it
- the other HE37 runs reportedly held the panel in its clear/non-
  `CONNECT NOT ACT` state for the length of the run
- none produced a visible wake-up beyond the normal/default panel state

#### Big picture

The "1990s ordered scan" idea was a good hypothesis, but these results do not
show a strong "correct order unlocks the panel" effect.

What they do suggest is a subtler model:

- **which frame family is present** matters
- **keeping to a tighter active subset** helps
- but **strict ordering by itself** does not appear to wake the panel

#### HE37a vs HE37b: ordered ascending vs shuffled `0x00-0x0F`

Result:

- both runs only produced the same familiar early group-1 branch behavior
- after that, both settled into heartbeat-compatible traffic
- no richer serial progression appeared in the ordered run

Read:

- using the same low-band pages in ascending order did **not** beat the same
  pages shuffled

#### HE37c vs HE37d: ordered odd-only vs reversed odd-only

Result:

- both odd-only runs behaved similarly
- both held the panel clearer than the full `0x00-0x0F` pass
- neither showed a decisive serial advantage for ascending vs reversed order

Read:

- trimming the cycle to the "live" odd-command subset helps more than the exact
  direction of the scan

#### HE37e: ordered `0x40`-family run

Result:

- group 1 still opened the familiar `0x40` branch around:
  - `07 80 40 24 DD 64`
- later groups stayed heartbeat-compatible
- panel reportedly stayed clear for the duration

Read:

- the `0x40`-family subset is a plausible maintained background class
- but it still does not look like the missing full wake/session stream

#### HE37f: ordered `0x20/0x10`-family run

Result:

- group 1 still opened the familiar `0x20 0x12` branch around:
  - `07 80 20 12 97 78`
- later groups stayed heartbeat-compatible
- panel reportedly also stayed clear for the duration

Read:

- the non-`0x40` active subset also behaves like a viable maintained background
  class
- again, no evidence yet that it is the unique "correct next page sequence"

#### HE37g: hold-then-step

Result:

- this run did complete and is valid
- it produced the same basic early one-shot `0x40`-family behavior in group 1
- then settled into heartbeat-compatible traffic

Read:

- holding a page longer before stepping did **not** unlock a richer progression

Current best interpretation after HE37:

- we still do **not** have evidence for one exact required page order
- we **do** have evidence that narrower active subsets maintain the panel better
  than a broad "everything from `0x00` upward" scan
- the panel may care more about:
  - being fed the right **class** of recurring pages
  - at a believable cadence
  - than about one strict ascending sequence

That means the next most 1990s-looking hypothesis is probably:

- a **small recurring scan set** for the maintained background layer
- plus some separate startup/beacon/identity pages
- rather than one giant ordered status loop by itself

### HE38: Broad Semi-Awake State Hunter

Goal: broaden outward from the known `20 D0` low-command surface and look for
other command regions that keep the panel in the same clear / semi-awake state,
even if they do not fully wake it.

This is intentionally a **state-hunting** pass, not just an anomaly-hunting one.
The useful observations are:

- whether the LCD stays clear / non-`CONNECT NOT ACT`
- whether a run only holds that state for group 1, or for the whole script
- whether any new structured families appear

#### HE38a: broaden low-band sweep `0x20-0x3F @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x20-0x3F --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he38-direct-20d0-cmd20-3f.txt
```

Purpose:

- check whether the semi-awake surface extends past `0x1F`
- keep the same state/value pair that has worked best so far

#### HE38b: broaden low-band sweep `0x40-0x5F @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x40-0x5F --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he38-direct-20d0-cmd40-5f.txt
```

Purpose:

- look for a second semi-awake band elsewhere in command space

#### HE38c: baseline control `0x20-0x3F @ 00 80`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x20-0x3F --states 0x00 --values 0x80 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he38-direct-0080-cmd20-3f-control.txt
```

Purpose:

- compare the same command region under the older baseline payload
- separate "command region effect" from "`20 D0` payload effect"

#### HE38d: alternate promising payload `0x20-0x3F @ 40 30`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x20-0x3F --states 0x40 --values 0x30 --settle 3.0 --after-each 0.8 --after 2.0 --pause-on-anomaly --log captures/he38-direct-4030-cmd20-3f.txt
```

Purpose:

- test whether this "holds panel clear" effect is unique to `20 D0`
- `40 30` is the other payload pair most worth cross-checking here

#### HE38e: semi-awake candidate repeat check

If any HE38 sweep appears to hold the panel clear for most of the run, repeat
just the first good-looking candidate frame on its own:

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "<candidate frame>" --repeat 30 --interval 0.60 --delay 3 --after 3 --frame-size 0 --log captures/he38-repeat-<candidate>.txt
```

Use this only for candidates that look good both:

- serially, and
- on the LCD/panel state

#### Recommended order

1. `HE38a` `0x20-0x3F @ 20 D0`
2. `HE38c` baseline control `0x20-0x3F @ 00 80`
3. `HE38b` `0x40-0x5F @ 20 D0`
4. `HE38d` `0x20-0x3F @ 40 30`
5. `HE38e` repeat any good-looking candidates

Interpretation guide:

- if `20 D0` keeps finding wider semi-awake bands while `00 80` does not, then
  the payload pair is doing real mode/session work
- if `0x20-0x3F` behaves like `0x00-0x1F`, then we are looking at a much larger
  maintained surface than we first thought
- if only a few sparse candidates hold the panel clear, then the maintained
  background layer may be a selected subset rather than a continuous command map

### HE38 Result: No-Pause Semi-Awake Hunting

Additional uninterrupted capture files present:

- `captures/he38-direct-20d0-cmd20-3f-nopause.txt`
- `captures/he38-direct-0080-cmd20-3f-control-nopause.txt`
- `captures/he38-direct-20d0-cmd40-5f-nopause.txt`

Panel-side observation:

- these uninterrupted reruns were stopped manually at roughly the point where
  the panel lost its "alive"/clear state

That means these runs are useful primarily as **semi-awake duration probes**, not
as complete command maps.

#### `0x20-0x3F @ 20 D0`

Observed before manual stop:

- `0x21` -> `07 80 48 24 DD 6C`

Read:

- the patterned `20 D0` surface definitely extends upward into the `0x20` band
- this is consistent with the earlier low-band `4x 24 DD 6x` family structure

#### `0x20-0x3F @ 00 80` control

Observed before manual stop:

- `0x29` -> `07 80 4A 20 D8 6F`

Read:

- the same command region is still live under baseline `00 80`
- but the family shape stays on the older `20 D8` style rather than the newer
  `24 DD` style

#### `0x40-0x5F @ 20 D0`

Observed before manual stop:

- `0x41` -> `07 80 50 24 DD 74`
- `0x42` -> repeated `07 80 50 24 DD 74`
- `0x43` -> `07 80 50 24 DD 74`

Read:

- this is the strongest new semi-awake lead from HE38
- the `0x40` command band under `20 D0` appears to open a neighboring
  `0x50 24 DD 74` family
- and it did so early enough in the run to matter before manual stop

Current best interpretation after HE38:

- `20 D0` remains the more interesting semi-awake payload
- not because baseline `00 80` is dead, but because `20 D0` keeps shifting the
  family surface into coherent `24 DD`-style siblings
- the semi-awake-maintenance surface is now plausibly broader than just the
  original low band:
  - `0x00-0x1F`
  - `0x20-0x3F`
  - and likely at least part of `0x40-0x5F`

The best next narrow branch is now:

- treat `0x41-0x43 @ 20 D0` as a new maintained-background candidate set
- and compare that `0x50 24 DD 74` behavior against the earlier `0x40 24 DD 64`
  / `0x48 24 DD 6C` bands

### HE38 Cross-Check: What The Paused Runs Still Taught Us

The paused HE38 runs are not the right source for "how long did the panel stay
alive?", but they were still very useful for **family mapping**.

#### `0x20-0x3F @ 20 D0` family structure

The paused run shows a very clean patterned surface:

- `0x21` -> `07 80 48 24 DD 6C`
- `0x25` -> `07 80 49 24 DD 6D`
- `0x29` -> `07 80 4A 24 DD 6E`
- `0x2D` -> `07 80 4B 24 DD 6F`
- `0x31` -> `07 80 4C 24 DD 68`
- `0x35` -> `07 80 4D 24 DD 69`
- `0x39` -> `07 80 4E 24 DD 6A`
- `0x3D` -> `07 80 4F 24 DD 6B`

Interleaved sibling families also appear:

- `0x23` -> `07 80 24 12 97 7C`
- `0x2B` -> `07 80 25 12 17 FD`
- `0x33` -> `07 80 26 12 97 7E`
- `0x3B` -> `07 80 27 12 17 FF`
- `0x27` -> `07 80 12 09 D7 11`
- `0x37` -> `07 80 13 09 D7 10`

So this is not just "some activity in the `0x20` band"; it is a strongly
ordered mapped surface.

#### `0x40-0x5F @ 20 D0` family structure

The paused run also maps a second coherent band:

- `0x41` -> `07 80 50 24 DD 74`
- `0x45` -> `07 80 51 24 DD 75`
- `0x49` -> `07 80 52 24 DD 76`
- `0x4D` -> `07 80 53 24 DD 77`
- `0x59` -> `07 80 56 24 DD 72`
- `0x5D` -> `07 80 57 24 DD 73`

That is the strongest evidence yet that the `.. 24 DD ..` surface spans
multiple command bands, not just the original low region.

#### `0x20-0x3F @ 00 80` control structure

The baseline payload maps the same general region, but with the older family
style:

- `0x29` -> `07 80 4A 20 D8 6F`
- `0x2D` -> `07 80 4B 20 D8 6E`
- `0x31` -> `07 80 4C 20 D8 69`
- `0x35` -> `07 80 4D 20 D8 68`
- `0x39` -> `07 80 4E 20 D8 6B`
- `0x3D` -> `07 80 4F 20 D8 6A`

with interleaved siblings like:

- `0x33` -> `07 80 26 10 2C C7`
- `0x3B` -> `07 80 27 10 2C C6`

That reinforces the idea that `20 D0` is not inventing a new command region
from scratch; it is shifting an existing mapped surface into a different family
space.

#### `0x20-0x3F @ 40 30` alternate-payload structure

The `40 30` paused run gives a third aligned surface:

- `0x21` -> `07 80 48 28 D3 6E`
- `0x25` -> `07 80 49 28 D3 6F`
- `0x29` -> `07 80 4A 28 D3 6C`
- `0x2D` -> `07 80 4B 28 D3 6D`
- `0x31` -> `07 80 4C 28 D3 6A`
- `0x35` -> `07 80 4D 28 D3 6B`
- `0x39` -> `07 80 4E 28 D3 68`
- `0x3D` -> `07 80 4F 28 D3 69`

with matching interleaved families:

- `0x23` -> `07 80 24 14 4A A7`
- `0x2B` -> `07 80 25 14 0A E6`
- `0x33` -> `07 80 26 14 CA 25`
- `0x3B` -> `07 80 27 14 8A 64`
- `0x27` -> `07 80 12 0A 6A AF`
- `0x37` -> `07 80 13 0A EA 2E`

Current read after including the paused runs:

- the command surface is looking increasingly like a **mapped lattice**
- the command byte selects a row/slot
- the host payload pair (`00 80`, `20 D0`, `40 30`) shifts the response family
  across parallel surfaces
- so the "semi-awake" question is probably about which of these surfaces is the
  right maintained background class, not about whether the map exists at all

### HE39: Narrow In On The `0x50 24 DD 74` Band

Working idea: the `0x40-0x5F @ 20 D0` region may be a stronger maintained
background class than the earlier low-band `0x40 24 DD 64` region.

Known lead so far:

- `0x41 @ 20 D0` -> `07 80 50 24 DD 74`
- paused run also showed:
  - `0x45` -> `07 80 51 24 DD 75`
  - `0x49` -> `07 80 52 24 DD 76`
  - `0x4D` -> `07 80 53 24 DD 77`
  - `0x59` -> `07 80 56 24 DD 72`
  - `0x5D` -> `07 80 57 24 DD 73`

Goal: see whether this band:

- holds the panel clear longer,
- behaves better as a maintained subset,
- or opens anything richer than the earlier `0x40 24 DD 64` surface.

#### HE39a: repeat only `0x41 @ 20 D0`

Target frame:

- `00 00 41 20 D0 EB`

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 41 20 D0 EB" --repeat 30 --interval 0.60 --delay 3 --after 3 --frame-size 0 --log captures/he39-repeat-cmd41-20d0.txt
```

#### HE39b: repeat only `0x45 @ 20 D0`

Target frame:

- `00 00 45 20 D0 EF`

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 45 20 D0 EF" --repeat 30 --interval 0.60 --delay 3 --after 3 --frame-size 0 --log captures/he39-repeat-cmd45-20d0.txt
```

#### HE39c: repeat only `0x49 @ 20 D0`

Target frame:

- `00 00 49 20 D0 E3`

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 49 20 D0 E3" --repeat 30 --interval 0.60 --delay 3 --after 3 --frame-size 0 --log captures/he39-repeat-cmd49-20d0.txt
```

#### HE39d: small `0x50`-band mixer

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 41 20 D0 EB" --frame "00 00 45 20 D0 EF" --frame "00 00 49 20 D0 E3" --repeat 10 --frame-interval 0.60 --read-after-frame 0.20 --read-after-group 0.80 --log captures/he39-mixer-41-45-49-20d0.txt
```

#### HE39e: wide `0x50`-band mixer

```powershell
python scripts/serial_sequence_probe.py --port COM5 --prompt --prompt-screen --pre-read 2.0 --frame "00 00 41 20 D0 EB" --frame "00 00 45 20 D0 EF" --frame "00 00 49 20 D0 E3" --frame "00 00 4D 20 D0 E7" --frame "00 00 59 20 D0 F3" --frame "00 00 5D 20 D0 F7" --repeat 6 --frame-interval 0.60 --read-after-frame 0.20 --read-after-group 1.0 --log captures/he39-mixer-50band-wide-20d0.txt
```

#### HE39f: control repeat from the older low-band lead

Target frame:

- `00 00 01 20 D0 AB`

```powershell
python scripts/serial_probe_response.py --port COM5 --tx-frame "00 00 01 20 D0 AB" --repeat 30 --interval 0.60 --delay 3 --after 3 --frame-size 0 --log captures/he39-repeat-cmd01-20d0-control.txt
```

#### Recommended order

1. `HE39a` repeat `0x41`
2. `HE39f` control repeat `0x01`
3. `HE39b` repeat `0x45`
4. `HE39c` repeat `0x49`
5. `HE39d` small `41/45/49` mixer
6. `HE39e` wide `0x50`-band mixer

Interpretation guide:

- if `0x41/0x45/0x49` hold the panel clear longer than `0x01`, the `0x50` band
  is probably a better maintained-background candidate
- if the mixer behaves better than any one frame, that supports a small
  recurring in-family scan model
- if everything behaves the same as `0x01`, then this is probably just another
  parallel mapped surface, not a privileged wake-adjacent one

#### HE39 results

Observed panel behavior across all HE39 runs:

- all runs kept the panel in the clear / semi-awake state for the duration of
  the script
- none produced a fuller wake-up beyond that state

Single-frame repeat results:

- `HE39a` repeating `00 00 41 20 D0 EB`:
  - first send: heartbeat only
  - second send: opens `07 80 50 24 DD 74`
  - several copies of that family then drain out
  - later repeats stay heartbeat-only
- `HE39b` repeating `00 00 45 20 D0 EF`:
  - first send: heartbeat only
  - second send: opens `07 80 51 24 DD 75`
  - then returns to heartbeat-only behavior
- `HE39c` repeating `00 00 49 20 D0 E3`:
  - first send: heartbeat only
  - second send: opens `07 80 52 24 DD 76`
  - several copies drain out, then heartbeat only
- `HE39f` control repeating `00 00 01 20 D0 AB`:
  - same overall pattern as before
  - second send opens `07 80 40 24 DD 64`
  - later repeats are heartbeat-only

Mixer results:

- `HE39d` small `41/45/49` mixer:
  - group 1 only:
    - `0x41`: no RX
    - `0x45`: opens `07 80 51 24 DD 75`
    - `0x49`: drains more `07 80 51 24 DD 75`
  - later groups are heartbeat-compatible only
- `HE39e` wide `41/45/49/4D/59/5D` mixer:
  - same practical pattern as the small mixer
  - group 1 opens on `0x45` into `07 80 51 24 DD 75`
  - later `0x49/4D/59/5D` frames do not open additional stages
  - later groups are heartbeat-compatible only

Takeaways:

- the `0x50`-band is real and coherent:
  - `0x41` -> `07 80 50 24 DD 74`
  - `0x45` -> `07 80 51 24 DD 75`
  - `0x49` -> `07 80 52 24 DD 76`
- but it behaves serially just like the older low-band `0x40 24 DD 64` lead:
  - an early one-shot family burst
  - then flat heartbeat-compatible maintenance
- so the "keeps the panel alive" effect is broader than one specific command
  slot inside the `20 D0` surface
- the mixers did not outperform the simpler repeats
- current best read: this is another maintained-background surface, not yet a
  privileged wake-up band

### HE40: Completion Sweeps For Untouched Areas

Goal: do a more systematic "dot the i's and cross the t's" pass over the
largest command-space areas we still have not really touched.

Important mode choice for this ladder:

- **do not use `--pause-on-anomaly`**
- these are coverage runs first
- we want them to complete and leave a clean map, even if that means the panel
  falls out of the semi-awake state partway through

Primary untouched areas from the current map:

- `0x60-0xFF @ 20 D0`
- most of the command space above the sampled `0x20-0x3F @ 40 30` pass
- most nonzero host prefix combinations on a known-live slot

#### Phase 1: extend the `20 D0` surface through the upper command space

#### HE40a: `0x60-0x7F @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x60-0x7F --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-direct-20d0-cmd60-7f.txt
```

#### HE40b: `0x80-0x9F @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x80-0x9F --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-direct-20d0-cmd80-9f.txt
```

#### HE40c: `0xA0-0xBF @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0xA0-0xBF --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-direct-20d0-cmda0-bf.txt
```

#### HE40d: `0xC0-0xDF @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0xC0-0xDF --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-direct-20d0-cmdc0-df.txt
```

#### HE40e: `0xE0-0xFF @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0xE0-0xFF --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-direct-20d0-cmde0-ff.txt
```

#### Phase 2: broaden the `40 30` parallel surface

#### HE40f: `0x40-0x5F @ 40 30`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x40-0x5F --states 0x40 --values 0x30 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-direct-4030-cmd40-5f.txt
```

#### HE40g: `0x60-0x7F @ 40 30`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x60-0x7F --states 0x40 --values 0x30 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-direct-4030-cmd60-7f.txt
```

#### HE40h: `0x00-0x1F @ 40 30`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00 --commands 0x00-0x1F --states 0x40 --values 0x30 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-direct-4030-cmd00-1f.txt
```

#### Phase 3: small host-prefix completion pass on a known live slot

These are not full protocol sweeps. They are a practical way to check whether
nonzero host prefixes hide another aligned surface on a command we already know
is live.

Known live slot chosen for prefix probing:

- `cmd=0x21`, `state/value=20 D0`

#### HE40i: `prefix2=0x00-0x0F` on `cmd=0x21 @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00 --prefix2s 0x00-0x0F --commands 0x21 --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-prefix2-00-0f-cmd21-20d0.txt
```

#### HE40j: `prefix1=0x00-0x0F` on `cmd=0x21 @ 20 D0`

```powershell
python scripts/serial_direct_response_sweep.py --port COM5 --prefix1s 0x00-0x0F --prefix2s 0x00 --commands 0x21 --states 0x20 --values 0xD0 --settle 3.0 --after-each 0.8 --after 2.0 --log captures/he40-prefix1-00-0f-cmd21-20d0.txt
```

#### Recommended order

1. `HE40a` `0x60-0x7F @ 20 D0`
2. `HE40g` `0x60-0x7F @ 40 30`
3. `HE40b` `0x80-0x9F @ 20 D0`
4. `HE40f` `0x40-0x5F @ 40 30`
5. `HE40c` `0xA0-0xBF @ 20 D0`
6. `HE40d` `0xC0-0xDF @ 20 D0`
7. `HE40e` `0xE0-0xFF @ 20 D0`
8. `HE40h` `0x00-0x1F @ 40 30`
9. `HE40i` prefix2 sweep
10. `HE40j` prefix1 sweep

Interpretation guide:

- if `20 D0` continues to produce aligned families well past `0x5F`, then the
  maintained-background surface is much broader than we first thought
- if `40 30` mirrors the same bands with a parallel surface, that strengthens
  the "payload pair selects surface, command selects slot" model
- if the prefix sweeps stay flat, then prefixes are probably less important
  than command and payload pair for the surfaces we have been mapping
- if one prefix range opens a new aligned family on `cmd=0x21`, that becomes a
  very strong next branch
