# 0.5.1 physical downlink validation — 2026-10-04

This session stopped after the Read Screen physical failure, as instructed.
Software test success does not establish screen or speaker success.
Test definitions: [checklist](physical-downlink-0.5.1-next-test.md).

## Released baseline

- Mac main, origin/main and Lighthouse main: `52d1d609ee980a88737bd0143e7b84d88cd4a4c3`, clean.
- Gateway restarted at 09:45:40 Asia/Shanghai; active/running, health ok, NRestarts 0.
- Production firmware freshly built on main: version 0.5.1, 2,123,456 bytes.
- Merged SHA-256: `9204bb3a8513c7ee897adca895a1575a87eaf9928a002796b1b79c478269f71a`.
- Flashed and hash verified on Mac: `/dev/cu.usbmodem1101`.
- Full Lighthouse suite: 137 Python, 18 Node, six firmware host harnesses,
  Gateway smoke and authenticated TLS voice smoke passed; IDF build and linked glyph checks passed.
- Pi target: `01a0fcd2-7b96-77fb-a57a-40bbfb9fe5da`, project `/home/lighthouse/esp32-dev`.
- Speech Mode OFF confirmed through Gateway.

## First voice attempt: live Pi lacked its control endpoint

Physical capture completed with 59 chunks / 208,152 PCM bytes, no capture failure,
successful voice_start/end sends, transcript-ready (12 bytes, zero missing glyphs),
and a queued SEND decision. Firmware then received voice_error/FAILED, with no
Agent RUNNING. The user saw `input failed`.

Read-only checks showed a live IDLE heartbeat but an absent session control socket
(ENOENT) and HTTP 502 from settings. The user ran `/reload` in that same Pi session.
Its control ping and settings then succeeded. No duplicate runtime was started,
and input was not automatically replayed. The original endpoint-init failure is
unknown; reload restored it.

## Retry: input and text downlink reached hardware; DONE notification crashed

The retry captured 55 chunks / 194,040 PCM bytes without error. Firmware received
SENT, RUNNING on the selected session, accepted text activity on PI with no modal,
and DONE with `final_bytes=277`. The selected Pi session returned a Chinese final
reply acknowledging the voice input.

Immediately after DONE, the first notification PWM pulse logged successfully,
then serial reported:

```text
***ERROR*** A stack overflow in task pi_notify has been detected.
ELF file SHA256: 963117b25
Rebooting...
```

Exact-ELF decoding identifies vApplicationStackOverflowHook / vTaskSwitchContext.
The corrupted remainder does not identify an individual driver frame. This is a
physical firmware notification failure, not evidence of failed final-text delivery.
After reboot, the device reconnected and received the same 277-byte final again.
Screen correctness and stable terminal rendering remain unconfirmed.

The follow-up firmware fix `6bffe571b7c1b61cfdb168b0f7a03737bad394f1` gives vibration tasks using the M5IOE1 PWM/I2C/logging
path 4096 bytes instead of 2048, including DONE/FAILED and approval patterns. It
logs the minimum free stack in bytes after the pattern completes.

## Corrected firmware and completed Agent tests

- Corrected firmware source: `6bffe571b7c1b61cfdb168b0f7a03737bad394f1`.
- Version 0.5.1; merged image 2,123,712 bytes; SHA-256
  `673ba20d8d010a562e441c7f136ec48afcb0853f45d684e40c489465c0009cbf`.
- Full Lighthouse suite repeated: 137 Python, 18 Node, six firmware host harnesses,
  Gateway and authenticated TLS voice smoke passed; IDF build and linked glyph checks passed.
- Mac downloaded this fresh production image, verified SHA and esptool write hash,
  and flashed `/dev/cu.usbmodem1101`. No Gateway code changed in this firmware fix.

Test 1 on the corrected firmware captured 65 chunks / 229,320 PCM bytes, received
SENT, RUNNING, accepted text, DONE with 155 final bytes, and completed the DONE
vibration pattern with **2020 bytes minimum free stack**. No overflow/reboot
appeared; the user explicitly reported no reboot and normal operation. This
establishes a physical PASS for voice input and DONE notification stability.

