# PT2 Protocol Working Notes

This document is the current working model for the serial protocol spoken by the Sony RCP-TX7 panel ROM.

A later RCP manual mentions a "PT2 compatibility mode" for controlling the same CCU family this panel was made for. We are using "PT2" here as a practical label for this six-byte SCI1 protocol model. It is not yet a claim that every field name matches Sony's official PT2 terminology.

Focused companion notes:

- [PT2 Copy State Machine](pt2-copy-state-machine.md)
- [PT2 Menu State Machine](pt2-menu-state-machine.md)
- [PT2 Shutter Display Trace](pt2-shutter-display-trace.md)

## Current High-Confidence Facts

- The real bench link is `38400 8E1`, not `38400 8N1`.
- The ROM uses H8/536 SCI1 through the MAX202 RS232 transceiver.
- Frames are six bytes long.
- The checksum is `0x5A XOR byte0 XOR byte1 XOR byte2 XOR byte3 XOR byte4`.
- The ROM validates the checksum before normal command dispatch.
- The first byte is decoded as `command = byte0 & 0x07`.
- Bytes 1 and 2 encode a logical selector.
- Bytes 3 and 4 are a 16-bit value for write-style commands.
- The protocol is stateful. Some commands only work while a continuation/session latch is live.
- `CONNECT:NOT ACT` is recoverable without a power cycle.
- `CONNECT:  OK` can be reached on real hardware after using the correct `8E1` serial format.

## Hardware Path

SCI1 is the external serial path:

- H8/536 pin 66, `P95/TXD`, goes to MAX202 pin 11.
- MAX202 pin 12 goes to H8/536 pin 67, `P96/RXD`.
- The ROM initializes SCI1 as async 8-bit, even parity, 1 stop.

ROM evidence:

- `build/rom_decompiled.asm:437`: `SCI1_SMR = H'24`.
- `build/rom_decompiled.asm:438`: `SCI1_SCR = H'3C`.
- `build/rom_decompiled.asm:439`: `SCI1_BRR = H'07`.

Bench implication:

- Use `38400 8E1` for all real-device captures and probes.
- Old `38400 8N1` captures mostly exercised parity/error handling and retry echoes. Do not assign normal protocol meaning to old `8N1` `07...` frames until they are reproduced under `8E1`.

## Frame Format

Working host/RCP frame layout:

```text
byte0  command byte; ROM uses byte0 & 0x07
byte1  selector page/high bits; byte1.7 is rejected by normal handlers
byte2  selector low byte
byte3  value high byte
byte4  value low byte
byte5  checksum = 0x5A XOR byte0..byte4
```

Examples:

```text
00 00 00 80 80 5A  ; command 0, selector 0x000, value 0x8080
01 00 00 00 00 5B  ; command 1, read selector 0x000
04 00 00 80 00 DE  ; command 4 shape, selector 0x000, value high 0x80
07 00 00 00 00 5D  ; command 7, repeat previous finalized TX frame
```

## Selector Decode

The ROM builds a raw selector from bytes 1 and 2, then maps it through `loc_622B`.

Known decode:

| byte1 page | byte2 range | selector |
| --- | --- | --- |
| page 0, or pages 4-7 | `00-7F` | `0x000 + byte2` |
| page 1 | `00-FF` | `0x080 + byte2` |
| page 2 | `00-7F` | `0x180 + byte2` |
| page 3 | any | `0x1FF` fallback |
| invalid range | any | `0x1FF` fallback |

Important caveats:

- `byte1.7` is rejected before normal command handling.
- Frames like `01 80 40 ...` may look like a selector encoding, but the normal command path rejects `byte1.7`.
- Pages 4-7 appear to alias the page-0 path when the low byte is in range.

## Checksum

Checksum formula:

```python
checksum = 0x5A
for b in frame[:5]:
    checksum ^= b
```

The ROM path:

- RXI captures bytes into `F868-F86D`.
- Main-loop processing copies them to `F860-F865`.
- Physical error latch `FAA4.7` is checked before checksum dispatch.
- Checksum mismatch enters the retry/error path.
- Valid checksum clears retry counter `FAA6`, decodes selector, and dispatches on `byte0 & 0x07`.

Key ROM areas:

- RXI/ERI capture: `BB57`, `BB67`.
- Validation and checksum: `BBAB-BBF0`.
- Selector decode call: `BC01 -> 622B`.
- Command dispatch: `BC08-BC67`.

## Command Model

The biggest protocol lesson is that the command set has two modes:

- Initial dispatcher: active while `FAA2 == 0`.
- Continuation dispatcher: active while `FAA2 != 0`.

That means command numbers are not globally meaningful. Command `4`, `5`, and `6` are continuation-path commands, not normal idle commands.

