Skip to content

fix(sdk): set EuRoC extrinsics + gravity-align init; surface VIO divergence - #209

Merged
Ravenwater merged 4 commits into
mainfrom
fix/vio-init-divergence-diagnostics
May 29, 2026
Merged

fix(sdk): set EuRoC extrinsics + gravity-align init; surface VIO divergence#209
Ravenwater merged 4 commits into
mainfrom
fix/vio-init-divergence-diagnostics

Conversation

@Ravenwater

@Ravenwater Ravenwater commented May 29, 2026

Copy link
Copy Markdown
Contributor

Problem

vio_bench diverged catastrophically on EuRoC V1_01_easyATE 26,574 m against a 0.5 m gate — and nothing surfaced the failure until a full ~13-minute run printed the giant number.

Root cause (two faults)

  1. Missing camera↔IMU extrinsics (dominant cause). vio_bench set intrinsics but left cal.extrinsics at identity. EuRoC cam0 T_BS is a ~90° rotation + ~7 cm offset from the IMU body frame, so every camera update was geometrically inconsistent with the IMU-propagated pose and corrupted the filter.
  2. Identity-attitude init fallback. When the static window never settled, MsckfBackend seeded an identity attitude. On a tilted/airborne start that leaves gravity uncancelled in propagation (a_world = R·a + gravity) → quadratic position blowup. (Not the trigger for V1_01_easy specifically — static init does succeed there — but a real latent bug for genuinely moving starts.)

Fix

  • Extrinsics: vio_bench now sets the published cam0 T_BS (matrix→quaternion helper, since SO3 has no matrix ctor).
  • Init: new ImuInitializer::try_gravity_align — roll/pitch from the mean specific force when no static window exists (yaw/biases stay zero, gyro mean trusted as bias only when rotation-quiet). Replaces the blind identity fallback. Full visual-inertial init (try_dynamic) needs an SfM/two-view bootstrap that doesn't exist yet — left as a documented seam.

Diagnostics (identify failure modes immediately)

  • MsckfBackend::init_diagnostics() — init method (static/gravity_align/identity), platform tilt, gravity residual, biases.
  • vio_bench per-frame divergence guard + top-line verdict (✅ OK / ⚠️ above-gate / ❌ DIVERGED at frame N), an Estimator health report section (markdown + JSON), and a --fail-fast flag.

These paid off immediately: the health block reported init method static, which is what pinned the real cause on the extrinsics rather than the init fallback I first suspected.

Result (V1_01_easy)

before after
Verdict (diverged) ✅ OK
ATE (m) 26,574 0.305 (gate 0.5)
Init method static (tilt 112°, gravity residual 0.0018)

Test

  • New try_gravity_align unit tests: moving-start recovery (<5° gravity error) and free-fall rejection.
  • Full suite green on clang and gcc — 151 tests, 0 failures.
  • End-to-end V1_01_easy run confirms ATE within gate.

Note

Branched off main; touches bench/src/vio_bench.cpp, which also changes in #208 (progress output + docs). Expect a small merge conflict in the bench loop — whichever merges first, I'll rebase the other.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Estimator health diagnostics added (init method, tilt/gravity residual, extrinsics-identity flag, divergence frame/time, peak speed/max position, accuracy availability); exposed in JSON and as a top-line Verdict plus an Estimator Health section in reports.
    • Gravity-aligned IMU initialization with configurable tolerance.
    • Fail-fast CLI option to stop processing on divergence.
  • Tests

    • Added tests for gravity-align behavior and tilted-start end-to-end initialization.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Walkthrough

Adds gravity-direction-only IMU bootstrap and init telemetry; rewrites MSCKF init flow to prefer gravity-align after timeout; adds tests for gravity-align; instruments vio_bench with divergence detection, EuRoC extrinsics, --fail-fast, and serializes estimator health in BenchReport.

Changes

Estimator Initialization Diagnostics & Health Tracking

