Skip to content

Sensor-fusion polish: calibration badge, 3D replay, richer CSV, chart-label fixes#25

Open
lkasso wants to merge 8 commits into
mainfrom
fusion-polish
Open

Sensor-fusion polish: calibration badge, 3D replay, richer CSV, chart-label fixes#25
lkasso wants to merge 8 commits into
mainfrom
fusion-polish

Conversation

@lkasso

@lkasso lkasso commented Jul 22, 2026

Copy link
Copy Markdown
Member

The fusion follow-up planned after the group-logging arc. Three pieces (the real MetaMotion.usdz is deliberately NOT here — the loader already picks it up from the bundle the moment the file lands, so it ships separately whenever we have it):

Calibration badge (Live Stream)

While any fusion channel streams, a badge shows the chip's own 0–3 calibration accuracy per sensor — A/G/M chips colored by level — with a rotate-to-calibrate hint until everything reads high. Heading is garbage before calibration and the app never said so.

  • Poll ([0x19, 0x8B], every 2 s) starts/stops with the streams — the read is only valid while fusion runs. Failures silently stop the badge; they never touch the streams.
  • Gated on sensor-fusion revision ≥ 1. Demo boards answer all-high, so it's simulator-visible.

3D replay of saved quaternion sessions

Session detail for a quaternion session now replays the recorded motion in the same 3D board view the live stream uses: play/pause, scrubber, 1×/2×/4× speed. Timing comes from the board's own tick clock (normalized, clamped monotonic). ReplayTimeline (the position→sample binary search) is unit-tested at the boundaries.

Richer CSV + label fixes

  • Quaternion exports (logged sessions and live-buffer exports) append derived heading,pitch,roll after w,x,y,z. No derived yaw — the firmware's yaw is gyro-integrated and can't be reconstructed from one quaternion; it would just duplicate heading.
  • Bug fix: saved-session charts labeled every trace with generic x/y/z/w — quaternion w rendered as "x", Euler channels as "x/y/z/w". Styles now recover the real sensor (labels, units, captured ± range) from the persisted kind + capture-time label.
  • Deletes dead SessionHistoryViewModel.

Validation

  • SPM suite: 1068 tests green (incl. new quaternion→Euler math tests: identity, 90° rotations, heading normalization, gimbal-pole clamp).
  • App tests green (new ReplayTimelineTests, SessionAxisStyleTests).
  • Worth a hardware pass: stream fusion on the MMS and watch the badge climb as you rotate; log a quaternion session and scrub the replay.

🤖 Generated with Claude Code

lkasso and others added 8 commits July 22, 2026 10:15
Users who think in Euler angles had to do quaternion math in a
spreadsheet. The exporter now appends host-computed Tait-Bryan angles
(heading 0-360, pitch +/-90, roll +/-180) after the raw w/x/y/z columns,
in both logged-session and live-buffer CSVs. Derivation happens at
export via a new MWDataConvertible hook so stored samples stay raw.

No derived yaw column on purpose: the firmware's yaw channel is
gyro-INTEGRATED (unbounded, drifts) and cannot be reconstructed from a
single orientation quaternion - it would just duplicate heading.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bosch fusion outputs (especially heading) are unreliable until the
algorithm calibrates against real motion, and the app never said so -
users had no way to know their orientation data was garbage. While any
fusion channel streams, a badge now shows the chip's own 0-3 accuracy
per sensor (A/G/M chips colored by level) with a rotate-to-calibrate
hint until everything reads high.

The read ([0x19, 0x8B]) is only valid while fusion is RUNNING, which is
exactly when the badge shows: polling starts with the streams and ends
with them (pause included). Any poll failure just stops the updates -
a courtesy readout must never tear down the streams. Gated on sensor-
fusion revision >= 1 (C++ CALIBRATION_REVISION). The demo transport
answers all-high, so the badge is simulator-testable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two fixes for saved sessions:

1. The detail chart styled everything with generic x/y/z/w labels,
which actively MISLABELED fusion sessions - a quaternion's first
channel is w (rendered as "x"), and Euler channels are heading/pitch/
roll/yaw. Styles now resolve from the persisted kind (fusion types) or
the capture-time label (units, colors, and the captured +/-range for
everything else), falling back to generic only for legacy records.

2. Quaternion sessions gain a 3D replay: the same board view the live
stream uses, driven by a playback clock instead of BLE, with play/
pause, a scrubber, and a 1x/2x/4x speed toggle. Timing comes from the
board's own tick clock (normalized 0-based; clamped monotonic so a
stray out-of-order tick can't break the scrub binary search).
QuaternionRealityView is a pure function of "latest sample", so replay
is just choosing which sample is latest - and it picks up the real
MetaMotion.usdz automatically once that ships.

Also deletes SessionHistoryViewModel, dead since the history screen
went direct-to-store.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
55%/45% of the container height crowded out the chart and replay
controls; 42%/35% (min 240pt) leaves the board plenty readable while
keeping the rest of the screen above the fold. Applies to both the
live view and session replay, which share this component.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Field test: all-green is nearly unreachable at a desk and doesn't
stick - the fusion algorithm continuously re-evaluates accuracy and
demotes the magnetometer the moment it sees field distortion, which
indoors is constant (laptop, monitor, chargers, metal). Nagging users
toward permanent green was asking the impossible.

Bosch's own guidance is that MEDIUM (2 of 3) is good enough to record,
so the badge now has three states: Calibrating (any sensor below
medium), Ready To Record (all at least medium - the blue state), and
Fully Calibrated (all high). While calibrating, each sensor below the
bar gets ITS OWN instruction - the three sensors calibrate with
different motions (accel: rest on each face; gyro: hold still; mag:
figure-8 away from electronics), so one generic "rotate the board"
line couldn't get anyone to green. The ready state explains that a
dipping M rating indoors is normal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Built from the owner's CAD STEP exports (100-050 bottom shell +
100-060 top-cover assembly with the button; 100-051 is the same top
without the button and was skipped). Pipeline: OpenCascade (cadquery)
tessellation at 0.05 mm -> SceneKit assembly with PBR materials (top
shell brighter/smoother than bottom so the seam reads) -> USDZ.
Both parts share one CAD frame so they align natively, and the case
already sits long-axis-Y / face-toward-+Z - the exact orientation
QuaternionRealityView expects, so no axis correction needed.

The loader finds it by name and the synchronized project group
bundles it - zero code change; the procedural board remains as the
fallback. CAD meshes carry no textures, so the shells are tinted
white in-material like the physical case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The top-cover STEP is an assembly of three solids (front cover,
button, LED light pipe) that the first conversion flattened into one
white mesh. Exploding the compound lets each part carry its own
material: grey button, glossier lens-like LED window, white shells -
matching the physical device.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hardware test: the model's rest pose was right but rotations came out
about the wrong axes - physical yaw rolled the model, etc. The fusion
quaternion lives in Bosch's Z-up world; RealityKit is Y-up with Z
toward the camera. Applying the raw quaternion mixed the two frames.

Fix is a change of basis: conjugate by the fixed -90-about-X rotation
(q' = M q M^-1) that sends Earth-up to screen-up. Conjugation - not a
pre-multiply - is the load-bearing choice: identity conjugates to
identity, which is exactly the observed symptom pattern (rest pose
correct, axes switched). Also guards against zero/malformed samples
that would produce NaN orientations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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