# 0.5.1 next physical downlink test

One focused session, about 15–20 minutes once the candidate Gateway and firmware are ready. No further networking tuning before this trace. Detailed evidence: [investigation](downlink-investigation-0.5.1.md).

## Mac Codex handoff

- **Branch:** `codex/downlink-investigation-0.5.1`, based on `7b5e687`.
- **Candidate commits:** `fef2777` (final field + Chinese Agent display + regression tests), `862738b` (deterministic transport tests/evidence). Later documentation-only commit contains this checklist.
- **What changed:** bounded 2048-byte UTF-8 `final_text` from Pi/Codex to terminal screen/Read Screen; RUNNING keeps activity. CN/TW font now covers Agent body. Activity acceptance and final byte counts have serial markers. No speech/network/I2S tuning.
- **Software proof:** 137 Python + 18 Node tests; six firmware host harnesses; linked glyphs; ESP-IDF build; real TTS/PCM; 3/10/20-second TLS fake-device transfers without missing/duplicate chunks or server disconnects.
- **Physical proof still needed:** timer/activity/final screen, ESP32 sustained receive, I2S and complete audible speech, interruption.
- **Gateway prerequisite:** tonight's production main/service are unchanged. Both candidate server and firmware are needed. A firmware-only test against current production cannot validate the new final field. Prepare the same fix on an approved validation Gateway endpoint; point Pi and device at that same endpoint. Using the production endpoint requires separate user authorization to release the reviewed fix. This handoff does not authorize deploying the investigation branch or restarting production.

Fetch directly from Lighthouse (no branch was pushed), then prepare a separate Mac validation branch:

```bash
git fetch lighthouse@claw.pangtiao.xyz:/home/lighthouse/esp32-dev codex/downlink-investigation-0.5.1
git switch -c codex/downlink-physical-0.5.1 7b5e687
git cherry-pick fef2777 862738b
# To include these docs too, replace the cherry-pick with: git merge --ff-only FETCH_HEAD
```

Exact build on Lighthouse, in the isolated worktree; its ignored local config currently uses the existing device endpoint/credentials. If a separate validation endpoint is used, set that endpoint in an independent local config before rebuilding (the current local config is a symlink to the original; do not edit through it):

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

Exact Mac download/flash, after the user authorizes flashing the candidate:

```bash
scp -r lighthouse@claw.pangtiao.xyz:/home/lighthouse/esp32-dev/.cache/downlink-investigation-0.5.1/dist/m5stack-stopwatch /tmp/downlink-0.5.1-candidate
./tools/mac/device.sh verify /tmp/downlink-0.5.1-candidate
./tools/mac/device.sh detect
./tools/mac/device.sh flash /tmp/downlink-0.5.1-candidate --yes
./tools/mac/device.sh monitor
```

Use a fresh download directory. Do not use `update-flash.sh`: it requires main and fetches the production artifact. Prepared image is 2123456 bytes, SHA-256 `9826e18068006cb4b78df0026e60fbe1914e0a099a15215039ae32f4305f556d`; rebuilt images may differ. VERSION remains 0.5.1. If multiple serial ports exist, set `ESPPORT` to the port reported by `detect`.

**First physical action to ask the user:** connect the StopWatch to the Mac with the USB data cable and leave the serial monitor open. Once flashed/connected, select the intended Pi session, set Speech Mode OFF, stay on PI. First interaction is Test 1 below.

## Six tests, in order

Serial strings below are exact markers/substrings; IDs/counts vary. Record the full serial trace locally. Do not print device tokens, transcripts or PCM into shared reports. Confirm the boot reaches `[CLOUD] connected` and `[CLOUD] hello acknowledged`.