Layer / File(s) Summary
SDK initialization telemetry types and gravity-align config
sdk/include/branes/sdk/msckf_backend.hpp, sdk/include/branes/sdk/imu_init.hpp
Adds InitMethod enum, InitDiagnostics<T> struct, init_diag_ storage and init_diagnostics() accessor; adds ImuInitConfig::gravity_align_tol.
IMU gravity-align bootstrap implementation
sdk/include/branes/sdk/imu_init.hpp, tests/sdk_imu_init_test.cpp
Implements ImuInitializer::try_gravity_align (window/shape checks, gravity-magnitude gating, R_world_imu from accel mean, conditional gyro_bias assignment) and two unit tests (success/failure).
MSCKF backend initialization orchestration
sdk/include/branes/sdk/msckf_backend.hpp, tests/sdk_msckf_backend_test.cpp
Reworks try_initialize to use a sliding window + init_samples_seen_, prefers gravity-align after timeout, adds apply_init to seed state and compute diagnostics, resets diagnostics on initialize(), and exposes telemetry via init_diagnostics(). End-to-end test asserts gravity-align path.
Benchmark tool divergence detection and diagnostics capture
bench/src/vio_bench.cpp
Adds <cmath> and so3_from_matrix, applies EuRoC cam0→IMU extrinsics, adds --fail-fast, computes per-frame speed/position, tracks peaks, detects and records first divergence frame/time, optionally aborts early, computes sequence duration from processed frames, sets accuracy_available, and populates the final report with init diagnostics and kinematic metrics.
BenchReport health field structure and serialization
bench/include/branes/bench/report.hpp
Adds health fields (accuracy_available, init_method, init_tilt_deg, init_gravity_residual, extrinsics_identity, diverged, divergence_frame, divergence_time_s, peak_speed_mps, max_position_m) and serializes them under a top-level health JSON object; Markdown adds a top-line “Verdict”, an “Estimator health” table, and updates accuracy/energy rows for unavailable accuracy.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • branes-ai/cortex#201: Both PRs modify bench/src/vio_bench.cpp and bench/include/branes/bench/report.hpp; this PR extends BenchReport with estimator health metrics while that PR adjusted benchmark reporting surfaces.
  • branes-ai/cortex#184: Extends the same ImuInitializer and IMU init system by introducing related bootstrap wiring used by this change.
  • branes-ai/cortex#186: Related MSCKF backend initialization changes; both PRs touch sdk/include/branes/sdk/msckf_backend.hpp init orchestration and telemetry.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(sdk): set EuRoC extrinsics + gravity-align init; surface VIO divergence' clearly maps to the main changeset: extrinsics setup, gravity-alignment initialization fallback, and divergence diagnostics across bench/sdk.
