voice-weak-network-followup.md
voice-weak-network-fix.md

## Voice weak-network root fix — remaining work

Branch: `fix/voice-weak-network-reliability`. Design/decisions: `docs/voice-weak-network-fix.md`.
Problem record and acceptance matrix: `docs/voice-weak-network-followup.md`.

Implemented (firmware + gateway, protocol 0.6.0):
- [x] Capture decoupled from the network: the full clip is retained in a fixed
      PSRAM buffer (`VOICE_MAX_RECORDING_BYTES`), so a stalled upload can no
      longer cancel the recording. `boards/m5stack_stopwatch/audio_backend.cpp`,
      `boards/common/include/voice_recording_store.h`.
- [x] Acknowledged chunked upload with reconnect resume and a `retry` decision
      (`firmware/main/cloud_client.cpp`, `server/device_gateway/voice.py`).
- [x] `suspend` vs `cancel`: transport drops keep an unfinished recording, while
      target change / approval / explicit cancel still discard it; replacement
      WebSocket preserves it and requires an explicit resume handshake.
- [x] Bounded behavior: 20 s recording cap, separate upload window
      (`MAX_UPLOAD_SECONDS`), finite retry (`VOICE_UPLOAD_MAX_RETRY_MS`), no
      infinite retry and no false success.
- [x] UI phases (`UPLOADING n%`, `UPLOAD PAUSED`, `UPLOAD FAILED` with
      RETRY / DISCARD) and firmware version + build time on the Help screen.
- [x] Tests extended: `firmware/tests/voice_recording_store_test.cpp` (wired into
      `scripts/test-wearable-shell.sh`), ACK/replay/resume/retry coverage in
      `server/tests/test_stt.py`.

第二轮及最终收口软件复验与设备验收：见 `docs/voice-weak-network-round2-acceptance.md`。
- [x] END 必须获得 `voice_end_ack` 才释放上传 PCM；terminal pending 可重连查询 STT。
- [x] 120 秒硬截止后不可 RETRY，显示 UPLOAD EXPIRED 并清理设备/网关请求。
- [x] 最终 full firmware host、Voice/STT 70 项、server 325 项、Node/Pi 24 项及 ESP-IDF 5.5.4 全量构建通过。
- [x] 合入最新 main，保留 Voice 与 Pi Running timer 修复；未合回 main。
- [x] Linux 隔离候选目录网关全量 320 项及统一脚本 smoke 通过。
- [x] ESP-IDF 5.5.4 固件构建、字体覆盖及 PMIC/IOE ABI 校验通过。
- [x] 2026-10-09 真机弱网、真实 STT 与内存实测：用户缩减后的 26 个有效用例 PASS；原始完整矩阵未全跑。见 `docs/voice-weak-network-final-acceptance-20261009.md`。

Remaining before close:
- [ ] Run the firmware verification plan below on real hardware (lighthouse build,
      Mac flash — no local flash from this repo checkout).
- [ ] Measure peak internal-heap and PSRAM with screen + Speech + TLS active.
- [ ] Fill in the closing record in `docs/voice-weak-network-followup.md`
      (SHAs, gateway version, memory peak, weak-network success rate, duration).
- [ ] Field acceptance on a mobile hotspot with multiple consecutive rounds,
      confirmed by the user against the real AI session.
- [ ] Decide separately whether post-reboot recovery (audio to disk) is wanted;
      it is an explicit non-goal of this fix.

## 语音上传时延与录音上限（待专门处理）

分析与方案评估：`docs/voice-latency-and-recording-cap.md`（记录日期 2026-10-09，**仅分析，未改动代码**）。

现象：现有链路为「先传完 → 再转文字 → 等用户确认 → 发给 Agent」的严格串行流程，
演示勉强够用，但等待偏长；同时需明确单段录音的合理时长上限。

- [ ] 先量化：抓 `[VOICE_STT] event=complete duration_ms`、`[VOICE] tx-stall elapsed_ms`、
      `[VOICE] tx type=voice_end max_send_ms`，算 `上传秒数 / 录音秒数`，再决定投哪一档。
- [ ] 静默截断缺反馈：写满 `VOICE_MAX_RECORDING_BYTES`（20 s）时应立即发 `voice_end`
      并在 UI 提示已达上限，而非无声丢弃。改动小，不触碰弱网逻辑，优先做。
- [ ] 降采样评估：44.1 kHz 对 STT 是过采样。降到 16 kHz 可把上限提到约 55 s、
      单块降到 1/2.75。**注意 `AUDIO_SAMPLE_RATE` 被播放与提示音共用**，须先确认播放侧影响。
- [ ] 窗口化发送（收益最大）：把停等式上传改成滑动窗口，把 500 × RTT 压到约 (500/N) × RTT。
      复用 `voice_ack_valid` 的 `sent_limit` 语义；需重跑弱网验收的丢 ACK / 断网恢复 / 重复 chunk。
- [ ] 明确排除「边录边传」：会推翻本轮弱网修复「传输卡住绝不污染麦克风」的设计前提。

## Firmware verification plan (no local flash)

Do not flash from this checkout. Build on the lighthouse host (ESP-IDF 5.5.4),
flash from the Mac with `tools/mac` esptool. Respect D5 in
`docs/review-2026-10-08-local-flash-role.md`: do not contend with the production
gateway while the device MAC `28:84:85:43:a2:00` is bound to `gw.pangtiao.men`;
point the device at a staging gateway for the fault-injection rounds.

Host tests (must pass before any device work):

```
scripts/test-wearable-shell.sh          # includes voice_recording_store_test
cd server && .venv/bin/python -m pytest tests/ -q
```

Device rounds, each logged with device serial plus gateway metadata:

| # | Scenario | Expected |
| --- | --- | --- |
| 1 | Normal network, 5 / 10 / ~20 s recordings | Complete capture, upload, transcription, acceptance; no tail truncation |
| 2 | Upload throttled below capture rate (trickle the WSS) | Recording never cancelled; release A continues uploading; UI stays responsive |
| 3 | Drop the network 5 s and 10 s mid-record and mid-upload | Clip retained, upload resumes from the acknowledged sequence; no duplicate AI submission |
| 4 | Lose the ACK after upload complete | `voice_status` / retry returns the breakpoint; no second AI instruction |
| 5 | Duplicate / missing / out-of-order chunk, stale socket data | Idempotent or explicit refusal; only a complete, owned recording is transcribed |
| 6 | Transcription timeout / service error | Failed stage clearly shown; recording reusable for retry |
| 7 | Explicit cancel, target change, Approval, new recording | Cancel immediate; old request isolated; no recovery into another session |
| 8 | Long offline / duration cap / memory pressure | Bounded, explainable; no crash, no false SENT, no infinite retry |
| 9 | Pi with queued tasks; Codex voice input | Native queue / submit behavior preserved with acceptance confirmed |
| 10 | Mobile hotspot, multiple consecutive rounds | Record success rate, audio integrity, wait time, button latency, reconnect recovery |

Evidence to attach to the closing record: firmware SHA + build time, gateway
version, per-round device serial and gateway log excerpts (diagnostic metadata
only — never PCM, base64, transcripts, or credentials), and the user's
confirmation of the transcription and actual AI session outcome.