# Surface Level — Implementation Attack Plan **Status:** Audited; ready for implementation after this revision. **Scope:** Surface Level only. Do not change Angle, Edge, ruler, or billing in this stage. ## Outcome Build a trustworthy two-axis surface-level tool for a phone lying flat, screen-up, on a surface. It must feel like a physical bullseye vial while giving clearer, more actionable information than a physical level. The user should be able to answer three questions at a glance or by sound: 1. Is this surface level? 2. How far off is it? 3. Which side/corner needs adjustment, and by roughly how much? ## Existing foundation to preserve - Unified device-frame world-up sensor contract. - Raw sensor -> stable measurement -> animated display separation. - Vector-space Surface calibration model and two-sample calibration math. - EMA smoothing, lock hysteresis/dwell/debounce, deadband, and current tests. - The current numeric Surface reading, pitch/roll values, and haptic lock hook. ## Product decisions to implement - Surface means the phone is intended to lie flat, screen-up, on the plane being measured. Its level-lock behavior is valid only in that geometry. - Numeric measurements are never hidden because the phone is steeply oriented. Surface tilt magnitude remains authoritative from 0 to 180 degrees. Outside the ideal face-up geometry, show the actual magnitude and a quiet placement hint; suppress level-lock feedback rather than replacing the reading with `—`. - Above 80 degrees of tilt, de-emphasize/suppress Pitch and Roll because their off-axis parameterization becomes noisy near vertical. Above 90 degrees, identify the screen-down-side state rather than pretending the bullseye is a useful placement tool. The magnitude remains visible in both cases. - The central visualization is a bullseye vial. It has a finite visual range: beyond that range the bubble rests at the rim, while numeric information remains full-range and authoritative. - Calibration corrects device/setup bias. It is not "make this desk level." A later relative-reference feature is separate and out of scope here. ## Work sequence ### 1. Make measurement behavior honest at every angle - Refactor Surface placement gating so it only controls the level-lock state, lock haptic, and geometry-specific guidance. - Keep the primary tilt magnitude visible from 0-180 degrees. Keep Pitch and Roll visible only where their meaning is stable (below 80 degrees tilt). - Add tests at flat, near level, 30 degrees, 80 degrees, near vertical, and screen-down. Verify magnitude stays correct, off-axis values are suppressed in their ambiguous range, and invalid placement never locks. - Keep the existing lock tolerance semantics: a lock label states the tolerance rather than pretending any non-zero value is exactly flat. ### 2. Add the guided Surface calibration flow - Add a Surface Calibration route from the Level header. - Explain that the phone may be calibrated on any firm, stable, *roughly level* surface; it need not be perfectly level. Warn and ask for a flatter surface when the measured tilt exceeds 5 degrees. - Capture the two samples from the uncalibrated sensor path. Never derive a new calibration from readings that an existing calibration has already corrected. - Step 1: settle, collect a 1.5-second stable window, capture. - Step 2: rotate the phone exactly 180 degrees in the same plane, settle, then collect and capture another 1.5-second stable window. - Use the shared settling detector: enter Settling above 1.0 degrees/second of two-axis plane movement; leave it only after movement is at or below 0.3 degrees/second for 500 ms. Reject/retry windows that are not settled. - Reject/retry a derived calibration bias above 3 degrees as evidence that the device, setup, or turn was inconsistent. Suppress lock feedback during the flow. - Persist only the resulting Surface calibration; never alter Edge calibration. - State that the calibration applies to the current phone/case/contact setup. - On completion, show a concise success result and a way to clear/re-run it. ### 3. Build the tactile bullseye instrument - Use Compose Canvas (or an equivalent composable drawing layer) for a scalable circular vial, etched rings, crosshairs, target zone, and bubble. - Glass, fluid, and meniscus effects must improve depth/readability; avoid a decorative or excessively glossy treatment. - Bubble target position derives directly from stable calibrated pitch/roll. - Derive bubble target, high/low instruction, and rise/run direction from one pure SurfaceGuidance function. Its hysteretic next-state input must prevent quadrant labels from flapping at an axis boundary. Use an edge label when the minor axis is under 25% of the dominant axis; otherwise use a corner label. - Bubble movement uses a critically damped spring (damping ratio >= 1) toward that real target: no overshoot, random drift, autonomous wobble, or animation that contradicts the number. Clamp the target to the vial's visual range before applying the spring so the bubble settles at the rim. - Etched rings are real visual calibration marks, not decoration: map them to explicit degree values (initially 1, 2, and 5 degrees) using the same pure measurement-to-position mapping as the bubble. - Respect disabled system animations and the app's reduced-motion preference by direct-positioning the deadbanded bubble value instead of running the spring. - Show a large total tilt beside/below the vial, then Pitch and Roll below it. - At level lock, use the restrained lime state and an explicit text label. ### 4. Add actionable adjustment information - Derive a screen-relative high/low direction from calibrated Pitch and Roll. Use language such as `High: lower-left`; do not claim a compass direction. - Display slope as both degrees and a useful normalized rise/run value: millimetres per metre and/or inches per foot based on the selected unit system. - Treat exact board/counter dimensions and calculated shim/foot height as a later Adjustment Assist feature, not a requirement of this stage. - Use the shared settling detector from calibration. When it is settling, show a subtle `Settling` state and defer strong correction guidance until the stable measurement is trustworthy. - Add a persisted units preference in core/settings for rise/run formatting; the Tools settings UI remains deferred. ### 5. Add optional hands-free Audio Level Assist - Surface-only, off by default, explicitly user-enabled. - Use a soft sonar-like pulse with a gentle attack and naturally fading tail; never a harsh alarm or rapid smoke-detector cadence. - Silent while the shared settling detector is active or materially outside the usable placement geometry. - Near level: occasional low-key proximity pulses with hysteresis between audio proximity bands, so band changes cannot chatter. - Locked: one distinct soft lock ping; optionally provide a sparse repeat-while- locked preference so a user adjusting a large plank can hear that it remains level. - Reuse the existing lock dwell/hysteresis/debounce so sound cannot chatter. - Use SoundPool with `USAGE_ASSISTANCE_SONIFICATION`; mix with media rather than taking audio focus for intermittent pulses. Respect media volume, system sound settings, app lifecycle, and the user's Audio Assist preference. Release audio resources with the tool composable/flow collection. ## Surface acceptance criteria - A stable phone on a known level plane achieves lock without repeated feedback. - A 180-degree calibration from uncalibrated stable samples on a stable, roughly-level plane improves repeatability without zeroing that plane's real slope; inconsistent/moving captures are rejected. - A screen-up phone at 30, 80, near 90, and screen-down angles always shows a correct magnitude. Pitch/Roll are suppressed at their documented ambiguous range, and the Surface tool never falsely claims a lock. - Bubble direction, high/low instruction, and rise/run direction agree in all four quadrants through one pure, unit-tested guidance function. - Bubble position is a pure mapping of stable measurement plus a no-overshoot spring. Its etched rings match that mapping's documented degree marks. - Audio can guide a user to lock without viewing the screen and stays quiet when disabled, settling, backgrounded, or outside the Surface geometry. - Manual checks cover phone-with-case, phone-without-case, a firm tabletop, and a large board adjusted by one person. ## Explicitly deferred - Angle-screen redesign and signed relative-angle semantics. - Edge/Plumb visual and geometry changes. - Saved measurements, target-angle alerts, screen ruler, and Play Billing. - Board dimension/shim calculator and automatic flat/edge switching. ## Questions for Claude's audit 1. Does the relaxed placement gate preserve truthful full-range Surface numbers while preventing false lock feedback? 2. Is the two-sample calibration flow mathematically and UX-wise safe for the stated use case, including phone cases and non-level calibration surfaces? 3. Is high/low mapping unambiguous and testable for all pitch/roll quadrants? 4. Does the bullseye animation remain derived from measurement truth and respect reduced-motion/system-animation settings? 5. Can the audio state machine reuse lock semantics without audio-focus, lifecycle, or repeated-feedback problems? 6. Is any proposed work outside this single-stage scope?