Description check ✅ Passed The description provides Problem, Root Cause, Fix, Diagnostics, Results, and Test coverage with a concrete V1_01_easy benchmark comparison. It follows the template structure with Summary, Changes, Test Results table, and Checklist. All required sections present.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/vio-init-divergence-diagnostics

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bench/include/branes/bench/report.hpp`:
- Line 197: The health-table label string written to the output stream contains
raw pipe characters causing Markdown table mis-parsing; update the stream
insertion that emits the label (the line using os << "| Max |position| (m) | "
<< r.max_position_m << " |\n\n"; referencing r.max_position_m) to escape the
pipe characters (e.g., replace '|' with '\|' in the literal) so the Markdown
renderer treats them as literal characters rather than column separators.

In `@bench/src/vio_bench.cpp`:
- Around line 357-366: Don't hard-code r.extrinsics_identity = false; instead
query the estimator's stored calibration/extrinsics (e.g. via
est.backend().calibration() or whatever holds the cam0 extrinsics exposed by
est.backend(), or init.extrinsics if present) and set r.extrinsics_identity by
comparing that extrinsics to an identity transform within a small tolerance:
check that rotation is close to identity (e.g. quaternion ~ [1,0,0,0] or
rotation matrix ~ I) and translation is near zero, and assign true only when
both checks pass; use the same numeric tolerance used elsewhere in the codebase
for pose comparisons.
- Around line 281-302: The code currently pushes est.current_pose() into traj
(traj.push_back) and updates peak_speed/max_pos before performing the divergence
guard, which can record NaN/Inf or extreme values; reorder the logic so the
divergence check using ns, speed, dist and the divergence handling (setting
diverged, divergence_frame, divergence_time_s, printing, and fail_fast behavior)
runs before any mutation of traj, peak_speed, or max_pos, and only when the
state passes that check do you append to traj and update peak_speed/max_pos (or
skip recording entirely if diverged); adjust uses of frame_idx and fail_fast
exactly as currently implemented so behavior on early exit remains unchanged.

In `@sdk/include/branes/sdk/msckf_backend.hpp`:
- Around line 275-300: The code erases one sample on every failed static attempt
(init_gyro_/init_accel_.erase(...)) so the buffer never reaches kInitMaxSamples
and the fallback at line 286 (gravity-align/identity bootstrap via
initializer_.try_gravity_align and apply_init/InitMethod) is never reached; stop
removing a sample on each failed check and instead only trim the buffer when you
append a new sample and its size exceeds kInitMaxSamples (or remove the erase
branch entirely so the window can grow to kInitMaxSamples), ensuring init_gyro_
and init_accel_ can reach kInitMaxSamples and trigger
try_gravity_align/apply_init.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 08ca0e5c-0288-45c7-a61e-4f34f73fb9c1

📥 Commits

Reviewing files that changed from the base of the PR and between e257a25 and 401cab0.

📒 Files selected for processing (5)
  • bench/include/branes/bench/report.hpp
  • bench/src/vio_bench.cpp
  • sdk/include/branes/sdk/imu_init.hpp
  • sdk/include/branes/sdk/msckf_backend.hpp
  • tests/sdk_imu_init_test.cpp

Comment thread bench/include/branes/bench/report.hpp Outdated
Comment thread bench/src/vio_bench.cpp Outdated
Comment thread bench/src/vio_bench.cpp Outdated
Comment thread sdk/include/branes/sdk/msckf_backend.hpp Outdated
@Ravenwater Ravenwater self-assigned this May 29, 2026
@Ravenwater Ravenwater added the bug Something isn't working label May 29, 2026
@Ravenwater Ravenwater added this to the Phase 3: VIO milestone May 29, 2026
Ravenwater and others added 2 commits May 29, 2026 14:56
…rgence

vio_bench diverged catastrophically on V1_01_easy (ATE 26,574 m vs a 0.5 m
gate). Two faults, plus no way to see either without a full 13-minute run:

1. vio_bench never set the camera↔IMU extrinsics, so every camera update
   was geometrically inconsistent with the IMU-propagated pose (EuRoC cam0
   is rotated ~90° + offset ~7 cm from the body). This was the dominant
   cause here. Set the published cam0 T_BS (matrix→quaternion).

2. MsckfBackend's init fallback used a blind identity attitude when the
   static window never settled. On a tilted/airborne start that leaves
   gravity uncancelled in propagation → quadratic position blowup. Replace
   it with ImuInitializer::try_gravity_align: roll/pitch from the mean
   specific force (yaw/bias stay zero). A robustness fix for moving starts;
   full visual-inertial (try_dynamic) needs an SfM bootstrap — see the
   documented seam.

Diagnostics so a bad run is identified immediately, not inferred from ATE:
- MsckfBackend::init_diagnostics() — method (static/gravity_align/identity),
  platform tilt, gravity residual, biases.
- vio_bench per-frame divergence guard + a top-line verdict (OK / above-gate
  / DIVERGED at frame N), an "Estimator health" report section, --fail-fast.

Result on V1_01_easy: ATE 26,574 m → 0.305 m (within gate); the health block
correctly reports init method `static`, which is what pinned the real cause
on the extrinsics rather than the init fallback.

Tests: new try_gravity_align coverage (moving-start recovery + free-fall
rejection); full suite green on clang and gcc (151 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nsics flag

CodeRabbit review on #209:

- msckf_backend: the init buffer was trimmed on every failed static attempt,
  pinning it at kInitSamples so it never reached kInitMaxSamples — the
  gravity-align/identity fallback was unreachable (the bug predates this PR;
  it made the new gravity-align path dead code). Slide a fixed kInitSamples
  window for the static check and count total samples seen for the timeout,
  so the fallback fires. New backend test: a never-stationary tilted start
  now initializes via GravityAlign within a few degrees.
- vio_bench: derive extrinsics_identity from the built calibration (rotation
  angle ≈ 0 and translation ≈ 0) instead of asserting a constant, so it stays
  truthful if the extrinsics are ever dropped.
- report: reword the "Max position norm (m)" health label — the old label
  contained raw '|' characters that broke the Markdown table.

Not changed: the divergence-guard ordering. peak_speed/max_pos are meant to
capture the divergence spike, so they must update on the offending frame;
std::max(running, NaN) keeps the running value and the verdict supersedes a
NaN ATE — reordering to skip recording would hide the magnitude we report.

Tests green on clang and gcc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ravenwater
Ravenwater force-pushed the fix/vio-init-divergence-diagnostics branch from 401cab0 to f34ce0c Compare May 29, 2026 19:01
@Ravenwater

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — addressed 3 of 4, and rebased onto main (resolving the vio_bench.cpp conflict with #208).

  • msckf_backend.hpp (init buffer never reaches kInitMaxSamples) — correct, and it was the important one: the per-miss erase pinned the window at kInitSamples, so the gravity-align/identity fallback was unreachable (the new path was effectively dead code; the bug predates this PR). Fixed by sliding a fixed kInitSamples window for the static check and counting total samples seen for the timeout. Added a backend test: a never-stationary tilted start now initializes via GravityAlign within a few degrees.
  • vio_bench.cpp (hardcoded extrinsics_identity) — now derived from the built calibration (rotation angle ≈ 0 ∧ translation ≈ 0), so it stays truthful if the extrinsics are ever dropped.
  • report.hpp (raw pipes in the table label) — reworded to Max position norm (m).

Not changed — divergence-guard ordering (vio_bench.cpp): peak_speed/max_pos are intended to capture the divergence spike (the report shows "peak speed X m/s"), so they must update on the offending frame. std::max(running, NaN) returns the running value (no NaN propagation) and +Inf surfaces correctly, and the verdict supersedes any NaN ATE — so reordering to skip recording would hide the very magnitude being reported.

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@Ravenwater — clean resolution across the board.

Fixes acknowledged:

  • msckf_backend.hpp (kInitMaxSamples): The root cause — per-miss erase pinning the window at kInitSamples and making the gravity-align/identity fallback dead code — is the most impactful fix here. Sliding the static-check window while advancing a separate total-sample counter is the correct pattern. Good that a backend test now exercises the GravityAlign path on a never-stationary tilted start.
  • vio_bench.cpp (extrinsics_identity): Deriving from calibration rotation-angle and translation norms is more robust than a compile-time constant; it stays truthful across future config changes.
  • report.hpp (raw pipes): Confirmed.

Divergence-guard ordering — accepted.
Your reasoning is sound: std::max(running, NaN) returns running under IEEE 754 (because NaN < running is false, so NaN loses the comparison), while +Inf propagates correctly. Reordering to skip the update on the flagged frame would drop exactly the magnitude the health report is meant to surface. The verdict/ATE superseding logic handles the NaN ATE case cleanly.

🐇✓


✏️ Learnings added
Learnt from: Ravenwater
URL: https://github.com/branes-ai/cortex/pull/209

Timestamp: 2026-05-29T19:02:13.851Z
Learning: In branes-ai/cortex (PR `#209`), `peak_speed`/`max_pos` in `bench/src/vio_bench.cpp` are intentionally updated *before* the divergence guard fires so the report captures the offending frame's magnitude. `std::max(running, NaN)` safely returns `running` (IEEE 754 / C++ `<`-based max), and `+Inf` propagates correctly. Do not suggest reordering these updates to skip the divergence frame.

Learnt from: Ravenwater
URL: https://github.com/branes-ai/cortex/pull/209

Timestamp: 2026-05-29T19:02:13.851Z
Learning: In branes-ai/cortex `sdk/include/branes/sdk/msckf_backend.hpp`, the original init buffer bug (pre-PR `#209`) caused a per-miss `erase` that pinned the sliding window at `kInitSamples`, making the gravity-align and identity fallback paths permanently unreachable (dead code). The fix slides a fixed `kInitSamples` window for the static check and uses a separate total-sample counter for the `kInitMaxSamples` timeout.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bench/include/branes/bench/report.hpp`:
- Around line 201-203: The message printed when r.init_method == "gravity_align"
incorrectly states biases start at zero; update the output string in the
gravity-align branch (the conditional checking r.init_method == "gravity_align")
to explicitly state that roll/pitch are set from mean specific force and yaw is
initialized to zero, and mention that biases may be non-zero (or optionally
seeded) depending on motion—i.e., separate the definitive "yaw starts at zero"
claim from any bias behavior and remove the absolute "biases start at zero"
wording so the note accurately reflects conditional bias initialization.
- Around line 184-187: The report currently treats r.accuracy_within_gate==false
the same whether accuracy is truly above the gate or accuracy is unavailable,
causing misleading "ATE 0 m above gate" output; update the reporting logic for
the Report struct (symbols: r.accuracy_within_gate, r.ate_m, r.ate_gate_m) by
adding and checking a new flag (e.g., r.accuracy_available or
r.has_ground_truth) or a dedicated verdict path for unavailable accuracy metrics
and print a distinct message like "accuracy unavailable / ground truth missing"
when that flag is false, otherwise keep the existing OK vs above-gate branches.
Ensure the new flag is set by the code that computes ATE (e.g., vio_bench.cpp)
so unavailable ground-truth does not default to ate_m==0 and a false
within_gate.

In `@bench/src/vio_bench.cpp`:
- Around line 301-308: When fail_fast triggers inside the divergence check (the
block that sets diverged, divergence_frame, divergence_time_s and optionally
breaks when fail_fast is true), record the timestamp of the last processed frame
(e.g., last_included_time_s or last_frame_t_s) before breaking and then compute
sequence_duration_s from images.front().t_s to that recorded timestamp instead
of images.back().t_s; ensure any reported real_time_factor and other
duration-derived metrics use that last-included timestamp so partial runs report
duration consistent with the frames actually processed. Also update any code
that currently derives sequence_duration_s from images.back() to prefer the
recorded last_included_time_s when diverged && fail_fast was used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 41a0b2cd-0d84-4c56-b685-9eddd594546e

📥 Commits

Reviewing files that changed from the base of the PR and between 401cab0 and f34ce0c.

📒 Files selected for processing (6)
  • bench/include/branes/bench/report.hpp
  • bench/src/vio_bench.cpp
  • sdk/include/branes/sdk/imu_init.hpp
  • sdk/include/branes/sdk/msckf_backend.hpp
  • tests/sdk_imu_init_test.cpp
  • tests/sdk_msckf_backend_test.cpp

Comment thread bench/include/branes/bench/report.hpp
Comment thread bench/include/branes/bench/report.hpp Outdated
Comment thread bench/src/vio_bench.cpp
…rtial-run duration

CodeRabbit incremental review on #209:

- Distinguish "accuracy unavailable" from "ATE above gate". With no usable
  ground truth, ate_m stayed 0 and the verdict printed "ATE 0 m above gate",
  hiding the real failure. Add BenchReport::accuracy_available (set from the
  ground-truth match count) and a dedicated verdict branch.
- --fail-fast reported the full-sequence duration for a partial run, inflating
  real_time_factor. Derive sequence_duration_s from the span of frames actually
  processed (traj first→last) instead of images.front()/back().
- Reword the gravity-align health note: roll/pitch from mean specific force and
  yaw start at zero, but the gyro bias is seeded only when the window is
  rotation-quiet (else zero) — the old note wrongly said biases are always zero.

Builds + bench tests green on clang and gcc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ravenwater

Copy link
Copy Markdown
Contributor Author

Round 2 addressed (incremental review):

  • Accuracy unavailable vs above-gate — added accuracy_available to the report (from the ground-truth match count) with a dedicated verdict branch, so a missing-GT run no longer prints "ATE 0 m above gate".
  • --fail-fast partial-run durationsequence_duration_s is now the span of frames actually processed (traj first→last) instead of images.front()/back(), so real_time_factor isn't inflated on an early exit.
  • Gravity-align note — reworded: roll/pitch from mean specific force and yaw start at zero, but the gyro bias is seeded only when the window is rotation-quiet (else zero).

Builds + bench tests green on clang and gcc.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bench/include/branes/bench/report.hpp`:
- Around line 186-188: The report currently prints zeroed ATE/RPE and marks
Energy as "ATE above gate" even when r.accuracy_available is false; update all
places that render the Accuracy table and the Energy verdict (references: the
variables/fields r.accuracy_available, r.ate, r.rpe and the functions/sections
that write the Accuracy table and Energy summary in report.hpp) to check
r.accuracy_available first and, if false, emit a clear "unavailable" / "n/a"
marker (and skip showing or evaluating numeric ATE/RPE or gate-comparison logic)
so the top-line verdict, Accuracy table, and Energy section consistently
indicate accuracy is unavailable rather than showing zeros or failed-gate text;
apply the same conditional logic where ATE/RPE are printed or used (the other
similar blocks flagged in the comment).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5f484544-a4c1-497d-9c59-8a7fdeaf1e9e

