Commit Graph

14 Commits

Author SHA1 Message Date
thejayman77 daf19c34b3 Address Codex audit: Tools cards, 4-point validation, durable persistence
P1 - Tools screen no longer exposes dead 'coming with the feature build'
cards. Replaced the stale Calibration and Units/Feedback cards with a
single working Settings card that opens the Settings screen.

P1 - Detailed (4-point) calibration now validates rotation quality, not
just the final mean. A 4-point set holds two independent 180-flip bias
estimates - the (0,180) and (90,270) pairs; a malformed turn makes them
diverge even when the mean stays small (which the 3-degree magnitude
guard can't see). isRotationConsistent rejects divergence beyond 0.4
degrees. The disagreement scales with the surface's true tilt, so it's
lenient near level and strict on a steeper permitted surface. Test
proves a deliberately 10-degree-over-rotated set is rejected and a clean
set passes.

P2 - Calibration persistence moved from the screen's rememberCoroutineScope
to a container-owned applicationScope, so the write survives an immediate
Done/back after 'saved'. Preference writes moved there too.

Cleanup: haptic copy is mode-neutral ('when the level locks'); Settings
no longer keeps the screen awake (calibration still does); calibration
method renamed Thoroughness -> Method with Standard/Detailed labels and
accurate 'Four positions, a quarter-turn apart' / 'Averages more sensor
noise' copy; lime now marks the active-calibration status (confirmed
state), consistent with lime = level/confirmed.

64 tests passing; assembleDebug clean; verified on device.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 11:15:29 -04:00
thejayman77 85a8ce1136 Calibration UX overhaul + Settings screen (Claude, driving)
Fixes a real defect and reworks the calibration/settings flow per Jay's
on-device review.

Bug: the second capture hung on 'capturing'. The completion code wrote
capture=null (a LaunchedEffect key) then awaited the suspend
setSurfaceCalibration BEFORE setting phase=COMPLETE; at the suspension
Compose cancelled the effect mid-write, stranding the transition. The
effect body is now fully synchronous and persistence runs on an
independent scope.launch. Verified end-to-end on device.

Settings screen (new): reached from the Level header gear (was: gear
jumped straight into calibration). Calibration is a sleek button in its
own section; Units, Haptics, and Reduce-motion prefs get a home; Done is
pinned to the bottom while the list scrolls independently.

Calibration screen: two stages. OVERVIEW shows the current correction
(Pitch/Roll offsets), the Thoroughness selector, and Calibrate / Reset
to phone defaults / Done. CAPTURING shows ONLY capture + Cancel; Cancel
restores the prior calibration (saved value is never touched mid-flow).
Instructions centered; outlined buttons given visible borders.

Thoroughness: Simple (2-point, 0/180) or Thorough (4-point,
0/90/180/270). Generalized deriveSurfaceFromSamples averages a symmetric
rotation set - the true tilt sums to zero, so the mean is the device
bias; 4-point also cancels each axis twice and averages more noise. Two
new unit tests (2-point equivalence, 4-point bias recovery).

61 tests passing; assembleDebug clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 10:37:35 -04:00
thejayman77 721fccd5ce Surface polish: motion fix, instrument redesign, readout typography (Codex + Claude)
Driven by on-device iteration on a Galaxy S24+ (live ADB install/
screenshot loop), gated by a real-device motion pass at animator
scale 1x and frame stats (p95 8ms, 0.85% legacy jank).

Motion (Codex): X/Y axes animate in independent concurrent effects.
The prior sequential animateTo calls starved Y under ~50Hz
retargeting - diagonal movement traced an L and the second axis
caught up seconds late. Reduced-motion/system-disabled paths snap
both axes.

