Skip to content

feat(tools): drive R-IEKF through the real EuRoC pipeline - #429

Merged
Ravenwater merged 3 commits into
mainfrom
feat/issue-365-riekf-euroc-harness
Jul 1, 2026
Merged

feat(tools): drive R-IEKF through the real EuRoC pipeline#429
Ravenwater merged 3 commits into
mainfrom
feat/issue-365-riekf-euroc-harness

Conversation

@Ravenwater

@Ravenwater Ravenwater commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the end-to-end harness that runs the right-invariant (R-IEKF) VIO backend (#347/#348) through the validated VioEstimator front-end / track manager on real EuRoC — the path #365 calls for, replacing the confounded hand-rolled Monte-Carlo loop. With the harness in place, R-IEKF and the standard MSCKF can be measured on the same sequence with the same front end.

This lands the infrastructure and an honest, inconclusive result — not a win. See "Result" below.

Changes

  • tools/include/branes/tools/invariant_backend_adapter.hpp (new) — a VioBackend-conforming wrapper bridging InvariantVioBackend's minimal API (set_nav / process_imu / process_camera-normalized / nav) to the VioEstimator contract. Bootstraps from a static, gravity-aligned IMU window; unprojects pixels → bearings; maps SE₂(3) → NavState. Uses the Joseph FullCovariance form (the backend default); the square-root form diverges on real data.
  • tools/src/vio_pipeline.cpp — a --invariant path (run_euroc_invariant) mirroring the standard EuRoC run, reporting ATE + mean position σ.
  • sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp (shipped SDK) — guard the update against a non-finite δx: snapshot the covariance and reject the track (as if it never happened) rather than abort in SO3::normalize on a degenerate real-data measurement. Strictly a crash→rejection conversion; healthy updates are unaffected.
  • tests/tools/invariant_backend_adapter.cpp (new) — gate: static-window bootstrap (levels gravity, zero velocity) + VioEstimator contract.

Result — R-IEKF vs standard MSCKF (same binary, real EuRoC)

Seq Backend ATE (Horn) pos σ ATE/σ
V1_01 MSCKF 0.29 m 0.136 m 2.13
V1_01 R-IEKF 1.31 m 0.076 m 17.3
V2_03 MSCKF 0.27 m 0.126 m 2.13
V2_03 R-IEKF 1.36 m 0.070 m 19.3

The R-IEKF now runs stably end to end (the FullCovariance switch fixed a 10⁴⁰-m divergence), but underperforms the standard MSCKF on both ATE and position-σ consistency. Critically, the position-norm ATE/σ ratio cannot isolate the yaw-gauge effect the R-IEKF targets — standard MSCKF is already ~calibrated at 2.1 on the position norm, because the leak is rotational. The Jacobian-level evidence (yaw leak 0.39 → 2e-16, merged in #426) remains the real proof of the fix.

Follow-up (the #365 acceptance criterion): a gauge-anchored attitude-NEES metric (yaw vs roll/pitch split) so the comparison measures rotational consistency where R-IEKF should win, plus seed/tuning parity. Tracked as the next step on #365.

Test results

Target gcc build gcc test clang build clang test
invariant_backend_adapter OK PASS (2/2) OK PASS (2/2)
vio_pipeline OK — (tool) OK — (tool)
msckf_invariant_backend (guard regression) OK PASS (5/5)

Test plan

  • Fast CI passes (gcc + clang CI_LITE)
  • CodeRabbit review addressed
  • Promote to ready when satisfied: `gh pr ready`

Relates to #365

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an optional right-invariant estimator execution path for the EuRoC workflow.
    • Added a new “mean position σ” metric to EuRoC results output, with updated run reporting (including adjusted NIS display behavior).
  • Bug Fixes
    • Improved robustness by detecting non-finite correction updates and rejecting the affected track while restoring the prior covariance.
  • Tests
    • Added automated coverage for the invariant backend adapter, including IMU bootstrapping and basic camera/IMU processing contract checks.

Adds the end-to-end harness that runs the right-invariant (R-IEKF) VIO
backend (#347/#348) through the validated VioEstimator front-end / track
manager on real EuRoC, so it can be compared head-to-head with the
standard MSCKF instead of the confounded hand-rolled Monte-Carlo loop.

  - invariant_backend_adapter.hpp: a VioBackend-conforming wrapper that
    bridges InvariantVioBackend's minimal API (set_nav / process_imu /
    process_camera-normalized / nav) to the VioEstimator contract. Boot-
    straps from a static, gravity-aligned IMU window; unprojects pixels to
    bearings; maps SE2(3) back to NavState. Uses the Joseph FullCovariance
    form (the backend default) — the square-root form diverges on real data.
  - vio_pipeline: a --invariant path (run_euroc_invariant) mirroring the
    standard EuRoC run, reporting ATE + mean position sigma so the two
    backends are measured on the same sequence with the same front end.
  - msckf_invariant_backend: guard the update against a non-finite dx —
    snapshot the covariance and reject the track (it never happened) rather
    than abort in SO3::normalize on a degenerate real-data measurement.

Result on V1_01 / V2_03: the R-IEKF now runs stably end to end, but
underperforms the standard MSCKF on both ATE (1.3 m vs 0.27 m) and
position-sigma consistency. The position-norm ATE/sigma ratio cannot
isolate the yaw-gauge effect the R-IEKF targets (standard MSCKF is already
~calibrated at 2.1 on position); a gauge-anchored attitude-NEES metric is
the needed next step (the #365 acceptance criterion) and is a follow-up.

Relates to #365

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

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 766d7124-b64e-4eca-b27c-7dab18e8694f

📥 Commits

Reviewing files that changed from the base of the PR and between 6914e50 and c14f025.

📒 Files selected for processing (1)
  • tests/tools/invariant_backend_adapter.cpp

Walkthrough

Adds an invariant backend adapter, a new invariant EuRoC execution path, mean position sigma reporting, adapter tests, and a non-finite correction guard in the invariant MSCKF backend.

Changes

Invariant backend adapter and EuRoC pipeline

Layer / File(s) Summary
MsckfInvariantBackend update guard
sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp
Imports <cmath> and changes update() to snapshot covariance before cov_.update(...), reject non-finite correction vectors, restore the snapshot, and return false instead of retracting.
InvariantBackendAdapter bootstrap
tools/include/branes/tools/invariant_backend_adapter.hpp
Defines the adapter template, stores camera and IMU-camera calibration, configures backend initialization, accumulates IMU samples, and seeds the backend after static and gravity-aligned bootstrap succeeds.
InvariantBackendAdapter camera and state mapping
tools/include/branes/tools/invariant_backend_adapter.hpp
Filters camera observations, unprojects pixels to bearings, drops back-facing rays, converts observations for the invariant backend, and maps the backend SE(2,3) state into sdk::NavState<T> plus telemetry accessors.
EuRoC calibration and mean position sigma
tools/src/vio_pipeline.cpp
Adds shared EuRoC cam0 calibration, introduces pos_sigma(...), extends RunResult, and updates the standard EuRoC path to accumulate per-frame position sigma and compute the mean before ATE association.
Invariant EuRoC run and CLI output
tools/src/vio_pipeline.cpp
Adds run_euroc_invariant() using the adapter, feeds EuRoC IMU and image data through the invariant estimator, accumulates invariant position sigma, computes ATE, and updates main() to branch on --invariant with revised output fields.
Adapter bootstrap and camera tests
tests/tools/invariant_backend_adapter.cpp
Adds synthetic helper setup plus tests for IMU bootstrap timing, seeded attitude and velocity, and process_camera() contract checks on covariance shape and timestamp advancement.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • branes-ai/cortex#218 — This PR changes the covariance/update path that now feeds the dx correction guarded here.
  • branes-ai/cortex#292 — Both PRs modify tools/src/vio_pipeline.cpp in the EuRoC execution and reporting flow.
  • branes-ai/cortex#400 — Both PRs touch the invariant backend update() path around correction application and retraction.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% 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 Conventional-commits title is clear and matches the main EuRoC R-IEKF harness change.
Description check ✅ Passed Mostly matches the template, but the required Checklist section and docs-update note are missing.
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.
✨ 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 feat/issue-365-riekf-euroc-harness

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

@Ravenwater

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🤖 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 `@sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp`:
- Around line 248-255: The covariance update path in the MSCKF invariant backend
only validates dx after cov_.update(), so a non-finite covariance/factor can
still be committed even when the correction looks fine. In the update logic in
the covariance update routine, restore cov_snapshot not only when dx is bad but
also when the updated cov_/S becomes non-finite, and only return success after
both the correction and the updated covariance state are verified; use the
existing symbols cov_, cov_snapshot, and cov_.update() to locate the fix.
- Around line 250-255: The finite check in the `msckf_invariant_backend` loop is
too restrictive because it forces each `T` value through a `double` conversion,
which breaks the template’s duck-typed `math::Scalar` contract. Update the `dx`
validation in the backend to use a `T`-aware finiteness check or customization
point instead of `std::isfinite(static_cast<double>(v))`, or else explicitly
constrain the backend’s `T`/`Scalar` requirements to double-convertible types if
that is the intended scope.

In `@tests/tools/invariant_backend_adapter.cpp`:
- Around line 94-100: The timestamp assertion in the camera-path test is too
weak because it only checks that the state timestamp is positive, which is
already guaranteed by bootstrap in invariant_backend_adapter::process_imu/init
state handling. Update the test around a.process_camera(...) to capture
a.current_state().timestamp_s before the call and then REQUIRE that it changes
or advances afterward, so the check specifically validates that process_camera()
updates the timestamp.
- Around line 59-62: The test in invariant_backend_adapter should verify the
pre-bootstrap boundary, not only the final initialized state. Update the
scenario around a.process_imu and initialized() to assert that the adapter
remains uninitialized before the sample window is full, then becomes initialized
only after enough stationary samples have been processed, using the existing
invariant_backend_adapter behavior to pin the threshold.

In `@tools/src/vio_pipeline.cpp`:
- Around line 557-559: The EuRoC evaluation paths currently treat an empty pose
association as a zero ATE and still return success, which masks missing ground
truth, timestamp mismatches, or an untracked trajectory. In both `run_euroc()`
and `run_euroc_invariant()`, add a guard after `ev::associate<T>(traj, gt,
0.02)` so that if `assoc.estimated` is empty the run fails instead of setting
`out.ate_rms_m` to 0 and returning true; keep the normal RMSE computation only
when association succeeds.
- Around line 152-154: The pos_sigma helper is masking invalid covariance by
clamping a non-positive trace to zero, which turns NaN into a false
zero-confidence result. Update pos_sigma to preserve NaN/invalid values instead
of forcing 0.0, and make the ATE reporting path in the harness that formats the
sigma value (the code around the ATE/sigma output) propagate or explicitly flag
invalid covariance rather than printing 0.0.
- Around line 589-604: The invariant path in vio_pipeline.cpp is hardcoding the
standard EuRoC intrinsics/extrinsics, which duplicates the same calibration data
used elsewhere and risks divergence between comparison modes. Extract the shared
EuRoC camera parameters into a single reusable constant/helper and have the
Estimator setup reuse it instead of constructing the Camera, R_imu_cam, and
p_imu_cam inline in this block.
🪄 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: 9d590cff-29db-4a81-b0be-c7a0cbadf803

📥 Commits

Reviewing files that changed from the base of the PR and between 58df23f and 923b691.

📒 Files selected for processing (4)
  • sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp
  • tests/tools/invariant_backend_adapter.cpp
  • tools/include/branes/tools/invariant_backend_adapter.hpp
  • tools/src/vio_pipeline.cpp

Comment on lines +248 to +255
const Cov cov_snapshot = cov_;
const std::vector<T> dx = cov_.update(H, std::span<const T>{proj.r}, std::span<const T>{R_diag});
for (const T v : dx) {
if (!std::isfinite(static_cast<double>(v))) {
cov_ = cov_snapshot;
return false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect Cov::update to see whether cov_ can emit a finite dx with a non-finite covariance.
fd -e hpp -e h 'covariance' sdk/include | xargs -r rg -nP -C4 '\bupdate\s*\('
rg -nP -C3 'pos_sigma' tools/src/vio_pipeline.cpp

Repository: branes-ai/cortex

Length of output: 6679


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the invariant backend guard in context.
sed -n '232,266p' sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp

# Show the full-covariance update implementation.
sed -n '106,170p' sdk/include/branes/sdk/msckf/covariance.hpp

# Show the square-root covariance update implementation and its guard.
sed -n '83,135p' sdk/include/branes/sdk/msckf/sqrt_covariance.hpp

# Show the telemetry path that consumes covariance.
sed -n '620,705p' tools/src/vio_pipeline.cpp

Repository: branes-ai/cortex

Length of output: 10830


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the square-root helpers used by the update path.
rg -n "retriangularize|sqrt_" sdk/include/branes/sdk -g '*.hpp'

# Read the helper implementation and any numeric guards around it.
fd -e hpp sdk/include/branes/sdk | xargs -r rg -n -C4 'retriangularize|sqrt_'

Repository: branes-ai/cortex

Length of output: 3755


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the square-root covariance API and QR helper.
sed -n '1,220p' sdk/include/branes/sdk/msckf/sqrt_covariance.hpp
sed -n '1,180p' sdk/include/branes/sdk/msckf/qr.hpp

# Inspect the invariant backend type aliases and covariance access path.
sed -n '1,320p' sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp

Repository: branes-ai/cortex

Length of output: 25506


Restore the covariance snapshot on any bad update, not just a bad correction. sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp:248-255

cov_.update() mutates the stored covariance before dx is checked. If the update leaves cov_/S non-finite while dx happens to stay finite, this path still commits the poisoned factor and covariance()/pos_sigma() will read it. Check the updated covariance (or its factor) before returning success, or narrow the comment to say only the correction is screened.

🤖 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 `@sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp` around lines 248 -
255, The covariance update path in the MSCKF invariant backend only validates dx
after cov_.update(), so a non-finite covariance/factor can still be committed
even when the correction looks fine. In the update logic in the covariance
update routine, restore cov_snapshot not only when dx is bad but also when the
updated cov_/S becomes non-finite, and only return success after both the
correction and the updated covariance state are verified; use the existing
symbols cov_, cov_snapshot, and cov_.update() to locate the fix.

Comment thread sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp
Comment thread tests/tools/invariant_backend_adapter.cpp Outdated
Comment thread tests/tools/invariant_backend_adapter.cpp Outdated
Comment thread tools/src/vio_pipeline.cpp
Comment thread tools/src/vio_pipeline.cpp
Comment thread tools/src/vio_pipeline.cpp Outdated
  - msckf_invariant_backend: use an unqualified isfinite (ADL) for the δx
    guard instead of narrowing each component to double, so a custom
    math::Scalar (Universal posit) supplies its own — honors the
    no-hardcoded-double rule. δx is documented as the sentinel: a poisoned
    covariance resurfaces as a non-finite δx on the next update.
  - vio_pipeline: extract the EuRoC cam0 calibration into a shared
    euroc_cam0() helper so the MSCKF and R-IEKF runs are driven from
    byte-identical intrinsics/extrinsics (a fair comparison can't drift).
  - vio_pipeline: fail the EuRoC run when pose association is empty rather
    than reporting a perfect ATE 0 — an empty association means missing GT,
    timestamp drift, or an untracked trajectory.
  - vio_pipeline: stop masking a non-finite position σ as 0 (in pos_sigma
    and the over-confidence print) so a poisoned covariance is surfaced,
    which is exactly what this harness exists to expose.
  - tests: assert the adapter stays uninitialized before the bootstrap
    window fills, and that process_camera advances the state timestamp
    (compare before vs after) instead of only checking it is positive.

V1_01 R-IEKF ATE is unchanged at 1.31 m after the calibration refactor.

Relates to #365

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

Copy link
Copy Markdown
Contributor Author

Thanks — addressed in 6914e50. Per comment:

  • std::isfinite(static_cast<double>(v)) narrows the generic Scalar ✅ Fixed — now using std::isfinite; + an unqualified call so a custom math::Scalar (e.g. a Universal posit) supplies its own via ADL. Honors the repo's no-hardcoded-double rule.

  • pos_sigma masks NaN as 0 ✅ Fixed — returns the non-finite trace instead of clamping, and the over-confidence print uses != 0.0 so a poisoned covariance propagates instead of reading as ATE/sigma = 0.

  • Empty pose association reported as a perfect run ✅ Fixed in both run_euroc() and run_euroc_invariant() — empty association now logs a diagnostic and returns false.

  • Pre-bootstrap boundary untested ✅ Fixed — the test now asserts the adapter stays uninitialized after a handful of samples, then initializes once a full window accumulates. (Note: the bootstrap window is kInitSamples = 150, not 200, so the literal 199/200 suggestion would have asserted on the wrong threshold — used a window-agnostic form instead.)

  • Weak timestamp assertion ✅ Fixed — captures before_t and requires current_state().timestamp_s > before_t across process_camera().

  • Duplicated EuRoC camera constants ✅ Fixed — extracted a single euroc_cam0() helper; both backends now read byte-identical calibration (V1_01 R-IEKF ATE unchanged at 1.31 m, confirming the refactor is behavior-preserving).

  • Also restore the covariance when the updated cov_/S is non-finite (not just dx) — Skipped, with reasoning: δx = K·r is the sentinel. A factorization degenerate enough to poison P makes K (hence δx) non-finite in the same step, so the existing guard already rolls both back; and in the residual case where P turns non-finite while δx stays finite, it resurfaces as a non-finite δx on the next update and is caught there. Materializing the dense P every update to check it (O(n²–n³), and the sqrt form reconstructs LLᵀ) is real hot-path cost on a shipped backend for a near-impossible decoupled case. Happy to revisit if you can construct a case where finite δx commits a non-finite P that then fails to surface.

@Ravenwater

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

♻️ Duplicate comments (1)
tests/tools/invariant_backend_adapter.cpp (1)

59-67: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Boundary check is too loose to catch the regression the original comment targeted.

This partially addresses the earlier feedback but checks initialized() at sample 10, far below kInitSamples (150, per invariant_backend_adapter.hpp). A regression that drops the threshold to, say, 100 samples would still pass both REQUIRE_FALSE (at 10) and the later REQUIRE(initialized()) (at 200). Move the negative assertion to right before the window fills (kInitSamples - 1) to actually pin the off-by-one boundary, as originally requested.

♻️ Suggested tightening
-    for (int i = 0; i < 10; ++i)
-        a.process_imu(stationary_sample(0.005 * i));
-    REQUIRE_FALSE(a.initialized());
-
-    // Once a full static window accumulates, init fires.
-    for (int i = 10; i < 200; ++i)
+    for (int i = 0; i < 149; ++i)
+        a.process_imu(stationary_sample(0.005 * i));
+    REQUIRE_FALSE(a.initialized());
+
+    // Once a full static window accumulates, init fires.
+    for (int i = 149; i < 200; ++i)
🤖 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 `@tests/tools/invariant_backend_adapter.cpp` around lines 59 - 67, The boundary
assertion in the IMU bootstrap test is too early to catch a lowered
initialization threshold; update the negative check in the test around
process_imu and initialized() to assert false at the last pre-init sample just
before the bootstrap window fills, using the kInitSamples boundary from
invariant_backend_adapter.hpp. Keep the existing positive assertion after the
full window accumulates, but move the failing case to kInitSamples - 1 so the
test pins the off-by-one behavior and detects regressions that initialize too
soon.
🤖 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.

Duplicate comments:
In `@tests/tools/invariant_backend_adapter.cpp`:
- Around line 59-67: The boundary assertion in the IMU bootstrap test is too
early to catch a lowered initialization threshold; update the negative check in
the test around process_imu and initialized() to assert false at the last
pre-init sample just before the bootstrap window fills, using the kInitSamples
boundary from invariant_backend_adapter.hpp. Keep the existing positive
assertion after the full window accumulates, but move the failing case to
kInitSamples - 1 so the test pins the off-by-one behavior and detects
regressions that initialize too soon.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b3b28be8-a019-46e0-a1fe-405181fd467e

📥 Commits

Reviewing files that changed from the base of the PR and between 923b691 and 6914e50.

📒 Files selected for processing (3)
  • sdk/include/branes/sdk/msckf/msckf_invariant_backend.hpp
  • tests/tools/invariant_backend_adapter.cpp
  • tools/src/vio_pipeline.cpp

Assert the adapter is still uninitialized one sample short of the window
and initializes exactly as it fills, instead of checking far below the
threshold — the off-by-one pin also catches a silently-lowered threshold
(CodeRabbit follow-up).

Relates to #365

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

Copy link
Copy Markdown
Contributor Author

Tightened in c14f025 — the negative assertion now sits at kInitSamples - 1 (149) and the positive one fires as the window fills, pinning the exact off-by-one (and catching a silently-lowered threshold). Verified init comes up at exactly 150 on gcc + clang.

@Ravenwater

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Ravenwater
Ravenwater marked this pull request as ready for review July 1, 2026 02:21
@Ravenwater
Ravenwater merged commit 3c6f4f5 into main Jul 1, 2026
15 checks passed
@Ravenwater
Ravenwater deleted the feat/issue-365-riekf-euroc-harness branch July 1, 2026 02:28
Ravenwater added a commit that referenced this pull request Jul 1, 2026
Session log + CHANGELOG milestone for #429 — the R-IEKF EuRoC harness measured against the standard MSCKF (stable but underperforms; position ATE/σ can't test the yaw-gauge thesis — attitude-NEES is the #365 follow-up). Docs only. Relates to #365.
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