# Lappy v0.1.3 — BLE setup verification and freeze diagnostics

The owner's v0.1.2 road test stayed on “Searching” and later stopped responding to touch, BOOT and PWR. Inspection of 68 SD captures found three v0.1.2 sessions that completed the old handshake but saved no FD02 GPS notifications. One logged a stream stall ten seconds later. The original UI blocking point was not captured; this release addresses evidenced BLE setup/diagnostic weaknesses without claiming that the complete road-test freeze has been reproduced or fixed.

## BLE connection and recovery

- Fully discover FD00 characteristics in handle order before looking up FD02, FD03 and optional FD04. Retain the pinned esp-nimble-cpp 2.3.4 dependency; do not edit managed components.
- Require an actual FD02 Client Characteristic Configuration Descriptor (CCCD, `0x2902`). The library's `subscribe()` can otherwise return true without enabling updates. Send the subscription with a write response, read the descriptor back, and require `0x0001` before reporting setup verified.
- Preserve the established FD03 challenge/response. A verified setup is distinct from the first FD02 notification, first decoded sample and GPS fix. Log setup stages, handles, readback, failures, first data and disconnect reasons.
- Do not synthesize a notification timestamp on connection. After ten seconds without notifications, request a disconnect and retry. Distinguish packets with no decodable NAV-PVT from a silent stream. No-fix packets keep the connection alive. Wait for asynchronous disconnection before scanning again; failed/no-data attempts back off by 1/2/5/10 seconds, capped at ten. Reset the backoff after a decoded sample, rather than after setup alone.
- The NimBLE notification callback copies data to bounded queues with zero queue wait. It does not take the application State mutex or wait for a storage producer. Raw payload bytes, boundaries and original arrival timestamps are unchanged. A busy/full capture queue or decoder queue is counted explicitly; decoder overflow breaks continuity. Late callbacks after release/disconnection are ignored, and queued old-generation data is discarded for decoding.

The upstream [2.5.0 release notes](https://github.com/h2zero/esp-nimble-cpp/releases/tag/2.5.0) document a descriptor-boundary fix for out-of-order characteristic handles. This patch works around that condition through complete ordered discovery and verifies the subscription directly. Actual compatibility with the owner's Dragy remains a radio test, not a consequence of host mocks passing.

## Connection text, unchanged design

Existing header and Street status widgets now distinguish scanning, linking, waiting for data, missing packets, setup failure, retry and stale data. A connected receiver awaiting its first sample displays `WAIT DATA` / `Waiting for data`, rather than `SCAN` / `Searching`. The same connection label/color logic is used by all three modes. GPS fix text remains separate from the Bluetooth state.

Positions, dimensions, fonts, icons, page dots, colors for established healthy states, boot artwork, gestures, settings controls, and Street/Drag/Track calculation policies are preserved. There is no new instrument page. The native simulator adds three external scenario-picker entries for waiting, silent-stream and setup-failure states.

## Freeze evidence and recovery

- Explicitly subscribe the main/UI task to ESP-IDF's task watchdog, feeding it only after a complete platform/timing/UI/LVGL iteration. A main task blocked for approximately **15 seconds** triggers the panic handler, saves an ELF crash dump and restarts. Watching only the idle tasks cannot detect a main task sleeping indefinitely on a mutex.
- Reserve a **256 KiB flash coredump partition at `0x410000`**. The application remains at `0x10000`, its 4 MiB allocation is unchanged, and NVS/PHY offsets remain unchanged. Dumps survive a normal restart and do not depend on the SD card. A later panic can replace the previous dump; read it promptly after a recurrence.
- Retain a small RTC RAM breadcrumb containing the last main-loop stage/progress, BLE setup stage and sampled counters. On a warm reset, record it in the new boot's log. A breadcrumb reports last progress, not a diagnosis. Cold power loss can clear it.
- An independent health task writes a compact SD event every five seconds: main stage/age, notification/byte/frame counts, drops, touch/display progress, internal heap/largest block and main stack margin. Its application snapshot and storage enqueue never wait. A blocked/unavailable SD writer can still lose these events; the flash dump is independent.
- Console `diagnostics` and `coredump` report progress and the boot-time saved-dump summary without taking the State mutex. Other commands have a bounded initial State snapshot and report busy instead of hanging there. Existing `status` remains available in normal operation.
- `tools/read_coredump.py --port PORT --out NEW_DIRECTORY` reads the device's partition table, locates the coredump partition, downloads it and writes a SHA-256 file. It uses esptool reads only; entering the ROM loader resets the board. Decode with `esp-coredump --chip esp32s3 info_corefile -t raw -c coredump.bin MATCHING-lappy.elf`. Use the ELF from the crashing firmware, not an arbitrary newer build.

This is emergency restart after a software stall, not the normal saved manual-PWR shutdown. It does not guarantee that the last buffered SD records survive or that a damaged filesystem is repaired. PWR/BOOT remain polled by the UI task; the watchdog provides recovery if that task stops progressing. BLE-worker-only stalls remain observable through progress/connection logs and do not independently trigger a UI watchdog reset.

## Flash and verification

**Updating from v0.1.2 or earlier requires the new partition table as well as the application.** Use the normal checksummed `tools/flash.py` release workflow; an application-only write will not install flash crash storage. No erase-all or settings reset is required.

Host checks include all 65,536 challenge pairs through the actual setup controller, fault injection for discovery/descriptor/read/write/subscribe/readback, transport timeout/backoff states, and a multithreaded test of the actual firmware telemetry service while State/storage locks are held. Address/undefined-behavior sanitizers and a separate ThreadSanitizer run cover the ingress test. Existing Street, Drag, Track, course-pack, clock, persistence and simulator interaction suites are retained.

The renderer exports **148 current screens**. Of 127 v0.1.2 screens, 123 are pixel-identical. The four differences are the Settings version string and connection-status text/colors in `connecting`, `stale-stream` and `gps-clock-unset`; no geometric/style edits occur. The 21 new renders exercise seven connection states across the three modes and assert identical status-strip pixels. The [limited physical V2 bench](HARDWARE_BENCH_v0.1.3.md) confirms deliberate main-task freeze → watchdog → persistent dump → successful decoding, followed by installation of the normal release, saved shutdown and restart. No real Dragy was available for this release's bench.