| Command | Path | Current meaning | Response |
| --- | --- | --- | --- |
| `0x00` | initial | Set primary/current value, queue selector processing | Immediate `0x04` echo-style response |
| `0x01` | initial | Read primary value table | Immediate `0x04` readback response |
| `0x02` | initial | Quiet clear/no-op style command | No immediate response seen in ROM |
| `0x04` | continuation | Set/update value without immediate response | Usually no immediate response |
| `0x05` | continuation | ACK/session-clear/pending handling | Usually no immediate response |
| `0x06` | continuation | Set secondary value table | Usually no immediate response |
| `0x07` | both | Retransmit previous finalized TX frame | Repeats last TX frame |

## Command Details

### Command 0: Initial Set Value

Path: `BC69`.

Conditions:

- Valid checksum.
- `FAA2 == 0`.
- `byte1.7 == 0`.

Effects:

- Writes value into `E000 + 2*selector`.
- Writes value into `E800 + 2*selector`.
- Sets dirty flag bit 7 in `EC00 + selector`.
- Calls `BE70`, which appends the selector into the `F970` selector-processing queue.
- Sends an immediate `0x04` response through `BA26`.

Selector-zero special case:

- For selector `0x000`, the low byte is forced to `0x80`.
- This makes selector-zero writes look like `xx80`, not arbitrary `xxLL`.

Important candidate:

```text
00 00 00 80 80 5A  ; selector 0 = 0x8080, strongest CONNECT OK seed
```

### Command 1: Read Value

Path: `BCD7`.

Effects:

- Reads `E000 + 2*selector`.
- Stages a `0x04` response.
- Clears `FAA2.7`.

Useful readback examples:

```text
01 00 00 00 00 5B  ; read selector 0x000
01 00 40 00 00 1B  ; read selector 0x040
01 01 76 00 00 2C  ; read selector 0x0F6
```

Bench implication:

- Command 1 verifies table state.
- It is not an ACK and does not enter continuation handling.
- Command 7 after command 1 can repeat the last finalized readback.

### Command 2: Initial Clear/No-Op Candidate

Path: `BD04`.

Effects:

- Clears `FAA2.7`.
- Returns without staging an obvious response.

Meaning is still unclear. Treat it as a quiet/session-control candidate, not as a data write.

### Command 4: Continuation Set Value

Path: `BD0E`.

Conditions:

- Valid checksum.
- `FAA2 != 0`.
- Command bit 2 set.
- `byte1.7 == 0`.

Effects:

- Writes value into `E000 + 2*selector`.
- Selector zero also updates `E800`.
- Nonzero selectors set dirty flag bit 7 in `EC00 + selector`.
- Can mirror/persist mapped nonzero selectors through the `F400`/EEPROM path when `F76E.7` allows it.
- If `FAA2.3` was set by a queued report, command 4 can advance `F9B5` to consume that report.
- Clears `FAA3` and `FAA2` before exit.

Known CONNECT test frames:

```text
04 00 00 40 00 1E
04 00 00 80 00 DE
04 00 00 C0 00 9E
```

ROM caveat:

- A standalone command 4 from a truly idle `FAA2 == 0` state should not reach `BD0E`.
- Bench evidence now proves the panel can still recover from `CONNECT:NOT ACT` without power cycling, so visible `NOT ACT` is not equivalent to "all serial continuation state is impossible".

### Command 5: Continuation ACK/Clear Candidate

Path: `BD80`.

Conditions:

- Continuation path only.

Effects:

- Usually no immediate response.
- Selectors `0x006C`, `0x006D`, and `0x006E` call `BE70`.
- If `F731.7` is set, selectors `0x006B`, `0x0096`, `0x0097`, `0x00C6`, and `0x00F8` clear `F731.7/F790.7`.
- If `FAA2.3` was set by a queued report, command 5 can advance `F9B5`.
- Clears `FAA3` and `FAA2` before exit.

Bench implication:

- Command 5 is not a generic always-live ACK.
- It only has ACK-like meaning when the continuation latch is live.

### Command 6: Continuation Secondary Set

Path: `BDDB`.

Effects:

- Writes value into `E400 + 2*selector`.
- Sets dirty flag bit 6 in `EC00 + selector`.
- Can advance queued-report state when `FAA2.3` is live.
- Clears `FAA3` and `FAA2`.

### Command 7: Repeat Previous TX

Path: `BE05`.

Effects:

- Copies the previous finalized TX frame back into staging.
- Sends it again through `BA26`.
- Works from initial and continuation paths.

Bench implication:

- Command 7 is useful as a "what did you last finalize?" probe.
- It does not prove a hidden continuation token by itself.

## RCP Transmit Frames

The TX side uses the same six-byte checksum model.

TX staging:

- `F850-F854`: staging bytes.
- `F858-F85C`: finalized bytes.
- `F85D`: computed checksum.
- `BA26`: finalizes and starts SCI1 TX.
- TXI sends bytes 1-5 after the first TDR write.

Known RCP-origin frames:

| Frame | Confidence | Current meaning |
| --- | --- | --- |
| `00 00 00 00 80 DA` | high | idle heartbeat / selector-zero report |
| `00 00 07 80 00 DD` | medium-high | observed CAM POWER button/report candidate |
| `00 00 15 80 00 CF` | medium-high | observed CALL on/report candidate |
| `00 00 15 00 00 4F` | medium-high | observed CALL off/report candidate |
| `02 00 02 00 00 5A` | medium | emulator CONNECT OK path response candidate |

Heartbeat:

- Idle frame: `00 00 00 00 80 DA`.
- Observed cadence: about 700 ms.
- ROM path: `loc_4067` enqueues selector 0, `loc_BAF2/BB08` dequeues it, `BB1C/BB20/BB2B` stages TX bytes, `BA26` emits the frame.
- FRT2 timing model: `TCR=H'02`, `OCRA=H'7A12`, modeled as a 100 ms tick at 10 MHz; `F9C4=0x07` gives about 700 ms post-send heartbeat delay.

## Retry/Error 07 Frames

`07...` frames are easy to misread.

The ROM can generate a `0x07` retry/error echo when:

- A physical RX error occurs, or
- A checksum mismatch occurs, and
- `FAA5.7` is set, and
- Retry count `FAA6` is below two.

Path:

- `BE29` retry gate.
- `BE4D` stages `F850=0x07`.
- `F851-F854` copy host `RX[1:4]`.
- `BA26` sends it.

Bench implication:

- A visible `07...` frame is not automatically a normal status report or ACK.
- Old `8N1` captures produced many misleading `07...` frames because parity errors exercised this path.

## Table Model

The ROM behaves like a selector-indexed state machine. The CCU likely seeds values, and the RCP updates LCD/lamp/control behavior from those values.

| Table | Range | Role |
| --- | --- | --- |
| Primary value table | `E000-E3FF` | Command 0/4 writes, command 1 reads |
| Secondary value table | `E400-E7FF` | Command 6 writes |
| Current/report table | `E800-EBFF` | Used when RCP builds outbound report frames |
| Dirty/flag table | `EC00-EFFF` | Per-selector flags, bit7 for primary writes, bit6 for secondary writes |
| EEPROM/shadow | `F400-F4FF` | Optional mapped persistence/config surface |

Important details:

- Selector zero is special in command 0 and command 4.
- Command 0 writes both `E000` and `E800`.
- Command 4 writes `E800` only for selector zero in the current ROM evidence.
- `BAF2` reads `E800 + 2*selector` when building autonomous RCP reports.
- `BE70/F970` is a selector-processing queue.
- `3E54/F870` is a separate serial-visible report queue.

Do not mix up:

- `F970`: "process this selector internally".
- `F870`: "send this selector/report over serial".

## State And Queues

Important RAM/state bytes:

| Address | Working name | Meaning |
| --- | --- | --- |
| `FAA2.7` | RX command in progress | Set on initial parse, cleared on exits |
| `FAA2.3` | queued report continuation needed | Set after autonomous report send |
| `FAA3.7` | pending resend mask | Set after queued report send |
| `FAA4.7` | RX physical error latch | Set by SCI1 ERI |
| `FAA5.7` | RX session gate | Set while `F9C5` is alive after complete RX |
| `FAA6` | retry counter | Limits retry/error echoes |
| `F9C1` | inter-byte timeout | Reloaded on RXI |
| `F9C3` | RX byte count | Counts up to six |
| `F9C4` | heartbeat/report cadence gate | Controls idle heartbeat enqueue |
| `F9C5` | RX/session timeout | Loaded with `0x14` after full RX frame |
| `F9B0/F9B5` | serial report queue cursors | Drive `F870`/`BAF2` |
| `F9B4/F9B9` | selector-processing queue cursors | Drive `F970`/`2806` |

Session expiry:

- A complete six-byte RX frame loads `F9C5=0x14`.
- FRT2 decrements `F9C5`.
- When `F9C5` reaches zero, `loc_3FEF` can clear queues/session state.
- If `FAA5.7` was set, expiry calls `loc_400C`.
- `loc_400C` clears connection/session RAM and refreshes inactive display state.

This explains why random traffic tends to settle back to `CONNECT:NOT ACT`.

## CONNECT State

`CONNECT` strings are built through the LCD driver, not received as literal serial text.

