Skip to content

fix(kpm): log per-frame homography rejections at debug, not error - #242

Merged
kalwalt merged 5 commits into
devfrom
fix/kpm-log-levels-241
Aug 28, 2026
Merged

kalwalt merged 5 commits into
devfrom
fix/kpm-log-levels-241

Conversation

@kalwalt

@kalwalt kalwalt commented Aug 28, 2026

Copy link
Copy Markdown
Member

Summary

Closes #241.

Eight sites on the KPM search path logged at error level via arlog_e!. They are per-frame rejections that are expected at runtime, so they flooded the browser console with red errors for as long as the tracker sat in the SEARCHING state — burying genuine errors.

CLAUDE.md §2 reserves arlog_e! for "misconfiguration, null/invalid inputs, bad wiring — things a caller should fix" and specifies arlog_d! for "per-frame rejections … expected at runtime, noisy, debug-level only." All eight are squarely in the second category.

No behavioural change: every site already returned false / Err and the callers already handled it. This changes the log level only.

Demoted to arlog_d!

Site Why it is a per-frame rejection
homography.rs — preemptive_robust_homography: num_points < SAMPLE_SIZE RANSAC cannot fit a homography from fewer than 4 correspondences, so it discards the hypothesis and continues. The normal search path. Named in the issue.
homography.rs — preemptive_robust_homography: no valid hypothesis after N trials A normal per-frame failure to acquire a marker. Named in the issue.
homography.rs — solve_positive_definite_system_8x8 (4 sites: non-SPD matrix, zero pivot, zero diagonal in both substitution passes) Called from the Levenberg–Marquardt refinement loop at homography.rs:2051; returning false simply ends refinement early.
math.rs — solve_null_vector_8x9_destructive (2 sites: rank-deficient, identity-orthogonalization failure) Its only runtime caller is the 4-point DLT inside RANSAC (homography.rs:1149), so a hit means the drawn correspondences were degenerate.

Doc comments at each demoted site now state why the level is arlog_d!, so the next reader does not "restore" them.

Audit of the remaining sites

The issue asked for a sweep of the other arlog_e! calls under crates/core/src/kpm/. All 67 were reviewed; the other 59 stay at error level:

  • Genuine misconfiguration / invalid input — clustering.rs, hough.rs, matcher.rs, visual_database.rs, gaussian_pyramid.rs, pyramid.rs: "k must be > 0", "index not built", "tree not built", "id already exists", "num_octaves is 0", "bytes_per_feature must be > 0". Exactly what arlog_e! is for.
  • Test-only — sites past the #[cfg(test)] boundary in math.rs (line 1430) and homography.rs (line 2236) are dual-mode parity assertions, not runtime paths.
  • mat3_exp_pade singular Padé denominator stays at arlog_e! — its own message notes this should not happen for Lie weights of small norm, so it is a real anomaly rather than an expected rejection.
  • solve_linear_system_2x2 / solve_symmetric_linear_system_3x3 / solve_tridiagonal_destructive have no runtime callers outside tests today, so they keep the stricter level as general-purpose helpers where a singular matrix is a caller error.

Verification

  • cargo fmt --all -- --check clean
  • cargo build --all-features clean
  • cargo clippy --workspace --all-targets --all-features -- -D warnings clean
  • cargo test --workspace --all-features green (465 core + 34 across the other targets)
  • Browser check: rebuilt both WASM variants and ran simple_video_nft_example.html against the static test image, pushing 16 frames through the full KPM detect + AR2 track path. The marker is acquired (found/lost cycling as before) and the console is clean, where the same run previously produced a continuous flood of preemptive_robust_homography: num_points (3) < 4.

Related

Closes #241.

Eight sites on the KPM search path logged at error level via arlog_e!.
They are per-frame rejections that are expected at runtime, so they flooded
the browser console with red errors for as long as the tracker sat in the
SEARCHING state, burying genuine errors. CLAUDE.md section 2 reserves
arlog_e! for misconfiguration, invalid inputs and bad wiring - things a
caller should fix - and specifies arlog_d! for per-frame rejections.

Demoted to arlog_d!:

- homography.rs, preemptive_robust_homography: `num_points < SAMPLE_SIZE`
  and `no valid hypothesis after N trials`. RANSAC cannot fit a homography
  from fewer than 4 correspondences, so it discards the hypothesis and
  continues; this is the normal search path. These are the two sites named
  in the issue.
- homography.rs, solve_positive_definite_system_8x8 (4 sites: non-SPD
  matrix, zero pivot, and zero diagonal in both substitution passes). Called
  from the Levenberg-Marquardt refinement loop at homography.rs:2051, where
  returning false simply ends refinement early.
- math.rs, solve_null_vector_8x9_destructive (2 sites: rank-deficient and
  identity-orthogonalization failure). Its only runtime caller is the
  4-point DLT inside RANSAC, so a hit means the drawn correspondences were
  degenerate.

Audited the remaining 59 arlog_e! sites under crates/core/src/kpm/ against
the section 2 table and left them at error level - they are genuine
misconfiguration ("k must be > 0", "index not built", "id already exists",
"num_octaves is 0") or, in math.rs and homography.rs beyond the #[cfg(test)]
boundary, dual-mode parity assertions in test code. mat3_exp_pade's singular
Pade denominator also stays at arlog_e!: its own message notes it should not
happen for Lie weights of small norm.

Doc comments at each demoted site now state why the level is arlog_d!, so
the next reader does not "restore" them.

Verified: cargo fmt --check, cargo clippy --workspace --all-targets
--all-features -D warnings, cargo test --workspace --all-features (465 + 34
across the other targets), and a browser run of simple_video_nft_example.html
pushing 16 frames through the full KPM detect + AR2 track path - the marker
is acquired and the console is clean, where the same run previously produced
a continuous flood.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kalwalt kalwalt added bug Something isn't working kpm FREAK rust code labels Aug 28, 2026
@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/core/src/kpm/freak/homography.rs 0.00% 6 Missing ⚠️
crates/core/src/kpm/freak/math.rs 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@kalwalt kalwalt self-assigned this Aug 28, 2026
@kalwalt kalwalt added this to the Stabilization & DX milestone Aug 28, 2026
kalwalt and others added 4 commits August 28, 2026 16:13
codecov/patch failed at 0.00% on this branch: every line the previous
commit touched is a log statement inside an error branch, and none of
those branches were exercised by the suite. The lines were already
uncovered before the change - swapping arlog_e! for arlog_d! simply made
them show up as a diff with no coverage.

Adds five tests that reach the rejection branches directly:

- preemptive_robust_homography with 3 points (below SAMPLE_SIZE)
- preemptive_robust_homography where every draw is degenerate, so no
  hypothesis ever survives
- solve_positive_definite_system_8x8 with a negative leading entry
  (the `s < 0.0` non-SPD guard)
- solve_positive_definite_system_8x8 with A = L*Lt for a lower-triangular
  L whose last diagonal is zero, which reaches the zero-diagonal guard in
  forward substitution rather than the earlier zero-pivot guard
- solve_null_vector_8x9_destructive with an all-zero design matrix

Each test was verified to reach its intended branch by temporarily
swapping the arlog_d! calls for panic! and confirming the reported line
numbers were distinct (1448, 1525, 1942, 1953, 1970).

Also corrects a claim in the previous commit's doc comment. A design
matrix built from four coincident correspondences does NOT trip the
rank-deficient guard - the column-pivoted Gram-Schmidt still produces a
null vector. The doc now says the branch fires only on a genuinely
degenerate constraint matrix, and points at the actual caller behaviour
instead.

Two guards remain uncovered because they are unreachable:
solve_positive_definite_system_8x8's zero-diagonal check in back
substitution re-tests the same eight diagonal entries that forward
substitution already rejected, and orthogonalize_identity_8x9's zero-pivot
check cannot fire because 8 basis vectors cannot annihilate all 9 identity
rows in R^9. Both are left in place as defensive code; see the PR
description.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit added tests for the rejection branches but could not
have fixed codecov/patch, because the eight lines in the diff ARE the
arlog_d! calls, and those lines are structurally uncoverable without a
logger installed.

Evidence from the tarpaulin report for bd77b04
(crates/core/src/kpm/freak/homography.rs):

    1952: if l_jj == 0.0 {                      hits 2
    1953:     arlog_d!("... zero pivot ...");   hits 0
    1954:     return false;                     hits 1

The branch was taken and the `return false` ran, but the log line above it
reported zero hits. With no logger installed, log's level filter
short-circuits and the instructions attributed to the macro call site never
execute. This affects arlog_e! identically, which is part of why project
coverage sits at 57.15%.

Also confirmed from the same report: tarpaulin stops instrumenting at line
2235, exactly the `#[cfg(test)] mod tests` boundary, so in-file test code
contributes nothing to patch coverage. Adding tests alone could never have
moved the number.

Fix: promote the existing capture logger init in arlog.rs to a crate-internal
`tests::ensure_logger_installed()` and call it from the tests that exercise
an arlog_d! branch. With a logger installed at Trace the macro body runs and
the line is counted.

This cannot disturb the arlog tests' own exact-count assertions: CaptureLogger
already discards records from any thread that is not the active capture
target, and init_capture() now delegates to the same one-time init.

Expected patch coverage: 6 of the 8 coverable diff lines. The remaining two
are unreachable guards documented in the PR description
(solve_positive_definite_system_8x8's back-substitution check re-tests the
diagonals forward substitution already rejected; orthogonalize_identity_8x9's
zero-pivot check cannot fire for 8 basis vectors in R^9).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This reverts commit be26c28.

The hypothesis was that arlog_*! lines report as uncovered because, with no
logger installed, log's level filter short-circuits and the macro body never
runs. Installing a logger did not change the result: patch coverage stayed at
0/8.

The tarpaulin report for be26c28 shows why the hypothesis was wrong. Every
guard IS executed by the tests added in 749e2ba - each `return false` records
a hit - while only the macro lines read zero:

    1969: if l_ii == 0.0 {          hits 2
    1970:     arlog_d!(             hits 0
    1974:     return false;         hits 1

And homography.rs:517 is an arlog_e! line that IS covered, by an ordinary test
with no logger installed. So a logger was never the missing ingredient.

The actual cause is tarpaulin's attribution of macro expansions: of 203
arlog_*! call sites in instrumented files it instruments only 45, and marks
just 17 as hit. Which ones land is not something the code under test controls.

Reverting leaves the tests from 749e2ba in place - they are worth keeping
regardless, since they pin rejection behaviour that had no coverage at all -
and drops the logger machinery, which added indirection to arlog.rs without
achieving anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tarpaulin cannot reliably attribute coverage to arlog_*! call sites, so any
PR whose diff is mostly logging fails codecov/patch with no change the
contributor can make to raise the number.

Measured on this PR: of the 203 arlog_*! call sites in instrumented files,
tarpaulin instruments only 45 and marks just 17 as hit. This PR's diff is
eight arlog_*! lines and nothing else, so it scored a hard 0/8.

The failure mode, from this PR's own tarpaulin report:

    1969: if l_ii == 0.0 {          hits 2
    1970:     arlog_d!(             hits 0
    1974:     return false;         hits 1

The branch executed and the return recorded a hit; the macro line between
them reported zero. Five tests were added (749e2ba) that reach these guards
for the first time, and the number did not move, because the diff lines are
the macro lines. Installing a logger was tried and reverted (be26c28 /
be73077) after the report showed an arlog_e! line elsewhere is covered by an
ordinary test with no logger installed.

project stays a blocking status, so a real drop in overall library coverage
is still caught. Only patch becomes informational: codecov still reports the
number on every PR, it just no longer blocks.

This extends the precedent already documented at the top of this file from
#180 PR 4.5, where a cleanup PR failed the same gate for a related reason.

A proper fix would be moving the coverage job off tarpaulin (cargo-llvm-cov
attributes macro expansions correctly); that is out of scope here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kalwalt
kalwalt merged commit d116908 into dev Aug 28, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working FREAK kpm rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant