# Tonight: one high-information Read Screen test

Diagnostic candidate only. This preparation did **not** merge main, restart/deploy
Gateway or flash hardware. The following release/restart/flash commands are for the
Mac operator **after the user returns and starts the physical test**. Do not point
production service at an investigation worktree. Review and release the diagnostic
commit into the approved Gateway checkout first; setting an environment variable on
the old main code alone cannot add Gateway trace support.

Full analysis / trace definitions: [diagnostics audit](speech-downlink-diagnostics-0.5.1.md).
Artifact identity and test results: [build record](speech-downlink-diagnostic-build.md).
Target: about 10 minutes **with USB cable, Mac flash tools and the prepared artifact
available**, not a cold ESP-IDF toolchain install or a long release review.

## 1. Fetch the exact diagnostic branch on Mac

In the existing Mac repository (path varies; preserve any user edits):

```bash
git status --short --branch
git fetch lighthouse@claw.pangtiao.xyz:/home/lighthouse/esp32-dev \
  codex/speech-downlink-diagnostics-0.5.1
git switch -c codex/speech-downlink-physical-0.5.1 FETCH_HEAD
git log --oneline cdaba7c..HEAD
```

The branch is available directly from Lighthouse; no GitHub push is required.
Do not run `update-flash.sh`; it intentionally retrieves production main's artifact.

## 2. Review / cherry-pick diagnostics

```bash
git diff --stat cdaba7c..HEAD
git diff cdaba7c..HEAD -- server/device_gateway/speech.py \
  server/device_gateway/speech_trace.py server/device_gateway/app.py \
  firmware/main/cloud_client.cpp boards/m5stack_stopwatch/audio_backend.cpp \
  boards/m5stack_stopwatch/board.cpp
```

Verify unchanged TCP defaults, Wi-Fi PS, queue 8, chunk 80ms, timeout 3000ms, I2S and
codec settings. Candidate implementation commit is `e12cad3cf9bfb900e376347da8c8d05eeeabf15e` (see the build record). Tonight's
operator must first fetch current main again and review changes since cdaba7c. Cherry-pick
that implementation commit into the approved deployment checkout (and resolve any
actual drift), then run the server checks. Do not blindly reset main or deploy this
whole investigation branch. Documentation can remain on this branch. For a separate **Mac review branch** on
fresh main, the concrete cherry-pick is:

```bash
git fetch origin
git switch -c codex/speech-release-review-0.5.1 origin/main
git cherry-pick e12cad3cf9bfb900e376347da8c8d05eeeabf15e
```

Keep these handoff instructions from the diagnostics branch. Publishing that reviewed
code to the Gateway is tonight's operator action; none of these preparation steps
has modified the Lighthouse production checkout.

## 3. Enable trace and restart Gateway — tonight only

On Lighthouse, after the reviewed commit is present in the deployed checkout, edit
`~/.config/esp32-device-gateway/gateway.env` without printing its other values. Set:

```text
GATEWAY_SPEECH_TRACE=1
GATEWAY_SPEECH_TRACE_EVERY=1
```

EVERY=1 is optional for a complete 3–5s Gateway trace; the firmware stays sampled.
Then execute in the existing user service context:

```bash
systemctl --user restart device-gateway.service
systemctl --user is-active device-gateway.service
```

Open a second Mac terminal **before** triggering speech:

```bash
mkdir -p /tmp/speech-physical-0.5.1
ssh lighthouse@claw.pangtiao.xyz \
  'journalctl --user -u device-gateway.service -n 0 -f -o cat' \
  | tee /tmp/speech-physical-0.5.1/gateway.log
```

Keep the existing provider, public endpoint/tunnel and target. This run should traverse
the same public path as the failure; a localhost fake-device result is not a replacement.

## 4. Build/verify + flash the diagnostic firmware

The artifact is already built on Lighthouse with firmware version 0.5.1 and
`CONFIG_DEVICE_LAB_SPEECH_TRACE=y`. Its config is an independent local copy of the
existing production endpoint/credentials, not a symlink back to production. No
credential-bearing file belongs in git or shared logs. If a rebuild is necessary,
run on Lighthouse in the isolated worktree:

```bash
cd /home/lighthouse/esp32-dev/.cache/speech-downlink-diagnostics-0.5.1
./scripts/build.sh m5stack-stopwatch
```

A rebuild may change SHA; compare against its own `flash.json` and update the record.
For the prepared image, on Mac download to a fresh directory:

```bash
SPEECH_ARTIFACT_DIR="$(mktemp -d /tmp/speech-diag-0.5.1.XXXXXX)"
scp -r lighthouse@claw.pangtiao.xyz:/home/lighthouse/esp32-dev/.cache/speech-downlink-diagnostics-0.5.1/dist/m5stack-stopwatch \
  "$SPEECH_ARTIFACT_DIR/"
./tools/mac/device.sh verify "$SPEECH_ARTIFACT_DIR/m5stack-stopwatch"
shasum -a 256 "$SPEECH_ARTIFACT_DIR/m5stack-stopwatch/merged-flash.bin"
./tools/mac/device.sh detect
./tools/mac/device.sh flash "$SPEECH_ARTIFACT_DIR/m5stack-stopwatch" --yes
```