Known LCD path:

```text
FAF0-FAFF line buffer -> 3ECC -> 3F28 -> 3F40 -> F200/F201 LCD ports
```

ROM/emulator findings:

- Boot/no-active-session display can show `CONNECT:NOT ACT`.
- Direct emulator entry at `loc_2CB9` with `E000[0]=0x8080` and `F730=0` reaches `CONNECT:  OK`.
- Queued selector-zero path reaches OK when:
  - `F970[0]=0`
  - `F9B9=0`
  - `F9B4=1`
  - `E000[0]=0x8080`
  - `F730=0`
- Selector zero dispatches through the `28A6` jump table into the CONNECT handler window.

Bench findings:

- Correct `8E1` serial format made the CONNECT path work on real hardware.
- Real hardware can recover from `CONNECT:NOT ACT` to `CONNECT:  OK` without a power cycle.
- Successful active-looking state included:
  - `CONNECT:  OK`
  - CAM POWER lamp illuminated
  - numeric readouts illuminated as `----`
- Matrix tests show that cadence matters:
  - `40 -> 80 -> C0` with 10 ms, 50 ms, or 150 ms inter-frame gaps stayed at `CONNECT:NOT ACT` after a fresh power-cycle test.
  - The same order with 700 ms and 1.5 s inter-frame gaps produced `CONNECT:  OK` before falling back to `CONNECT:NOT ACT`.
  - At 700 ms gaps, no single frame worked by itself.
  - At 700 ms gaps, every tested two-frame pair worked: `40 -> 80`, `80 -> C0`, and `40 -> C0`.
  - A repeated identical pair also worked: `80 -> 80` at about 700 ms produced eight `02 00 02 00 00 5A` OK-path responses, then heartbeat traffic resumed.
  - The no-power-cycle recovery test from an already visible `CONNECT:NOT ACT` state produced repeated `02 00 02 00 00 5A` OK-path responses, then returned to heartbeat traffic.

Current interpretation:

- `CONNECT:NOT ACT` is a normal no-active-session/cleared-state display, not a terminal latch.
- `CONNECT:  OK` is table/state driven, probably selector-zero active/connect state.
- `0x8080` at selector zero is the strongest known active/connect value.
- The panel likely expects the CCU to consume/ACK report-queue frames and keep seeding or refreshing state after entering OK.
- The working fake-CCU sequence is probably not "three frames as fast as possible"; it appears to need CCU-like cadence or a live session window, roughly on the heartbeat/report timescale.
- A single selector-zero continuation-shaped frame is insufficient in the current tests; two selector-zero writes at the working cadence are enough. They do not need to carry different values, because `80 -> 80` also worked.

ROM report-source update:

- The active `02/01 ...` frames seen during CONNECT OK attempts are best modeled as `F870 -> BAF2 -> BA26` report-queue transmissions.
- `BAF2` dequeues a report word, encodes the first three TX bytes, reads the payload from `E800 + 2*selector`, and `BA26` appends the `0x5A` XOR checksum.
- After sending a queued report, the ROM sets `FAA2.3` and `FAA3.7`; command `4`, `5`, or `6` can then consume/advance the report only while that continuation latch is live.
- This makes a reactive fake-CCU test more valuable than another blind fixed-delay matrix: recover to OK, wait for the first active report, then send one candidate continuation/ACK frame.

## Candidate CCU Seed Values

These are syntactically valid host frames produced from ROM table mining. Use them as candidate fake-CCU state seeds, not as final protocol truth.

| Selector | Candidate value | Frame | Why it matters |
| --- | --- | --- | --- |
| `0x000` | `0x8080` | `00 00 00 80 80 5A` | strongest CONNECT OK seed |
| `0x003` | `0x8000` | `00 00 03 80 00 D9` | ROM default enabled bit candidate |
| `0x040` | `0xFFFF` | `00 00 40 FF FF 1A` | ROM default all-ones/status block candidate |
| `0x040` | `0x4030` | `00 00 40 40 30 6A` | bench-touched 0x40 family value |
| `0x0F6` | `0x2000` | `00 01 76 20 00 0D` | `loc_48FA` tests `E1EC.13` and can enqueue report `0x00F6` |

Readbacks:

```text
01 00 00 00 00 5B  ; selector 0x000
01 00 03 00 00 58  ; selector 0x003
01 00 40 00 00 1B  ; selector 0x040
01 01 76 00 00 2C  ; selector 0x0F6
```

## Observed Button/Panel Reports

Before the protocol format was corrected, the RCP appeared to emit only a few report families by itself:

```text
00 00 00 00 80 DA  ; heartbeat
00 00 07 80 00 DD  ; CAM POWER candidate
00 00 15 80 00 CF  ; CALL on candidate
00 00 15 00 00 4F  ; CALL off candidate
```

Current interpretation:

- The RCP can report some panel events.
- Many other controls probably need CCU-provided state before they become reportable or meaningful.
- The CCU likely streams display/lamp/readout state to the RCP, while the RCP reports operator changes back.

## EEPROM And Board Config

The P9 bus is not the external PT2 serial link. It is a bit-banged EEPROM/config path:

- H8 pin 62, `P91`, reaches X24164 pin 6 `SCL`.
- H8 pin 68, `P97`, reaches shared X24164 pin 5 `SDA`.

ROM findings:

- `loc_40BB` checks `P7DR.7` and `F402 == H'6B6F` before deciding whether to default EEPROM/shadow tables.
- `loc_4103` writes ROM default words through `BFE0`.
- `loc_41D2` reads sixteen 8-byte records into `F7B0-F82F`.
- Command 4 can persist mapped serial table writes when `F76E.7` is set.

Current interpretation:

- EEPROM stores panel/config/default state.
- It can affect startup and option behavior.
- After the `8E1` discovery, EEPROM is less likely to be the fundamental reason CONNECT failed, but it can still influence which selectors/features are active.

## Known Useful Bench Commands

Minimal CONNECT sequence runner:

```powershell
.\.venv\Scripts\python.exe scripts\bench_connect_lcd_sequence.py --port COM5 --relay-port COM6 --parity E --prompt-screen
```

Recover from `CONNECT:NOT ACT` without power cycling:

```powershell
.\.venv\Scripts\python.exe scripts\bench_connect_lcd_sequence.py --port COM5 --relay-port COM6 --no-power-cycle --parity E --prompt-before-send --prompt-screen --post-sequence-read 10 --log captures\connect-notact-to-ok.txt
```

Run the reproducibility/minimization matrix:

```powershell
.\.venv\Scripts\python.exe scripts\connect_ok_matrix.py --suite minimal --parity E --prompt-observation --result-json captures\connect-ok-minimal-result.json
```

Test timing/cadence:

```powershell
.\.venv\Scripts\python.exe scripts\connect_ok_matrix.py --suite gap --parity E --prompt-observation --result-json captures\connect-ok-gap-result.json
```

Test whether OK is held:

```powershell
.\.venv\Scripts\python.exe scripts\connect_ok_matrix.py --suite hold --parity E --prompt-observation --result-json captures\connect-ok-hold-result.json
```

Sweep strong continuation/ACK candidates after recovering to `CONNECT: OK`:

```powershell
.\.venv\Scripts\python.exe scripts\connect_ok_advance_sweep.py --suite core --parity E --prompt-observation --result-json captures\connect-ok-advance-core-result.json
```

Candidate suites:

- `core`: `05 00 40 00 00 1F` pure command-5 report-consume candidate, then `04 00 00 80 00 DE` selector-zero refresh/consume candidate.
- `special`: command-5 `0x006C/0x006D/0x006E`, which call the ROM's `BE70` queue helper.
- `latch`: command-5 `0x006B/0x0096/0x0097/0x00C6/0x00F8`, which can clear the `F731/F790` latch bits when that path is live.
- `all`: all of the above.

Current matrix result summary:

```text
minimal suite at 150 ms gaps: all cases stayed CONNECT NOT ACT
gap suite at 10/50/150 ms: stayed CONNECT NOT ACT
gap suite at 700 ms and 1.5 s: CONNECT OK, then CONNECT NOT ACT
minimal suite at 700 ms gaps: singles stayed CONNECT NOT ACT; all pairs reached CONNECT OK then CONNECT NOT ACT
repeated 80 -> 80 at about 700 ms: CONNECT OK responses, then CONNECT NOT ACT
hold suite at 150 ms gaps: stayed CONNECT NOT ACT
no-power-cycle NOT ACT recovery: CONNECT OK responses observed, then heartbeat resumes
```

Advance sweep result summary:

```text
core suite: no new LCD behavior
all suite, ack-006C (05 00 6C 00 00 33): CONNECT OK, then LCD blank while CAM POWER lamp stayed on
all suite, ack-006D (05 00 6D 00 00 32): CONNECT OK, then COPY IN PROGRESS, then CONNECT NOT ACT
all suite, ack-006E/latch candidates: mostly OK then NOT ACT
fresh isolated ack-006D run: COPY path reproduced after a relay power-cycle
```

The `0x006D` copy path is now confirmed outside the earlier all-suite ordering caveat: `captures/connect-ok-advance-special-20260526-153339.txt` power-cycled the panel, waited for heartbeats, sent the two-frame `0x8080` CONNECT baseline, then sent only `05 00 6D 00 00 32`; the LCD again entered the copy path. The ROM strongly supports that these are real selector side effects:

- Command 5 compares `R5` against `0x006C`, `0x006D`, and `0x006E`; those selectors call `BE70` to append the selector to the `F970` processing queue.
- Selector `0x006C` dispatches to `H'2FAF`. Forced decoding shows it manipulates `F76E`, `F795`, `F797`, `F799`, can set display selector `F732=H'1904`, sets `FB02=H'14`, and calls the `48FA` display/report bridge.
- Selector `0x006D` dispatches to `H'3015`. Forced decoding shows it sets `F731.7`, loads `F798=H'C8`, sets `F795.6/F795.7`, sets display selector `F732=H'1903`, sets `FB02=H'64`, calls `48FA`, then sets `F76E.6`.
- The LCD dispatch for these states is now traced: `loc_48FA` reads the high byte at `F732`, so `F732=H'1903/H'1904` selects display page `0x19`, not direct page `0x03/0x04`. `493E[0x19] -> H'930A`; that page's local table at `H'931C` includes `H'9F6A` for `COPY` / `IN PROGRESS` and `H'9FDA` for `COPY` / `COMPLETED`. The low byte at `F733` is the substate selector: `0x03` is in-progress and `0x04` is completed.
- This makes the likely copy handshake: `0x006D` starts copy and sets the `F795.6/F795.7` in-progress flags; `0x006C` is the completion/exit sibling only when those flags are live. Sending `0x006C` alone can therefore blank or clear state instead of displaying `COPY COMPLETED`.
- Bench step-through confirmed the sequence model: `006C` alone produced `CONNECT OK -> blank`; `006D` alone produced `CONNECT OK -> COPY IN PROGRESS`; `006D` followed by `006C` after 250 ms produced a brief `COPY IN PROGRESS` then `COPY COMPLETED`; the same after 1.0 s and 1.5 s produced a longer `COPY IN PROGRESS` then `COPY COMPLETED`; after 2.0 s or 2.5 s it fell to `CONNECT:NOT ACT` instead of completing. Repeating `006D` before `006C` also completed successfully in the 2x and 3x repeat tests. A longer `006D` hold test kept `COPY IN PROGRESS` active for several seconds and then completed when `006C` arrived, while the same hold without `006C` timed out from `COPY IN PROGRESS` to `CONNECT:NOT ACT`. This points to `006D` as an in-progress/progress-window refresh selector and `006C` as the explicit completion/exit selector, not a stateless command pair.
- The FRT1 timer path decrements `F797` and `F798`; when either reaches zero, it clears `F731.7`. This matches the observed transient display modes falling back to `CONNECT:NOT ACT`.
- The string `COPY IN PROGRESS` is present in the ROM LCD resources, so the `006D` result is not a generic serial artifact.
- Manual interpretation: the RCP-TX7 operating manual describes `COPY IN PROGRESS` as the LCD state shown during the multi-camera `COPY TO SLAVES` data-transfer operation over the RS232C command-link system. During that state, all linked RCP units display the message and their buttons/knobs are locked until `COPY COMPLETED`. Therefore selector `0x006D` is best treated as entering a command-link copy/data-transfer state, not as a normal CCU connection ACK.

RCP-side OTHERS/COPY menu trace:

- The OTHERS menu is page `0x01`: `493E[0x01] -> H'631C`, local table `H'632E`.
- Local table entry 1 points to `H'6FF0`, the page that renders `OTHERS` / `COPY TO SLAVES`.
- The entry descriptor immediately before `H'6FF0` requires selector `0x0015` in the secondary table: `E400[0x0015] != 0`. Because command 6 writes `E400`, this is probably a CCU-provided feature/visibility bit, and command 6 must be sent in a live continuation/report window to have an effect.
- When the page sees `F770.2` set, it only follows the local copy-start branch if `F791.7` is already set. That branch sets `F76E.6`, `F795.7`, `F731.7`, `F798=H'C8`, `F711.7`, `F726=H'64`, calls `loc_5500`, then displays `COPY TO SLAVES`. This is the RCP-side equivalent of the serial `0x006D` copy-start effect.
- If `F770.2` is set while `F791.7` is clear, the ROM diverts through `H'704C` to a `SET RCP` / `MASTER` display path instead of starting copy. That makes `F791.7` a second, likely master/link/session gate for the physical COPY operation.
- The OTHERS root handler at `H'6EE4` also tests primary selector `E000[0x008F]` (`H'E11E`) bits 11 and 12, and uses them to set `F711.6` and `F711.4`. Bench isolation shows this selector is broader than an OTHERS gate: bit 11 makes the SHUTTER seven-segment display show observed `EUS`, probably the manual's `EVS` display, and bit 12 makes it show the literal letters `OFF`; either bit also illuminates the iris AUTO lamp.
- Candidate gate probes, not final protocol truth:

```text
00 01 0F 18 00 4C  ; command 0, set E000[0x008F] bits 11+12
06 00 15 00 01 48  ; command 6, set E400[0x0015] nonzero; requires live continuation
06 00 15 80 00 C9  ; command 6, alternate nonzero visibility value; requires live continuation
```

Read table state:

```powershell
.\.venv\Scripts\python.exe scripts\serial_table_dump.py --port COM5 --relay-port COM6 --start 0x000 --count 0x200 --parity E --log captures\table-read-8e1.txt
```

## Current Best Model Of Normal CCU/RCP Communication

The protocol looks like a shared selector table with stateful reporting:

1. CCU sends initial state seeds into selector tables, especially selector zero and status/display selectors.
2. RCP updates LCD, lamps, and numeric readouts from selector dispatch handlers.
3. RCP emits heartbeat/report frames from `E800` via the `F870 -> BAF2` report queue.
4. Host/CCU uses continuation commands to consume/ACK/update live reports while `FAA2/FAA3` gates are active.
5. If the CCU stops talking or session state expires, RCP clears volatile session state and returns to `CONNECT:NOT ACT`.

This fits the real panel behavior:

- Idle panel emits heartbeat.
- Correct fake-CCU traffic can wake it to `CONNECT:  OK`.
- Without richer CCU state, readouts illuminate but show placeholders like `----`.

## Active-State Local-Control Watch

Bench evidence now suggests `CONNECT: OK` is maintained by an ongoing CCU refresh stream, not by one magic wake frame. This creates a useful local-control test: keep the RCP in the active state and press/turn physical controls while logging device TX.

Two JSON scenarios are set up for that:

```powershell
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\active-control-report-watch-quiet.json --parity E --log captures\active-control-report-watch-quiet.txt --result-json captures\active-control-report-watch-quiet-result.json
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\active-control-report-watch-broad.json --parity E --log captures\active-control-report-watch-broad.txt --result-json captures\active-control-report-watch-broad-result.json
.\.venv\Scripts\python.exe scripts\serial_scenario.py scenarios\active-control-report-watch-gated.json --parity E --log captures\active-control-report-watch-gated.txt --result-json captures\active-control-report-watch-gated-result.json
```

The quiet scenario sends the selector-zero OK seed, then refreshes `E000[0x0093]=0x9020` every 0.60 s. This is the lowest-noise active hold that has already kept `CONNECT: OK` alive.

The broad scenario streams `E000[0x008F]=0x1800` and `E000[0x0093]=0xFFFF` every cycle. This is noisier, but may open more local-control/status gates.

The gated scenario first seeds candidate secondary-table feature bits with command 6:

```text
06 00 15 80 00 C9  ; E400[0x0015] OTHERS/COPY visibility candidate
06 01 0F 18 00 4A  ; E400[0x008F] shutter/OTHERS bits 11+12
06 01 13 FF FF 4E  ; E400[0x0093] broad local-report/status gate candidate
```

Then it streams `E000[0x008F]=0x1800` and `E000[0x0093]=0x90FF`. This is the better next watch if quiet/broad E000-only refreshes do not produce local-control TX.

After a run, summarize unexpected device frames with:

```powershell
.\.venv\Scripts\python.exe scripts\serial_scenario_unexpected.py captures\active-control-report-watch-quiet.txt --show-all
.\.venv\Scripts\python.exe scripts\serial_scenario_unexpected.py captures\active-control-report-watch-broad.txt --show-all
.\.venv\Scripts\python.exe scripts\serial_scenario_unexpected.py captures\active-control-report-watch-gated.txt --show-all
```

Expected refresh responses are ignored by default: heartbeat, table readbacks, and `02 00 02 00 00 5A`. Any remaining checksum-valid frame is a candidate local-control report. This is where physical button/dial reports should appear if the RCP only talks while the CCU keeps the selector tables active.

The unexpected-frame summarizer also labels known autonomous button frames so they are not mistaken for newly unlocked controls:

```text
00 00 15 80 00 CF  ; known CALL active report
00 00 15 00 00 4F  ; known CALL inactive report
00 00 07 80 00 DD  ; known CAM POWER report
```

First quiet/broad watch captures on 2026-05-26 showed no unexpected checksum-valid frames in the observed window:

```text
active-control-report-watch-quiet: 114 detected frames, all expected refresh/heartbeat/readback
active-control-report-watch-broad: 133 detected frames, all expected refresh/heartbeat/readback
```