Instrument (Claude, on Codex's base):
- Fluorescent yellow-green fluid (matches real spirit-level dye);
  amber retained for guidance text.
- Bubble as a true void: borderless symmetric gradient orb,
  translucent interior (marks read through it), soft bright
  refraction band, contact shadow, fluorescent halo, window-style
  reflection + glint. Velocity-squash experiment tried and removed -
  shape fidelity beats the flourish.
- Bubble (0.12R) nests inside the 1-degree target ring; ring turns
  lime on VISUAL containment of the drawn bubble (lock label/pulse/
  haptic remain on LockDetector truth).
- Machined bezel, etched cardinal/45 ticks, per-ring degree labels
  via the same SurfaceGuidance mapping. dp-scaled strokes replace
  raw-pixel hairlines; contrast lifted for real screens.
- Red center target dot beneath the translucent bubble (Codex).

Text hierarchy: guidance suppressed while locked (no correction
advice under a 'Flat within' verdict), demoted to titleLarge, and
raised into the empty status line's slot. Primary readout optically
dead-centered via an invisible leading degree twin; degree symbol
.55em at TextDim with cap-height baseline shift (.38 - metrics pair,
resize together).

59 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 07:34:58 -04:00
thejayman77 30417a5f98 Slice 5: Audio Level Assist + fixed guidance slot (Codex)
- AudioAssistStateMachine (core/audio, pure, tested): proximity bands
  with enter/exit hysteresis (close 1.0/1.2, near 3.0/3.4 degrees),
  cadence preserved across band changes (no threshold double-ping),
  lock cue driven solely by LockDetector's fireFeedback transition -
  no second lock threshold, and the haptic debounce paces the ping.
- Silence gates: disabled, backgrounded, settling, invalid placement,
  or outside FACE_UP all reset and mute; all five unit-tested.
- SonarSoundPool: locally synthesized WAV pings via SoundPool with
  USAGE_ASSISTANCE_SONIFICATION; zero audio-focus APIs (verified by
  grep); player exists only while enabled and resumed, released on
  composition disposal.
- Persisted SONAR ON/OFF header control using the existing
  audioCueEnabled preference; off by default; state shown as text,
  never color alone.
- Guidance slot fixed at 56dp so settling/correction/level copy no
  longer shifts the instrument (Slice 4 polish note).

59 tests passing.

Audited-by: Claude (no findings)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:43:07 -04:00
thejayman77 027b9b96b3 Slice 4: actionable adjustment guidance (Codex)
- SurfaceAdjustmentInfo: high-side instruction sourced exclusively from
  SurfaceGuidance.from(displayed pitch, displayed roll).highLabel - the
  deadbanded axes supply the label hysteresis, so wording cannot flap
  at direction boundaries.
- Shared SettlingDetector exposed through the pipeline as the sole
  motion gate; strong guidance defers behind a quiet Settling state.
- RiseRun (core/sensors, pure): tan-based mm/m and in/ft, derived from
  the same deadbanded magnitude as the numeric readout; pinned at
  45 degrees = 1000 mm/m = 12 in/ft.
- Persisted MeasurementUnits preference in core/settings (metric
  default); Tools UI control remains deferred per plan.
- Guidance renders only in face-up Surface mode, hidden at level
  (no direction below the 0.1-degree deadband).

56 tests passing.

Audited-by: Claude (no findings)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:43:42 -04:00
thejayman77 422854de07 Slice 3: tactile bullseye instrument (Codex)
- SurfaceGuidance (core/sensors, pure, tested): the sole source of
  bubble position, high-side direction, and ring scale. Bubble and
  1/2/5-degree etched rings share one 5-degree visual range; the
  clamped target puts a saturated bubble exactly at the rim ring.
- SurfaceBullseye: Compose Canvas glass vial with crosshairs and
  amber bubble. Critically damped (no-overshoot) spring toward the
  pre-clamped target; velocity carries across retargets. Reduced
  motion (in-app pref or ANIMATOR_DURATION_SCALE == 0) snaps directly.
- Lock treatment: brief lime ring pulse on acquisition, persistent
  lime tolerance label while locked, neutral numeric readout. Never
  color-only; no full-screen lime state.
- Pipeline passes stable calibrated pitch/roll to the instrument;
  deadbanded readout remains authoritative. Bullseye renders only in
  the FACE_UP presentation.

53 tests passing.

Audited-by: Claude (1 finding raised and resolved)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:30:45 -04:00
thejayman77 d3854ac402 Slice 2: guided Surface calibration flow (Codex)
- SurfaceCalibrationScreen: two-sample guided flow from the Level header.
  Captures read SensorSource.gravity directly - stored calibration is
  never applied to either sample. Exact 180-degree same-plane guidance,
  clear-saved-calibration action, Surface persistence only.
- SettlingDetector (core/sensors, shared): 1.0 deg/s enter, 0.3 deg/s
  exit held 500 ms; mid-band movement resets the quiet dwell without
  leaving Settling (audit finding 1).
- StableSurfaceCapture (core/sensors): continuous 1.5 s settled window
  with the 5-degree calibration-surface guard; restarts on motion.
- SurfaceCalibrationValidation (core/sensors): 3-degree bound on the
  combined two-axis bias magnitude, unit-tested both sides of the
  boundary (audit finding 2).
- Lock feedback suppression is structural: the calibration route removes
  LevelScreen from composition, cancelling its sensor collection.

50 tests passing.

Audited-by: Claude (2 findings raised and resolved)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 14:51:28 -04:00
thejayman77 8d942a88b4 Slice 1: honest full-range Surface readings, self-gating lock (Codex)
- Surface tilt magnitude stays visible 0-180 degrees; never blanked by
  placement. Lock is self-gating (only near-zero magnitude can enter),
  so the placement gate now drives hints only. Edge gating unchanged.
- Pitch/Roll suppressed as ambiguous at >=80 degrees; explicit
  screen-down state past 90 degrees with the true magnitude retained.
- SurfacePresentationDetector classifies from the same deadbanded
  stable magnitude the UI displays, with 1-degree hysteresis at both
  boundaries and a direct face-up -> screen-down transition.
- Tests: angle sweep (30/80/near-vertical/screen-down), hysteretic
  boundary tests, and a sustained 30-degree run proving a steep phone
  never locks or fires feedback. 43 tests passing.

Audited-by: Claude (2 findings raised and resolved)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 12:10:05 -04:00
thejayman77 0c197894ff Revise Surface plan per audit: near-vertical spec, calibration guards, shared settling, pure guidance mapping (Codex) 2026-07-13 11:52:52 -04:00
thejayman77 abff397364 Add Surface Level attack plan (Codex) and Gradle daemon JVM pin 2026-07-13 10:29:04 -04:00
thejayman77 99c2b12192 Address Codex measurement-core review
- Calibration: still derived in angle space via the 180-degree flip, but now
  APPLIED in vector space as a reference-orientation rotation (Rodrigues
  alignment for Surface, Z-rotation for Edge), exact away from zero; tests
  at 30 degrees, cross-axis, and edge-polarity cases.
- Angle relative zero: stores the gravity direction vector; relative reading
  is the angle between directions, so cross-axis movement is honest.
- Edge mode: precise geometry documented (either long edge down, gravity
  along +/-X), placement-validity guard so e.g. Edge mode never locks on a
  phone lying flat; UI shows repositioning hints.
- Lock/readout coherence: locked label states the tolerance (exit threshold)
  so the rounded readout can never contradict it; pipeline acceptance tests
  pin the invariant.
- Sensor-vector contract: documented and pinned by SensorContractTest. The
  suggested negation of gravity/accelerometer fallbacks is NOT applied: per
  Android SensorEvent docs, a stationary flat device reads +9.81 on Z (the
  gravity reaction), matching the rotation-vector path as-is. The contract
  tests prove all paths agree.

38 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:45:53 -04:00
thejayman77 2a48d79c77 Scaffold Android project per BRIEF.md
Single-module Kotlin/Compose app with manual DI container. Pure-Kotlin
measurement math (orientation mapping, two-sample per-mode calibration,
EMA smoothing, lock hysteresis, display deadband) fully unit-tested.
Sensor fallback: game rotation vector -> gravity -> low-passed
accelerometer. Live Level (manual Surface|Edge) and Angle (hold-to-zero)
scaffolds; Ruler/Tools placeholders; stub Pro entitlement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 18:34:57 -04:00
thejayman77 bcda2856f7 Add authoritative v1 product & technical brief (Codex) 2026-07-11 18:25:38 -04:00
thejayman77 d29d9336f4 Add concept board, v1 brief audit, and .gitignore
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 17:52:31 -04:00