📥 Commits

Reviewing files that changed from the base of the PR and between f34ce0c and 3002dd9.

📒 Files selected for processing (2)
  • bench/include/branes/bench/report.hpp
  • bench/src/vio_bench.cpp

Comment thread bench/include/branes/bench/report.hpp
…eport

CodeRabbit follow-up on #209: the top-line verdict handled missing ground
truth, but the Accuracy table still printed zeroed ATE/RPE and the Energy
section still read "n/a (ATE above gate)" — contradicting the verdict. Gate
both on accuracy_available: the table shows "n/a (no usable ground truth)" and
the Intelligence/Watt line shows "n/a (accuracy unavailable)". (JSON keeps the
numeric fields, which already carry an explicit `available` flag.)

Bench tests green on clang and gcc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ravenwater

Copy link
Copy Markdown
Contributor Author

Round 3: applied accuracy_available consistently across the Markdown report — the Accuracy table now shows "n/a (no usable ground truth)" and the Energy/Intelligence-per-Watt line shows "n/a (accuracy unavailable)", so the verdict, table, and energy section agree. Bench tests green on clang + gcc.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
bench/include/branes/bench/report.hpp (1)

150-151: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Non-finite floats produce invalid JSON when the estimator diverges.

json_kv streams doubles directly, so NaN/Inf render as nan/inf — tokens that are not valid JSON. PR objectives confirm these values can occur when divergence happens. Downstream JSON parsers will choke even though diverged: true is present.