Tests 2–3 used voice input to ask the same Pi to emit Chinese progress, execute
`sleep 6`, and return the final result. Capture completed with 240 chunks /
846,720 PCM bytes. Firmware accepted both text and tool activity, showed
WAITING_APPROVAL, received an A approval, returned to RUNNING during the six-second
tool, and received DONE with `final_text="检查完成"` (12 UTF-8 bytes; the actual
reply omitted punctuation). The user confirmed the timer, Chinese and tool
activity and final display all looked correct, and that Approval occurred.
Pending approvals then numbered zero. Approval vibration completed with **2024
bytes minimum free stack**; DONE vibration completed with **2028 bytes**. No
overflow/reboot occurred. FAILED notification was not separately exercised.

## Test 4: Read Screen physical FAIL — receive/timeout boundary

User action followed Settings → Speech → Read Screen Now while Speech Mode was OFF.
READ_SCREEN and speech_request were queued. Stream
`13433e346fd44a4f8ccb30f22a59da90` started with source=screen at device uptime
409853 ms. The user reported low/inaudible sound followed by an error; complete
audible output was not verified.

```text
409853 ms: [SPEECH] start ... generation=1 source=screen
417843 ms: [CLOUD] link reason=slow-text bytes=9503 parts=2 elapsed_ms=2040
423313 ms: [SPEECH] receive idle timeout generation=1 elapsed_ms=3000
423693 ms: [SPEECH] stop ... chunks=29 ended=0 notify=1
425053 ms: Could not lock ws-client within 200 timeout
425693 ms: Could not lock ws-client within 200 timeout
425693 ms: [UI] modal NONE -> ERROR reason=speech-error
464833 ms: [CLOUD] link reason=receive-error ... elapsed_ms=9997
464863 ms: [CLOUD] disconnected
```

No valid speech_end was received. Other sampled text-frame reassembly delays were
251, 489, 919, 1252, 2932 and 2724 ms. RSSI samples were -36 to -50 dBm, PS=1.
Both failed outbound sends took approximately two seconds. The user also pressed
B during the stream at 423003 ms, moving PI → NETWORK; serial records the idle
timeout independently, without a page-change speech_stop before it.

This is **classification B: speech_start without valid speech_end**. The evidence
narrows investigation to the sustained WebSocket receive/queue/timeout path; it
does not prove the specific cause or whether the Gateway had sent every chunk.
It does not justify treating speaker gain, I2S or amplifier output as the sole
failure. Gateway stayed active/running with the same PID and NRestarts=0 while
the device connection later closed. AUTO_REPLY and interruption were stopped
without testing. No TCP/window, Wi-Fi power save, chunk, DMA or timeout tuning
was performed after this failure.

Gateway subsequently recorded automatic device reconnection at 10:07:23
Asia/Shanghai; health returned online_devices=1, with PID 99332 and NRestarts=0.
Serial uptime continued without a new boot during this speech failure.

## Current physical status

| Area | Status |
|---|---|
| Voice input to selected Pi | PHYSICAL PASS on corrected firmware, after extension reload |
| RUNNING / live text and tool activity | PHYSICAL PASS: serial plus user observation |
| Chinese Agent rendering / final screen | PHYSICAL PASS: authoritative final and user observation |
| Approval / DONE notification stability | PHYSICAL PASS after stack fix; FAILED pattern untested |
| Read Screen | PHYSICAL FAIL: incomplete receive, idle timeout, no valid end, audible output unverified |
| AUTO_REPLY / interrupt | NOT TESTED, stopped after Test 4 failure |
| Screen flicker | NOT CONFIRMED: user did not observe this explicitly |
| Startup page | Serial shows Overview initially, then automatic PI selection on target restore; independent UI issue |

Full local traces and conductor state are preserved under ignored
`dist/physical-validation-20261004/` on the Mac. No credentials or audio are
included in this document. Remaining investigation should correlate Gateway
send timestamps/counts with ESP32 frame completion and queue/consumer timing for
one speech ID before changing transport or audio parameters. Latest main may
contain a later documentation-only commit; the tested/flashed firmware source
and image hash above identify the physical artifact.