Compare SHA to the build record before flash. `ESPPORT` may select the detected USB
port if there is more than one. Existing factory backup / installed Mac tools are
assumed from the prior physical sessions; do not waste this test redoing passed features.

## 5. Serial capture

```bash
mkdir -p /tmp/speech-physical-0.5.1
./tools/mac/device.sh monitor 2>&1 | tee /tmp/speech-physical-0.5.1/serial.log
```

Check `[SPEECH_RX_CONFIG] enabled=1` at boot (reset once if the monitor missed it), then
`[CLOUD] connected` and hello acknowledgment. Record firmware SHA, test case
`read-screen-fixed-01`, network, RSSI and PS from link markers. Keep phone hotspot and
PS unchanged for this first run. Keep the Gateway journal capture running.

## 6. Exactly one READ SCREEN NOW

Use Speech Mode OFF to prevent an automatic extra speech. In the selected Pi session,
prepare a fixed short terminal reply **without voice input**; for example ask it to
reply only:

> 现在进行语音测试。请确认这句话能够完整播放。

Wait for that exact text on the DONE screen. This uses the already-passed final-text
path only as fixture preparation; do not retest Agent, Approval or voice flow. TTS
duration varies; aim for about 3–5 seconds. Do not change text mid-comparison.

On the device: **Long B → A → B → A → B** (Settings → Speech → Read Screen Now).
Trigger once. Do not press A, B or change target while speech plays. Note audible
outcome separately: complete / truncated / silent / distorted. Do not infer sound
from log counters. Continue capture through end or timeout and for ~5 seconds after,
so a late `settled final=1` or disconnect is preserved. Then stop both captures.

## 7. Match the two logs

Record the full 32-character ID from `[SPEECH] start id=...`; match it to Gateway
`[SPEECH_TRACE]` speech_id. A start with no chunks is useful evidence; keep it.
A Gateway ID alone does not prove device reception. The device's generation is local
and maps the queue/playback summaries to that ID.

Expected marker sequence (values illustrative):

```text
Gateway: [SPEECH_TRACE] {"event":"begin","speech_id":"...",...}
Gateway: [SPEECH_TRACE] {"event":"send","message_type":"speech_start",...}
Gateway: [SPEECH_TRACE] {"event":"send","message_type":"speech_chunk","sequence":0,...}
ESP32:   [SPEECH_RX_EVENT] event=start id=... complete_ms=...
ESP32:   [SPEECH] start id=... generation=3 source=screen ...
ESP32:   [SPEECH_RX] event=chunk id=... seq=0 ... push=1
ESP32:   [SPEECH_RX_SLOW] id=... seq=... largest_inter_part_gap_ms=...  # only when slow
ESP32:   [SPEECH_RX_EVENT] event=end id=... chunks=N
ESP32:   [SPEECH] end id=... chunks=N valid=1 queued=1
ESP32:   [SPEECH_QUEUE] generation=3 reason=end ... accepted=N played=N ... final=1
ESP32:   [SPEECH_PLAY] generation=3 reason=end ... bytes_written=... final=1
```

Absence of end, `push=0`, timeout, partial frame or a byte deficit is exactly what this
run is intended to locate. Preserve original logs locally; share the content-free
correlation output and relevant marker lines, not transcripts or Agent payloads.

## 8. Generate the correlation table

```bash
SPEECH_ID='paste-the-full-speech-id-here'
python3 scripts/correlate-speech-trace.py \
  --gateway /tmp/speech-physical-0.5.1/gateway.log \
  --serial /tmp/speech-physical-0.5.1/serial.log \
  --speech-id "$SPEECH_ID" \
  --json /tmp/speech-physical-0.5.1/correlation.json \
  | tee /tmp/speech-physical-0.5.1/correlation.txt
```

Output columns: seq, Gateway send start/complete/send duration/lock wait, device frame
complete, relative network_gap, queue depth, played_bytes snapshot and push status.
Summary: Gateway maxima, device max gap/frame time, expected/received/accepted/played
counts, first provable missing sequence, end seen/valid, disconnect, playback final.

A = observed Gateway send/lock stalls. B = Gateway normal, device receive stalls.
C = both normal and settled playback discrepancy. D = insufficient evidence or no
measured stall. These are transparent threshold labels, **not root-cause diagnoses**.
No synchronized clocks: `network_gap` is relative accumulation anchored on a common
sequence, never an absolute one-way network delay. Unsampled serial sequences remain
blank and are not called missing. Complete codec bytes do not prove audible playback.

**First physical action:** connect StopWatch to Mac using the USB data cable. After
candidate setup, the first and only speech action is the fixed-text READ SCREEN NOW.
Stop after this trace and classify it before making parameter changes.

Optional next experiment, only after preserving the first result: network-only
hotspot vs stable 2.4GHz AP, same SSID/password (only one radio active), firmware SHA,
Gateway config and fixed text. Keep the same *test-case* ID and record a fresh actual
speech_id for each request. No Wi-Fi PS toggle, timeout increase or chunk/I2S change.