| Test | User action and visible/audible expectation | Exact serial markers |
|---|---|---|
| **1 — Voice → Pi** | Long A, say **“哈喽哈喽”**, release, check transcript, **A SEND**. Pi receives exact/near-exact request; RUNNING is delivered. A very fast reply may finish during the one-second SENT overlay; use Test 2 for sustained timer visibility. | `[INPUT] A LONG`; `[VOICE] capture-started`; `[VOICE] tx type=voice_start` with `sent=1`; `[VOICE] long-release`; `[VOICE] tx type=voice_end` with `sent=1`; `[VOICE] transcript-ready`; `[CONTROL] queue type=voice_decision`; `[CONTROL] rx type=voice_state state=SENT`; `[AGENT] snapshot agent=pi` with correct session and `state=RUNNING`. Confirm input separately in the selected Pi session. |
| **2 — Live process** | In the same Pi session request: **“先回复正在检查，再用 bash 执行 sleep 6，最后只回复：检查完成。”** Stay on PI. If approval appears, approve with A. After its overlay closes, RUNNING timer advances and tool/text changes are visible. | `[AGENT] snapshot` with `state=RUNNING`; `[AGENT] activity agent=pi` with `accepted=1 kind=text` and `accepted=1 kind=tool`, `bytes>0`, `page=PI modal=NONE`; `[DRAW] reason=agent_activity_or_timer page=PI modal=NONE speech=0`. |
| **3 — Terminal result** | Continue Test 2 without changing page. Expect green **DONE** and **“检查完成。”**, not `Completed/bash` or earlier commentary. For an actual failed run, FAILED and useful final text must be separate. | `[AGENT] snapshot` with `state=DONE`, `final_bytes>0`; then `[DRAW] reason=agent_activity_or_timer page=PI modal=NONE speech=0`. A failed turn must log `state=FAILED` independently of `final_bytes`. |
| **4 — Read Screen** | On final screen: **Long B → A → B → A → B** (Settings → Speech → Read Screen Now). Hear the displayed reply through to its end, without gaps/repeated tail. | `[SETTINGS]` with `action=READ_SCREEN`; `[CONTROL] queue type=speech_request`; `[SPEECH] start` with `source=screen`; `[SPEECH] end` with `chunks=N valid=1 queued=1`; `[SPEECH] stop` with `chunks=N ended=1 notify=0`. End validation proves all contiguous chunks reached the firmware protocol/queue, not that the speaker sounded correct. |
| **5 — Automatic reply / summary** | **Long B → A → B → B**, then A until AUTO_REPLY, B save, Long B close. Ask Pi for two short Chinese sentences. Expect one complete Agent → speech path. Repeat with AUTO_SUMMARY for a short tool task if time permits; record each mode separately. | `[CONTROL] queue type=speech_settings_set`; `[CONTROL] rx type=speech_settings`; `[AGENT] snapshot` terminal with `final_bytes>0`; exactly one `[SPEECH] start ... source=reply` (or `source=summary`); matching valid/queued end and `ended=1 notify=0` stop. Synthesis runs concurrently with terminal push, so do not require start to follow the terminal snapshot strictly. |
| **6 — Interrupt** | Start a longer automatic reply (about four short sentences). **During audible speech, Long A and keep holding.** Speech stops, microphone records; say “测试结束”, release, B CANCEL. | `[INPUT] A LONG`; `[SPEECH] stop ... ended=0 notify=1`; `[CONTROL] queue type=speech_stop`; `[VOICE] start`; `[VOICE] capture-started`; `[VOICE] tx type=voice_start ... sent=1`; `[UI] modal NONE -> VOICE reason=long-A`. An already fully received stream can have `ended=1`; trigger early to test interruption while chunks are arriving. |

No per-chunk serial spam was added. For continuous receive, correlate start/end/stop, `chunks=N valid=1 queued=1`, elapsed wall time and listening. Capture any `[CLOUD] link reason=slow-text`, `[CLOUD] rejected text frame`, `[CLOUD] WebSocket error`, `[CLOUD] disconnected`, `[SPEECH] receive idle timeout`, or premature stop (`ended=0`). `[CLOUD] link ... ps=... rssi=...` reports actual link state; current main does **not** explicitly disable Wi-Fi power save.

## Stop and classify

If a test fails, preserve that one trace and stop retuning. No RUNNING snapshot → inspect Pi/Gateway delivery; `accepted=0` → identity; `accepted=1` but non-PI/modal/speech → view gating; correct snapshot/draw markers but blank Chinese → physical rendering. Speech start without valid end → receive/queue path; valid end but incomplete/inaudible output → I2S/speaker path. A valid end alone is never audio success.

Record each test as **SOFTWARE VERIFIED**, **REQUIRES PHYSICAL TEST**, or a newly evidenced **CONFIRMED SOFTWARE BUG**; use **FIXED IN BRANCH** for the candidate and **UNKNOWN** for unlocalized causes. Add a separate factual observation for each actual screen/audible outcome. Do not infer physical success from the existing software results.