Those runs stopped mid-scenario, so this is not a full negative proof. The practical interpretation is narrower: E000 active-state refresh alone did not make the pressed controls emit visible TX in the watched window. The gated command-6 version is the next ROM-supported test.

The first gated watch capture produced a new periodic active-state response:

```text
02 00 04 00 00 5C  ; gated 0x0004 transition candidate, seen twice
01 00 04 00 00 5F  ; gated 0x0004 active response candidate, seen 328 times
```

After treating those as expected gated refresh traffic, the capture had zero novel frames. A direct search found no known CALL/CAM POWER frames in that log. So the gated setup changed the session/status response shape, but it did not yet prove that physical controls beyond the already-known autonomous buttons are reporting.

## Lamp Selector Mapping

Bench lamp sweeps now prove that several panel outputs are directly driven by command-0 selector writes while `CONNECT: OK` is alive. The current detailed map lives in `docs/pt2-lamp-selector-map.md`.

Newest confirmed behavior:

- `lamp-known-button-selector-probe` made CAM, CALL, BARS, MASTER, and camera tally outputs flash individually.
- `0x0007 = 0x8000/0x0000` blinked CAM POWER in isolation.
- `0x0015 = 0x8000/0x0000` blinked CALL and red tally in isolation.
- Fresh-boot isolation maps `0x0013` to SLAVE, `0x0016` to green tally, and `0x0017` to BARS for the `0x8000/0x0000` value pair.
- Fresh-boot isolation maps `0x0092` to iris AUTO/OFF behavior, and maps both `0x00B9` and `0x0110` to KNEE AUTO behavior.
- A KNEE bit scan keeps the working bit assignment at `0x00B9.15` and `0x0110.15`; no lower-bit KNEE behavior has been reported yet.
- A KNEE precedence test argues against a simple OR: clearing `0x0110` turned KNEE AUTO off even after `0x00B9` had been set. Current best interpretation is that `0x0110.15` is the stronger live KNEE AUTO source, while `0x00B9.15` is related or transient.

## What Is Still Unknown

- The official PT2 names for commands and selectors.
- Which selectors drive every lamp and numeric display.
- Whether the CCU sends a periodic refresh stream after CONNECT OK.
- Exact hold time before OK falls back to NOT ACT, if no refresh traffic follows.
- Whether command 4 CONNECT success depends on an existing continuation latch, timing, or a side effect created by earlier frames.
- Whether the remaining emulator/bench mismatch is an SCI timing bug or a missing ROM/peripheral behavior. The emulator now has an optional 8E1 TX wire-timing mode; with it enabled, repeated `80` no longer immediately reaches `CONNECT:  OK`, which is closer to bench behavior but still needs calibration.
- How EEPROM option bits change selector behavior.
- Whether all old visible `07...` families can be reproduced under `8E1`.

## Next Best Refinements

1. Finish the CONNECT matrix runs:
   - rerun `hold` with 700 ms gaps to measure how long OK remains without refresh traffic.
2. Run the reactive advance sweep from OK and compare:
   - ACK-only `05 00 40 00 00 1F`
   - refresh/consume `04 00 00 80 00 DE`
   - command-5 special selectors `0x006C/0x006D/0x006E`
3. Test whether periodic report ACKs plus periodic `80` refreshes hold CONNECT OK.
4. Sweep emulator RX phase/gap with `--tx-wire-timing` and compare where `BD0E` is reached or missed.
5. Dump selector table state before and after CONNECT OK.
6. Seed selectors `0x003`, `0x040`, and `0x0F6` after selector-zero OK and watch lamps/readouts.
7. Mine selector dispatch handlers for known UI text terms: `IRIS`, `GAIN`, `SHUTTER`, `BARS`, `BLACK`, `CALL`, `AUTO`, `DIAG`.
8. Run the active-control watch scenarios and map any unexpected frames back to physical controls.

## Source Files And Reports

Generated evidence:

- `build/rom_decompiled.asm`
- `build/rom_rx_branch_trace.txt`
- `build/rom_ccu_seed_hints.txt`
- `build/rom_eeprom_layout.txt`
- `build/rom_table_xrefs.txt`
- `build/connect-state-search-ok.json`

Useful tools:

- `h8536_protocol_trace.py`
- `h8536_protocol_capture.py`
- `h8536_rx_branch_trace.py`
- `h8536_ccu_seed_hints.py`
- `h8536_emulator_rx_probe.py`
- `h8536_emulator_state_search.py`
- `scripts/bench_connect_lcd_sequence.py`
- `scripts/connect_ok_matrix.py`
- `scripts/connect_ok_advance_sweep.py`
- `scripts/serial_table_dump.py`
- `scripts/serial_scenario.py`
