Skip to content

feat: coded-word ultrasonic signaling and full-depression trigger - #26

Merged
joshuagruenstein merged 5 commits into
mainfrom
feat-coded-symbols
Jul 12, 2026
Merged

feat: coded-word ultrasonic signaling and full-depression trigger#26
joshuagruenstein merged 5 commits into
mainfrom
feat-coded-symbols

Conversation

@joshuagruenstein

@joshuagruenstein joshuagruenstein commented Jul 12, 2026

Copy link
Copy Markdown
Member

The system

  • Signaling: every device event is a codeword of four 25ms chirp symbols (two disjoint ultrasonic bands × two sweep directions, ~29ms apart — ~110ms per event). The codebook is Reed–Solomon [4,2] over GF(4): minimum Hamming distance 3, so the Mac corrects any single corrupted symbol and rejects anything further — noise can never decode as a different button. SymbolSet.swift is the air-gap contract shared by the flasher and the decoder.
  • Detection: per-band heterodyne (exact 64-sample carriers) → polyphase FIR decimation 48k→3k → sliding normalized complex correlation (~19dB processing gain). The decoder is pilot-locked: silent until three periodic, level-consistent beacon words; pilot loss (sleep/unplug) mutes it; a single beacon at the remembered level fast re-locks. The pilot's level continuously calibrates a credibility gate.
  • Trigger: full-depression semantics from TE's processed handle signal, with a measured rate-based fast path for mashes (the processed signal slews at ~0.35/s): press at ≥0.90 or switch-closed + sustained climb (~45ms after the physical click); release below 0.60 only while genuinely descending, or fully returned.
  • Dictation, config, serial transport, Sparkle/brew distribution unchanged.

Verification

  • 249 Swift assertions + payload suite: codeword decode for every message, single-error correction, double-error rejection, acquisition lifecycle (cold lock / pilot loss / fast re-lock), deep-SNR floors, adversarial corpus (plain tones, sibilance, percussion, glides, 60s noise soak), mash/stall/rest trigger laws pinned to measured hardware profiles
  • Live-tested end to end by Josh over USB and 3.5mm: mash-to-red ~150ms, slow-squeeze feel unchanged, long holds stable, buttons snappy, no phantoms

🤖 Generated with Claude Code

joshuagruenstein and others added 2 commits July 11, 2026 23:17
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SymbolSet: 4 linear chirps (2 bands x 2 directions), the single
  air-gap contract shared by Flasher and the decoder
- SymbolDetector: heterodyne (exact 64-sample carriers) -> polyphase
  64-tap FIR decimation 48k->3k -> sliding normalized complex
  correlation; symbol fires on dominant peak. Pilot lock, level
  calibration, plausibility floor, beaconSensed ported from v1.
- Flasher writes symbol WAVs (config toneFrequencies legacy-ignored)
- GoertzelDetector + its heuristic pile + v1 tone fixtures DELETED
  (hard cutover per Josh; the v1 waveform is now an adversary test)
- Suite: all pair codes, acquisition lifecycle, deep SNR (trigger at
  -8dB broadband SNR — ~2x below v1's floor), legacy-tone/sibilance/
  percussion/glide adversaries, 60s noise soak. 248 assertions.

NOT yet live-tested on hardware — needs Flash EP + power cycle + the
dev.sh loop with Josh before any merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joshuagruenstein
joshuagruenstein marked this pull request as draft July 12, 2026 03:37
joshuagruenstein and others added 2 commits July 12, 2026 00:09
The white press was the protocol's only lone-symbol event, and it was
never serialized: it came from STOCK sample playback (outside the chirp
queue), so it could interleave with an in-flight beacon pair and
misdecode as green/orange (~30% of presses near a beacon), or pay the
full 350ms lone-resolution wait (~500ms perceived latency, reported
intermittent by Josh).

- Payload: white down is no longer chained to stock (its only stock
  action was the unserialized playback); it queues (sam, sam) like
  every other event. White up chains nothing (gate-off of a oneshot
  is a no-op).
- Decoder: (N,N) pair -> whitePress; lone symbols are no longer events
  at all (a lone is a degraded pair or noise: logged, dropped). The
  beacon-cadence rescue for degraded beacons stays.
- Latency: white fires at pair completion (~250ms total), beacon
  collisions structurally impossible (device queue serializes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… law

Every event is a four-symbol codeword: 25ms chirp symbols (two disjoint
ultrasonic bands x two sweep directions) at ~29ms spacing, coded with a
Reed-Solomon [4,2] code over GF(4) — minimum distance 3, so the decoder
corrects any single corrupted symbol and rejects anything further. A
word occupies ~110ms on the wire. Noise cannot turn one button into
another by construction.

The trigger fires on full depression via TE's processed handle signal,
with a rate-based fast path for mashes (the processed signal is
rate-limited ~0.35/s and lags a fast squeeze by ~300ms — measured):
press at handle>=0.90 or switch+sustained-climb (~45ms after the
physical click); release below 0.60 only while genuinely descending
(a mash's mid-climb stall must not read as release) or fully returned.

Docs (README, DESIGN.md, CLAUDE.md) rewritten to describe the current
system; payload is pure ASCII (serial-write deploys drop non-ASCII).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joshuagruenstein joshuagruenstein changed the title feat: coded-symbol signaling — matched-filter decoder, hard cutover feat: coded-word ultrasonic signaling and full-depression trigger Jul 12, 2026
@joshuagruenstein
joshuagruenstein marked this pull request as ready for review July 12, 2026 04:57
@joshuagruenstein
joshuagruenstein merged commit 0a82829 into main Jul 12, 2026
2 checks passed
@joshuagruenstein
joshuagruenstein deleted the feat-coded-symbols branch July 12, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant