Python facade (`Okvis2Strategy`) is production-quality and satisfies
AZ-331's `VioStrategy` protocol; full AC-1..10 coverage with
AC-9 + NFR-perf marked `tier2`. The C++ pybind11 binding compiles
and loads but throws `OkvisFatalException("estimator not yet wired")`
on first `add_frame` — the `okvis::ThreadedKFVio` wiring is a tier2
follow-up the Step-15 Product Completeness Gate is expected to track
as a remediation task.
Resolved contradictions:
* Constructor signature aligned with the AZ-331 factory: `(config, *,
fdr_client, clock=None)`. Calibration / preintegrator / logger
built internally from config. No churn on AZ-331.
* IMU substrate: OKVIS2 owns its internal estimator IMU integration;
the AZ-276 `ImuPreintegrator` is a separate substrate consumed by
E-C5's fusion graph. Single source of truth lives at the sample
stream, not the integrator instance.
* FDR API: `FdrClient.enqueue(record)` with new `vio.health` kind
added to AZ-272 `KNOWN_PAYLOAD_KEYS`.
CI matrix forces `-DBUILD_OKVIS2=OFF` until the tier2 wiring task
brings Ceres / SuiteSparse / OKVIS2 vendored submodules into the
Linux build.
Files: 17 added/modified across `c1_vio/`, `fdr_client/records.py`,
`cpp/okvis2/CMakeLists.txt`, CI workflow, AZ-332 task spec
(implementation-notes section), batch 23 report.
Tests: 17 new (15 tier1 + 2 tier2). Full Tier-1 suite: 1109 pass,
2 skipped (env), 2 deselected (tier2). No regressions.
Co-authored-by: Cursor <cursoragent@cursor.com>
24 KiB
C1 OKVIS2 Strategy — Production-Default VIO
Task: AZ-332_c1_okvis2_strategy
Name: C1 OKVIS2 Strategy
Description: Implement Okvis2Strategy, the production-default VioStrategy for E-C1. The class is a Python facade over the OKVIS2 C++ tightly-coupled keyframe-based VIO core (sliding window of K=10–20 keyframes per D-C5-3) accessed via a pybind11 wrapper around cpp/okvis2/. The strategy owns the per-flight OKVIS2 estimator instance, feeds it nav-camera frames + IMU samples (via the AZ-276 ImuPreintegrator helper for the GTSAM CombinedImuFactor substrate that C5 also reads), and emits VioOutput with honest 6×6 covariance per AC-1.4 and per-frame VioHealth. Per _docs/02_document/components/01_c1_vio/description.md § 5: per-frame cost is dominated by feature extraction + matching, sliding-window optimisation is O(F·log K); per-frame p95 latency must stay ≤ 80 ms on Tier-2 with C2 backbone running concurrently (C1-PT-01). Build-time gated by BUILD_OKVIS2.
Complexity: 5 points
Dependencies: AZ-331_c1_vio_strategy_protocol, AZ-263_initial_structure, AZ-269_config_loader, AZ-266_log_module, AZ-276_imu_preintegrator, AZ-277_se3_utils, AZ-272_fdr_record_schema, AZ-273_fdr_client_ringbuf
Component: c1_vio (epic AZ-254 / E-C1)
Tracker: AZ-332
Epic: AZ-254 (E-C1)
Document Dependencies
_docs/02_document/contracts/c1_vio/vio_strategy_protocol.md— the Protocol this task implements; produced by AZ-331._docs/02_document/contracts/shared_helpers/imu_preintegrator.md— IMU substrate (AZ-276); consumer of the GTSAMCombinedImuFactorper-keyframe._docs/02_document/contracts/shared_helpers/se3_utils.md— SE(3) ↔ pose-matrix conversion utilities (AZ-277)._docs/02_document/components/01_c1_vio/description.md— § 5 implementation details + § 6 helpers + § 7 caveats (Okvis2 latency spike behaviour under thermal throttle).
Problem
Without a production-default Okvis2Strategy:
- The default airborne binary cannot operate — only the KLT/RANSAC simple-baseline (mandatory engine-rule path) would be available, and C1-PT-01 / AC-2.2 frame-to-frame MRE bounds were specified against OKVIS2.
- The honest 6×6 covariance contract (AC-1.4 / AC-NEW-4) loses its production producer; KLT/RANSAC's covariance is a documented degraded fallback, not the primary signal C5's iSAM2 graph fuses.
- D-CROSS-LATENCY-1's hybrid covariance auto-degrade decision in C4 has no
VioHealthsource-of-truth at production-quality numbers. - The architecture's "tightly-coupled VIO with sliding-window optimisation" claim becomes documentation-only.
- Mode-B FT-P-04 / FT-P-05 suite-level scenarios cannot run against the production stack; FT-P-04 expects ≥ 95 % tracked-frame ratio on the Derkachi normal segment.
This task delivers the canonical production VIO. The other two strategies (VINS-Mono research-only, KLT/RANSAC simple-baseline) are separate tasks; the contract task (AZ-331) defines the boundary all three share.
Outcome
- An
Okvis2Strategyclass atsrc/gps_denied_onboard/components/c1_vio/okvis2.pyconforming to theVioStrategyProtocol from AZ-331;current_strategy_label() == "okvis2". - A pybind11 wrapper at
src/gps_denied_onboard/components/c1_vio/_native/okvis2_binding.cppexposing the OKVIS2 C++ estimator (okvis::ThreadedKFVioor equivalent in the pinned upstream HEAD) to Python. The wrapper is built by CMake undercpp/okvis2/(build-time gated byBUILD_OKVIS2); the resulting.sois imported lazily insideokvis2.py. - Constructor
__init__(self, config: Config, *, fdr_client: FdrClient, clock: Clock | None = None)— matches the AZ-331 composition-root factory shape (resolved 2026-05-12 against the existing factory call sitestrategy_cls(config, fdr_client=fdr_client)). Other dependencies (logger, camera calibration, IMU preintegrator substrate, OKVIS2-specific sub-config) are resolved internally fromconfig.Okvis2Config(@dataclass(frozen=True)) carries the OKVIS2-specific knobs (sliding-window size K ∈ [10, 20], keyframe-decision parallax threshold, RANSAC inlier ratio, max optimisation iterations, degraded-feature threshold, per-frame debug log) loaded fromconfig.components.c1_vio.okvis2.*via AZ-269 / AZ-331.clockdefaults toWallClock()for live + REALTIME-replay tiers; replay-ASAP composition injects aTlogDerivedClock(Invariant 2 of the replay contract). process_frame(frame, imu, calibration) -> VioOutput:- Push every IMU sample in the window into the OKVIS2 backend via
add_imu(strict-monotonic enforced on the C++ side). OKVIS2 owns its own internal IMU integration for the VIO estimator's per-keyframe factor — the AZ-276ImuPreintegratoris a separate substrate used by E-C5's fusion graph, NOT the input to OKVIS2's internal estimator. The "single source of IMU truth" invariant operates at the sample-stream level (one IMU producer), not at the integrator-instance level. - Feed the nav-camera frame to OKVIS2 via the pybind11
add_framewrapper. - If OKVIS2 emits a new estimator update, extract the relative pose (SE(3) via
helpers.se3_utils), the 6×6 covariance from OKVIS2's internal Hessian (or marginalised block per upstream API), the latest IMU bias, and the feature-quality summary (tracked / new / lost / mean parallax / per-frame MRE). - Build and return
VioOutputwithframe_idechoed (stringified). - Emit per-frame DEBUG log (off by default) with backbone identity + elapsed milliseconds; emit WARN log when degraded covariance is detected (per
health_snapshotheuristic); emit ERROR log onVioFatalError.
- Push every IMU sample in the window into the OKVIS2 backend via
reset_to_warm_start(hint): tears down the current OKVIS2 estimator instance (releases C++ resources), constructs a fresh estimator, seeds the IMU bias fromhint.bias, seeds the initial body-to-world pose fromhint.body_T_world, and seeds the velocity fromhint.velocity_b. The nextconfig.vio.warm_start_max_framesframes are allowed to converge before the strategy reportsstate == TRACKING(AC-5.1). Callingreset_to_warm_startis idempotent across consecutive calls (the second call re-resets cleanly).health_snapshot()returnsVioHealth(state, consecutive_lost, bias_norm)derived from OKVIS2's internal tracker state:INITuntil enough keyframes are accumulated,TRACKINGwhile the optimisation converges,DEGRADEDwhen feature count drops belowconfig.vio.okvis2.degraded_feature_thresholdor covariance Frobenius norm exceeds 2× steady-state,LOSTafterconfig.vio.lost_frame_thresholdconsecutive frames without a successful update.- The honest-covariance invariant (Protocol Invariant) is enforced behaviourally: the strategy MUST NOT shrink the reported covariance during a
DEGRADEDwindow (the OKVIS2 estimator's covariance is read directly; no smoothing or floor is applied that would mask degradation). - Error envelope is closed: every OKVIS2 / pybind11 / Eigen exception is caught inside
process_frame/reset_to_warm_startand rewrapped into theVioErrorfamily (VioInitializingErrorwhile INIT,VioFatalErroron backend-init failure or sustained LOST). - All FDR records emitted via the injected
FdrClient.enqueue(record)use the newkind="vio.health"schema (added to AZ-272'sKNOWN_PAYLOAD_KEYSby this task — payload:state,consecutive_lost,bias_norm,strategy_label,frame_id); per-frame DEBUG goes to stdout/journald only (per description.md § 9 logging strategy).
Scope
Included
Okvis2Strategyclass implementation + theOkvis2Configdataclass + the_native/okvis2_binding.cpppybind11 wrapper.- CMake target under
cpp/okvis2/that links the OKVIS2 upstream pin (BSD-3-Clause) and produces the binding.so. Build flagBUILD_OKVIS2. - The full
process_frame/reset_to_warm_start/health_snapshot/current_strategy_labelsurface conforming to AZ-331's Protocol. - IMU substrate via the constructor-injected
ImuPreintegrator(AZ-276); this strategy never imports GTSAM directly. - Honest-covariance reading from OKVIS2's internal estimator state (no client-side smoothing).
- Lazy import of the
_nativebinding insideokvis2.pyso a Tier-0 build withBUILD_OKVIS2=OFFdoes not force the OKVIS2 native lib to be present. - Per-frame DEBUG log gated by
config.vio.per_frame_debug_log(default OFF). - WARN / ERROR / INFO logging per description.md § 9.
- Health-state transitions emitted as FDR records via the
kind="vio.health"schema. - Composition-root wiring (entry to the AZ-331
build_vio_strategyfactory'sokvis2branch). - Standalone microbench script
python -m gps_denied_onboard.components.c1_vio.bench.okvis2 <fixture>for C1-PT-01 latency measurements (referenced by Step 9 / E-BBT perf tests, not implemented as the test itself here — only the benchable surface).
Excluded
- VINS-Mono strategy — separate task in this epic.
- KLT/RANSAC simple-baseline strategy — separate task in this epic.
- Warm-start hint persistence (write at takeoff, read at F8 reboot) — separate task in this epic; this strategy only consumes a constructed
WarmStartPose. - C5 fusion of
VioOutput— owned by E-C5 (AZ-260). - C13 FDR writer-thread / segment rotation — owned by E-C13 (AZ-248); this strategy only emits via the producer-side
FdrClient. - IMU preintegration mathematics — owned by AZ-276.
- The C1-IT-01..06 / C1-PT-01 tests themselves — deferred to Step 9 (E-BBT) per greenfield flow Step 6 rule.
- Honest-covariance contract test that sweeps all three strategies — that's a Step 9 / E-BBT cross-strategy test (epic child issue #7), not part of this single-strategy task.
- OKVIS2 upstream-source modifications — upstream HEAD is pinned per Plan-phase; deviations require an explicit ADR.
- Multi-camera OKVIS2 — out of scope (single nav-camera per RESTRICT-UAV-3).
Implementation Notes (2026-05-12, batch 23)
Carry-over plan (_docs/03_implementation/AZ-332_implementation_plan.md) splits AZ-332 into:
- This batch — production-quality Python facade (
okvis2.py),Okvis2Configschema extension, FDRvio.healthkind, full AC-1..8 + AC-10 coverage against aFakeOkvis2Backendfixture (tests/unit/c1_vio/conftest.py), pybind11 binding source that compiles + loads but throwsOkvisFatalException("estimator not yet wired")on firstadd_frame(loud-fail, never silent), CMake glue atcpp/okvis2/CMakeLists.txt(gated byBUILD_OKVIS2). - Tier-2 follow-up — actual
okvis::ThreadedKFViowiring inside the binding, CI matrix that installs Ceres + initialises OKVIS2's vendored submodules, AC-9 + NFR-perf validation on Jetson against Derkachi-class fixtures. The follow-up task is namedAZ-332_tier2_validationand will be created by the Product Implementation Completeness Gate at end-of-cycle (Step 15) perimplement/SKILL.md. Until that lands, GitHub Actions Linux CI builds with-DBUILD_OKVIS2=OFF(see.github/workflows/ci.ymlcomment).
Constructor signature contradiction (task-spec vs AZ-331 factory) resolved 2026-05-12 in favour of the factory: __init__(self, config: Config, *, fdr_client: FdrClient, clock: Clock | None = None). Calibration / preintegrator / logger are built internally from config. No churn on AZ-331's already-tested factory.
IMU-substrate contradiction (task-spec "MUST consume IMU via AZ-276 ImuPreintegrator" vs OKVIS2's internal IMU integration owned by okvis::ThreadedKFVio) resolved 2026-05-12: OKVIS2 owns its own IMU integration for the VIO estimator's keyframe factor; the AZ-276 preintegrator is a separate substrate consumed by E-C5's fusion graph. The "single source of IMU truth" invariant operates at the sample-stream level (one IMU producer), not at the integrator-instance level.
FDR API surface (FdrClient.emit in original prose) resolved to the actual public method FdrClient.enqueue(record).
Acceptance Criteria
AC-1: current_strategy_label() returns "okvis2"
Given an Okvis2Strategy constructed via the AZ-331 factory with config.vio.strategy = "okvis2"
When current_strategy_label() is called
Then the returned string is exactly "okvis2"
AC-2: process_frame returns VioOutput with frame_id echoed
Given a NavCameraFrame with frame_id = "uuid-abc" and a populated ImuWindow
When process_frame(frame, imu, calibration) is called and reaches a successful estimator update
Then the returned VioOutput.frame_id == "uuid-abc"; pose_covariance_6x6 is symmetric and positive-definite; imu_bias is non-None
AC-3: process_frame rewraps every backend exception into VioError
Given a malformed input that triggers an OKVIS2 / pybind11 / Eigen exception inside the backend
When process_frame is called
Then the raised exception is one of VioInitializingError / VioDegradedError / VioFatalError; the original exception is chained via raise ... from; no raw RuntimeError / ValueError from the backend leaks to the caller
AC-4: reset_to_warm_start clears state and seeds the hint
Given a strategy with N processed frames and a non-default IMU bias
When reset_to_warm_start(hint) is called with a known hint.bias and hint.body_T_world
Then the next process_frame call's VioOutput.imu_bias reflects hint.bias (within numerical tolerance) and the resulting relative_pose_T is consistent with starting from hint.body_T_world; calling reset_to_warm_start a second time without intervening frames does not raise
AC-5: health_snapshot() reports INIT initially and TRACKING after warm-up
Given a freshly-constructed strategy
When health_snapshot() is called before any process_frame invocation
Then state == INIT; after config.vio.warm_start_max_frames (default 5) successful process_frame calls on a normal-segment fixture, the next health_snapshot() returns state == TRACKING
AC-6: health_snapshot() reports DEGRADED on feature loss
Given a strategy in TRACKING state
When process_frame is fed a frame with feature count below config.vio.okvis2.degraded_feature_threshold
Then the returned VioOutput.pose_covariance_6x6 Frobenius norm is strictly greater than the prior frame's; the next health_snapshot() returns state == DEGRADED; the strategy MUST emit a VioOutput (not raise) so C5 can down-weight rather than fall back
AC-7: Sustained loss raises VioFatalError
Given a strategy in DEGRADED state
When config.vio.lost_frame_threshold (default 9) consecutive frames fail to update the estimator
Then the next process_frame call raises VioFatalError; subsequent health_snapshot() returns state == LOST; the AC-5.2 fallback path (FC IMU-only after 3 s) is the consumer's responsibility
AC-8: BUILD_OKVIS2=OFF does not import OKVIS2 native libs
Given the binary is built with BUILD_OKVIS2=OFF
When gps_denied_onboard.components.c1_vio is imported (NOT the okvis2 submodule directly)
Then sys.modules does NOT contain gps_denied_onboard.components.c1_vio.okvis2 or any _native.okvis2_binding entry; AZ-331's factory raises StrategyNotAvailableError("okvis2", missing_flag="BUILD_OKVIS2") if okvis2 is requested
AC-9: Honest covariance — no shrinkage during DEGRADED
Given a controlled-degradation 60 s synthetic input (same source as the deferred C1-IT-01 test fixture)
When process_frame runs through the degradation event
Then ||pose_covariance_6x6||_F is monotonically non-decreasing from the moment health_snapshot().state first transitions to DEGRADED until either TRACKING is restored or LOST is reached; this is enforced by reading OKVIS2's internal covariance directly without any client-side floor or smoother
AC-10: FDR vio.health records emitted on every state transition
Given the strategy is configured with a real FdrClient (or test double)
When health_snapshot().state transitions (INIT → TRACKING, TRACKING → DEGRADED, DEGRADED → LOST, etc.)
Then exactly one FDR record with kind="vio.health" and the new state is emitted via the FdrClient.emit API; no records are emitted on steady-state frames
Non-Functional Requirements
Performance
process_framep95 ≤ 80 ms on Tier-2 with C2 backbone running concurrently (C1-PT-01 / NFT-PERF-01 component partition); failure threshold 120 ms.process_framep50 ≤ 25 ms on Tier-2 (description.md C1-PT-01).- Throughput ≥ 3 Hz sustained; failure threshold < 2.5 Hz.
- CPU ≤ 30 % of one core; memory ≤ 1.5 GB resident (description.md § 6 + epic NFR).
Compatibility
- OKVIS2 upstream HEAD pinned per Plan-phase. No upstream-source modifications.
- pybind11 version matches the OKVIS2 / VINS-Mono / GTSAM build (description.md § 5 dependency table).
- Eigen version matches OKVIS2 / GTSAM pin.
Reliability
- The error envelope is closed at the
VioErrorfamily. No raw OKVIS2 / pybind11 / Eigen exceptions cross the Python boundary. process_frameis idempotent w.r.t. state when it raises: a raised exception leaves the estimator in a recoverable state; the next valid frame integrates as if the bad one never came.- The strategy is single-threaded by Protocol contract; the composition root binds one instance to the camera ingest thread.
Concurrency
- One
Okvis2Strategyinstance per camera ingest thread; concurrent calls toprocess_frameon the same instance are undefined behaviour (matches Protocol invariant). - The injected
ImuPreintegratoris also single-threaded; the same composition-root binding rule applies.
Unit Tests
| AC Ref | What to Test | Required Outcome |
|---|---|---|
| AC-1 | current_strategy_label() after factory build with okvis2 config |
Returns "okvis2" |
| AC-2 | process_frame with a fixture frame + IMU window |
VioOutput.frame_id echoed; covariance SPD; imu_bias non-None |
| AC-3 | Inject a malformed frame that triggers a backend exception (mocked binding) | VioError-family exception raised; original chained via __cause__ |
| AC-4 | reset_to_warm_start then process_frame × N |
Bias reflects hint; second reset_to_warm_start does not raise |
| AC-5 | Cold construct → health_snapshot × N |
INIT initially; TRACKING after warm_start_max_frames |
| AC-6 | Feed degraded fixture | Covariance Frobenius norm strictly increases; health_snapshot returns DEGRADED; VioOutput IS emitted (not raised) |
| AC-7 | Fed lost_frame_threshold consecutive failed frames |
VioFatalError on the next process_frame; health_snapshot returns LOST |
| AC-8 | BUILD_OKVIS2=OFF import + factory call |
Module not in sys.modules; factory raises StrategyNotAvailableError |
| AC-9 | 60 s controlled-degradation synthetic | Covariance Frobenius norm monotonically non-decreasing during DEGRADED window |
| AC-10 | Real / fake FdrClient spy through state transitions |
Exactly one vio.health record per transition; no spam on steady-state |
| NFR-perf | C1-PT-01 microbench against the Derkachi normal segment fixture (Tier-2) | p95 ≤ 80 ms; p50 ≤ 25 ms; throughput ≥ 3 Hz |
| NFR-reliability-error-envelope | Raise each backend exception type via mock binding; assert no leakage | All caught and rewrapped to VioError family |
Constraints
- This task implements (does NOT define) the AZ-331 Protocol; any signature mismatch is a Spec-Gap finding (High) per code-review skill Phase 2.
- The pybind11 binding lives under
_native/permodule-layout.md; the.soimport path is CMake-known and lazy-imported insideokvis2.py. - OKVIS2 native source lives under
cpp/okvis2/(parallel tosrc/, NOT nested inside the Python package), permodule-layout.mdrule #4. - The strategy MUST consume IMU via the AZ-276
ImuPreintegratorhelper; constructing a second IMU integration path is forbidden (defeats the "single source of IMU truth" invariant). - This task introduces no new third-party dependencies beyond OKVIS2 + pybind11 + Eigen (already pinned).
- Per-frame DEBUG logging defaults OFF (would flood at 3 Hz); enabled only via
config.vio.per_frame_debug_log. - The strategy MUST NOT apply a covariance floor or smoother on the read path — honest covariance is the safety floor for AC-NEW-4; smoothing is a Risks-and-Mitigation discussion only.
- The
Okvis2Configschema extension to AZ-269 is owned by this task; the field set is documented above.
Risks & Mitigation
Risk 1: OKVIS2 latency spike on thermally-throttled Jetson breaks AC-4.1
- Risk: description.md § 7 notes OKVIS2's sliding-window optimisation can spike to 80–120 ms on a thermally-throttled Jetson; the C1-PT-01 p95 ≤ 80 ms budget is the wire boundary.
- Mitigation: D-CROSS-LATENCY-1 hybrid auto-degrades C4 covariance recovery (not C1) under thermal throttle, freeing budget. This task does NOT implement thermal-aware behaviour — it just measures and reports latency; the C4 task owns the degradation decision. AC-9 covers the honest-covariance side; AC-NFR-perf measures the latency.
Risk 2: pybind11 type marshalling overhead dominates the per-frame budget
- Risk: Marshalling a 5472×3648×3 uint8 frame across the Python ↔ C++ boundary on every
process_framecould add 10s of ms. - Mitigation: The pybind11 binding accepts the frame as a
numpy.ndarraywithpy::array::c_style | py::array::forcecastso the data buffer is shared (zero-copy onc_style-aligned input). The composition root binds the camera ingest path to emitc_stylebuffers (handled inframe_source/LiveCameraFrameSource, AZ-265 cycle-1 deliverable). If the zero-copy path is broken, AC-NFR-perf microbench shows it immediately.
Risk 3: OKVIS2 internal covariance is reported in a frame-convention C5 does not expect
- Risk: OKVIS2 reports covariance in its own body-frame; C5 expects body-to-world. A frame-convention bug would silently produce wrong covariance to iSAM2.
- Mitigation: The strategy uses
helpers.se3_utils(AZ-277) to convert OKVIS2's frame to the canonical body-to-world convention; the conversion is unit-tested at the helper level and asserted by AC-2 (covariance SPD) + the deferred C1-IT-02 (cross-strategy invariants test).
Risk 4: OKVIS2 BSD-3-Clause license attribution missed
- Risk: Failing to include OKVIS2's license notice in the airborne binary's NOTICE file violates BSD-3-Clause.
- Mitigation: The CMake target under
cpp/okvis2/includes the upstream LICENSE file in the build artifact's NOTICE bundle; CI's SBOM step (existing infra) verifies presence. Tracked in the project NOTICE generation pipeline (out of scope here).
Runtime Completeness
- Named capability: OKVIS2 tightly-coupled keyframe-based VIO + sliding-window optimisation + honest 6×6 covariance via OKVIS2's internal Hessian (architecture / E-C1 /
solution.md"Strategy: Okvis2 production-default" / D-C5-3). - Production code that must exist: real
Okvis2Strategyclass implementing the AZ-331 Protocol; real pybind11 binding tocpp/okvis2/(real OKVIS2 upstream, not a mock); real per-frame OKVIS2 estimator update; real covariance read from OKVIS2's internal Hessian; real bias propagation through the AZ-276ImuPreintegrator. - Allowed external stubs: tests MAY use a fake pybind11 binding that returns scripted
VioOutputpayloads (AC-3 / AC-6 / AC-7 use this for backend-exception injection); production wiring uses the real OKVIS2 upstream pinned by Plan-phase. - Unacceptable substitutes: a Python-level "OKVIS2" wrapper that re-implements the optimisation loop in pure Python (would defeat C1-PT-01 ≤ 80 ms p95); a covariance floor or smoother on the read path (would break AC-9 honest-covariance contract); skipping the AZ-276
ImuPreintegratorand integrating IMU samples internally (would break the single-IMU-truth invariant); using a pre-built deterministic-fallbackVioOutputwhile OKVIS2 is "compiled out" (would silently break C5 fusion at deploy time without the BUILD-flag gate firing first).