thejayman77 604d61ebb2 Voice init-race fix + velocity-aware Surface lock
Voice (final P1 from audit): the policy is no longer consumed until TTS is
ready, so a phrase can't be queued and then spoken stale after init. When
ready, the policy evaluates the CURRENT reading (silent if moving, correct
instruction if settled). VoiceSpeaker drops its pending-text queue and just
exposes isReady; the policy is remember(speaker) so each VOICE session starts
fresh. Covers all of Codex's cancellation cases (movement, unlock, invalid
placement, foreground loss, mode change, shutdown).

Velocity-aware Surface lock (Jay's observation + Codex): the fixed 400 ms
dwell made a slowly-arriving bubble wait, delaying the level sound, while a
fast crossing could still lock. LockDetector now takes a movement rate:
- error <=0.2 deg AND rate <=0.3 deg/s -> lock after a short 175 ms confirm;
- faster than that -> dwell never accumulates (a center fly-through never
  locks); slowing to a stop inside the zone starts a fresh confirmation;
- 0.35 deg exit hysteresis and the single shared lock (sound/lime/haptic)
  unchanged.
LevelPipeline feeds SettlingDetector.movementRateDegreesPerSecond (was
discarded); Edge passes null -> classic fixed 400 ms dwell. Simple gated
machine, no adaptive-dwell formula.

Tests: slow-entry-locks-promptly, fast-traversal-never-locks, intermediate-
speed-no-accumulate, slowing-inside-zone-fresh-dwell, exit hysteresis,
feedback debounce, Edge fixed-dwell fallback. 82 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 10:27:47 -04:00
2026-07-11 18:34:57 -04:00

On the Level

Offline-first Android pocket toolset: a trustworthy level, angle meter, and calibrated screen ruler (Pro). Kotlin + Jetpack Compose, single module, no backend.

  • BRIEF.md — the authoritative v1 product & technical brief
  • AUDIT.md — audit of the brief; the amendments it lists are incorporated
  • resources/ — concept board (visual direction)

Building

Open in Android Studio, or:

./gradlew assembleDebug        # build
./gradlew testDebugUnitTest    # measurement-math unit tests

Scaffold status

The project skeleton follows BRIEF.md §Sensor and measurement architecture. Implemented and tested:

  • core/sensors — sensor selection (game rotation vector → gravity → low-passed accelerometer), device-frame gravity stream, and the pure measurement math: orientation mapping, two-sample per-mode calibration (derived in angle space, applied in vector space so it stays correct away from zero), EMA smoothing, lock hysteresis/dwell/debounce, display deadband, placement validity, and the sensor-vector contract (all sources emit device-frame world-up; flat = +9.81 on Z, pinned by SensorContractTest). All unit-tested.
  • core/settings — DataStore preferences and per-mode calibration persistence.
  • core/billing — entitlement interface with a stub (free-tier) implementation.
  • feature/level, feature/angle — live numeric scaffolds wired to the real sensor pipeline, with manual Surface|Edge selection, placement-validity hints, lock state (locked label states the tolerance so it can never contradict the rounded readout), and directional hold-to-zero (vector reference, not magnitude subtraction).
  • feature/ruler, feature/tools — static placeholders.

Not yet implemented (deliberately — see TODO markers):

  • The tactile vial visuals and the spring-animated display value (value 3 of 3 in the brief); scaffold screens render the deadbanded stable value directly.
  • Guided calibration flow UI (math and persistence are done).
  • Edge-mode readout counter-rotation on the portrait-locked activity.
  • Play Billing implementation of ProEntitlementRepository.
  • Screen ruler measurement UI and per-display scale persistence.
  • Audio cue, reduced-motion behaviors, preferences UI.
S
Description
No description provided
Readme 1.7 MiB
Languages
Kotlin 83.9%
HTML 16.1%