Consider sanitizing non-finite values (emit null or a sentinel like 1e308) in json_kv, or add a dedicated overload for fields that can blow up:

Proposed helper for safe double output
 inline void json_kv(std::ostream& os, const char* k, double v, bool comma = true) {
-    os << "    \"" << k << "\": " << v << (comma ? ",\n" : "\n");
+    os << "    \"" << k << "\": ";
+    if (std::isfinite(v))
+        os << v;
+    else
+        os << "null";  // or a finite sentinel; null keeps JSON valid
+    os << (comma ? ",\n" : "\n");
 }

(Requires #include <cmath> for std::isfinite.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/include/branes/bench/report.hpp` around lines 150 - 151, The JSON
writer is emitting non-finite doubles (NaN/Inf) for fields like peak_speed_mps
and max_position_m which produces invalid JSON; update detail::json_kv to
sanitize double values by checking std::isfinite (include <cmath>) and, when
non-finite, emit JSON null (or a chosen sentinel) instead of streaming the raw
value, or add a dedicated overload detail::json_kv(os, key, double val, ...)
that performs this finite-check and uses it for fields that can diverge (e.g.,
calls from the report serialization for r.peak_speed_mps and r.max_position_m).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@bench/include/branes/bench/report.hpp`:
- Around line 150-151: The JSON writer is emitting non-finite doubles (NaN/Inf)
for fields like peak_speed_mps and max_position_m which produces invalid JSON;
update detail::json_kv to sanitize double values by checking std::isfinite
(include <cmath>) and, when non-finite, emit JSON null (or a chosen sentinel)
instead of streaming the raw value, or add a dedicated overload
detail::json_kv(os, key, double val, ...) that performs this finite-check and
uses it for fields that can diverge (e.g., calls from the report serialization
for r.peak_speed_mps and r.max_position_m).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 71eb6824-2a4a-4832-984e-5b7fc3c4858a

📥 Commits

Reviewing files that changed from the base of the PR and between 3002dd9 and 1d53d10.

📒 Files selected for processing (1)
  • bench/include/branes/bench/report.hpp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant