- 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>
- 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>
- 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>
- 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>