diff --git a/.agents/instructions.md b/.agents/instructions.md
new file mode 100644
index 0000000..0935e13
--- /dev/null
+++ b/.agents/instructions.md
@@ -0,0 +1,60 @@
+# Agent Instructions: Porting Core Data Structures (`include/AR/*.h`)
+
+## Phase 1: Header Triage & Analysis
+1. Analyze in the https://github.com/webarkit/WebARKitLib the `include/AR/` directory. Consider code from lib/src/AR, lib/SRC/AR2, lib/SRC/ARUtils
+2. **IGNORE** the following headers: `video.h`, `gsub.h`, `gsub_lite.h`, `arMulti.h`, `arGL.h`, `arOSG.h`.
+3. Focus strictly on: `ar.h`, `param.h`, `matrix.h`, `arFilterTransMat.h`.
+
+## Phase 2: Struct Extraction & Translation
+1. Identify ALL core data structures in `ar.h` and related math headers. This includes, but is not limited to:
+ - Calibration: `ARParam`, `ARParamLT`
+ - Tracking & State: `ARHandle`, `AR3DHandle`, `ARMarkerInfo`
+ - Math: `ARMat`, `ARVec`
+2. Create the corresponding Rust modules (e.g., `crates/core/src/types.rs`, `crates/core/src/math.rs`).
+3. Translate the C `struct`s into Rust. Ensure correct memory alignment and types.
+4. Add `#[derive(Debug, Default, Clone)]` where applicable.
+
+## Phase 3: Validation
+1. Write unit tests for every function and struct created to ensure their default instantiation behaves as expected and mathematical equivalence is maintained.
+2. Ensure all generated documentation and inline comments are in English.
+
+## Phase 4: Clippy hygiene (post-#180)
+
+The strict clippy gate (`cargo clippy --workspace --all-targets --all-features -- -D warnings`)
+is enforced in CI by the `kpm-build (ubuntu-latest)` job. When writing
+or modifying any code in this repo, follow the conventions below to
+avoid recreating lints cleared during the #180 series. Full reference
+in [CLAUDE.md §7](../CLAUDE.md).
+
+1. **Struct-init over `Default::default()` + field reassign.**
+ `let x = T { f: ..., ..Default::default() };`, not
+ `let mut x = T::default(); x.f = ...;`. Refs: PR #186, #188.
+
+2. **FFI shim cfg must match its caller's cfg.** When declaring
+ `extern "C" { fn webarkit_cpp_*(...) }` only for dual-mode parity
+ tests, gate the block with `#[cfg(all(test, feature = "dual-mode"))]`
+ — same as the caller module. A `#[cfg(feature = "dual-mode")]`
+ alone leaks the extern into the non-test lib build, where it has
+ no caller and trips `dead_code`. Ref: PR #185.
+
+3. **Place new items above `#[cfg(test)] mod tests`.** Clippy's
+ `items_after_test_module` fires otherwise. Ref: PR #184.
+
+4. **SIMD runtime-dispatch variants get inline
+ `#[allow(clippy::too_many_arguments)]` + `// rationale:`** — not
+ a `clippy.toml` threshold raise. Their signatures are locked by
+ the dispatcher contract. Ref: PR #187.
+
+5. **Auto-generated test fixtures from external sources** (e.g.
+ `tests/kpm_regression.rs` constants captured from C++) get
+ file-scoped `#![allow(clippy::excessive_precision)]` + rationale
+ to preserve source-traceability. Ref: PR #188.
+
+6. **Re-run strict clippy after every clean.** Cargo halts on lib
+ errors before checking `--all-targets`. Lints in
+ examples/tests/benches stay masked until the lib is clean. Refs:
+ PR #187 → #188 (26 lints surfaced this way).
+
+When you must add a lint suppression, write it as `#[allow(...)]` with
+a `// rationale:` comment explaining *why* — not `#[expect(...)]` or a
+bare allow. The rationale is the contract for future contributors.
\ No newline at end of file
diff --git a/.agents/rules/rules.md b/.agents/rules/rules.md
new file mode 100644
index 0000000..90229b3
--- /dev/null
+++ b/.agents/rules/rules.md
@@ -0,0 +1,21 @@
+---
+trigger: always_on
+---
+
+# Antigravity Agent Rules for WebARKitLib.rs
+
+## 1. Role & Identity
+You are an expert systems programmer porting WebARKitLib from C/C++ to Rust. The ultimate target is a pure, side-effect-free WASM module and native library.
+
+## 2. Strict Exclusions (Out of Scope)
+- **NO Video Handling**: Completely ignore all files and functions related to video capture (e.g., V4L2, DirectShow, QuickTime, `video.h`, `arVideo.h`). Video buffering will be handled externally (e.g., via JavaScript `Uint8Array` passed to WASM).
+- **NO Rendering/OpenGL**: Completely ignore all files and functions related to OpenGL, GLUT, or 3D rendering (e.g., `gsub.h`, `gsub_lite.h`, `arGL.h`).
+- **NO arMulti**: Skip all code related to multi-marker tracking (`arMulti.h`, `arMulti*.c`) for now.
+
+## 3. Code Standards & Language
+- **Language**: ALL code comments, explanations, docstrings, and commit messages MUST be written in English.
+- **Safety**: Prefer safe Rust. Use `unsafe` only for specific WASM SIMD intrinsics (`std::arch::wasm32`).
+
+## 4. C to Rust Translation Guidelines
+- Translate C structs from headers into idiomatic Rust. Use exact primitive types (e.g., `f64` for `ARdouble` if configured so).
+- Replace C arrays inside structs with Rust arrays or `Vec` depending on whether the size is known at compile time.
\ No newline at end of file
diff --git a/.agents/skills/license-header-adder/SKILL.md b/.agents/skills/license-header-adder/SKILL.md
new file mode 100644
index 0000000..16f0133
--- /dev/null
+++ b/.agents/skills/license-header-adder/SKILL.md
@@ -0,0 +1,37 @@
+---
+name: license-header-adder
+description: Automatically adds a license header to source files with a filename placeholder.
+---
+
+# License Header Adder Skill
+
+This skill allows you to add a consistent LGPLv3 license header to all source files in the project.
+
+## Instructions
+
+1. **Read the Template**: Locate the license template in `resources/HEADER.txt`.
+2. **Placeholder Replacement**: Before applying the header to a file, replace the `{{FILENAME}}` placeholder with the basename of the target file.
+3. **Supported Files**: apply this header to:
+ - `.rs` files
+ - `.c` and `.h` files
+ - `.js` and `.ts` files
+4. **Application Logic**:
+ - If the file already has a license header, skip it.
+ - **Exclusions**: Never apply this to files in `benchmarks/c_benchmark/src/WebARKitLib` or third-party vendor directories.
+ - Insert the header at the very top of the file.
+ - Ensure there is a blank line after the header before the original content starts.
+
+## Example Usage
+
+When applying to `core/src/lib.rs`:
+```rust
+/*
+ * lib.rs
+ *
+ * WebARKitLib.rs - High-performance AR tracking library
+ ...
+ */
+
+use wasm_bindgen::prelude::*;
+...
+```
diff --git a/.agents/skills/license-header-adder/resources/HEADER.txt b/.agents/skills/license-header-adder/resources/HEADER.txt
new file mode 100644
index 0000000..8d9ef2c
--- /dev/null
+++ b/.agents/skills/license-header-adder/resources/HEADER.txt
@@ -0,0 +1,36 @@
+/*
+ * {{FILENAME}}
+ * WebARKitLib-rs
+ *
+ * This file is part of WebARKitLib-rs - WebARKit.
+ *
+ * WebARKitLib-rs is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * WebARKitLib-rs is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with WebARKitLib-rs. If not, see .
+ *
+ * As a special exception, the copyright holders of this library give you
+ * permission to link this library with independent modules to produce an
+ * executable, regardless of the license terms of these independent modules, and to
+ * copy and distribute the resulting executable under terms of your choice,
+ * provided that you also meet, for each linked independent module, the terms and
+ * conditions of the license of that module. An independent module is a module
+ * which is neither derived from nor based on this library. If you modify this
+ * library, you may extend this exception to your version of the library, but you
+ * are not obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * Copyright 2026 WebARKit.
+ *
+ * Author(s): Walter Perdan @kalwalt https://github.com/kalwalt
+ *
+ */
+
diff --git a/.claude/issue-180-plan.md b/.claude/issue-180-plan.md
new file mode 100644
index 0000000..eaed82d
--- /dev/null
+++ b/.claude/issue-180-plan.md
@@ -0,0 +1,173 @@
+# Issue #180 — Tighten CI Clippy Gate to `--all-targets --all-features`
+
+**Status**: Planning complete, ready for implementation.
+**Branch base**: `dev` (HEAD `a30b033` at planning time, post-PR #183 criterion 0.8 merge).
+**Tracking issue**: [#180](https://github.com/webarkit/WebARKitLib-rs/issues/180).
+
+---
+
+## Understanding Summary
+
+- **What**: Close the gap between CI's clippy gate (`--workspace -- -D warnings`, default features, lib targets only) and the strict local check (`--all-targets --all-features -- -D warnings`) — a ~70-lint delta on `dev` HEAD.
+- **Why**: Contributor onboarding (new contributors run the strict check and assume the codebase is broken); the strict gate catches real lints in `ffi-backend` / `dual-mode` / `simd-*` paths CI never sees today.
+- **Who**: WebARKitLib-rs contributors; no end-user behavior change.
+- **How**: Execute issue #180's 5-PR sequence as the spec — trivial wins → FFI shim annotations → `ar/marker.rs` field-reassign cleanup → SIMD `#[allow]` with rationale → tighten CI in `.github/workflows/ci.yml`.
+- **Non-goals**:
+ - No `clippy.toml` file (deferred to M10+ if SIMD surface grows).
+ - No `FeatureMapConfig` / struct refactor (SIMD signatures are locked by runtime-dispatch contract).
+ - No deletion of `webarkit_cpp_*` FFI shims unless truly orphaned.
+ - No `CHANGELOG.md` edits (project rule).
+ - Not blocking v0.7.0 (already released).
+
+---
+
+## Decision Log
+
+| # | Decision | Alternatives considered | Why this choice |
+|---|---|---|---|
+| 1 | Follow #180's 5-PR sequence verbatim | Refine ordering; merge PRs; re-baseline lint counts first | Issue is well-scoped; counts may drift but plan structure is sound |
+| 2 | **(Updated during PR 2)** Tighten each extern block's cfg from `#[cfg(feature = "dual-mode")]` to `#[cfg(all(test, feature = "dual-mode"))]` — matches caller cfg, lint disappears because the extern only exists when callers do. Originally planned `#[allow(dead_code)]` + rationale. | Original plan (allow + rationale); wire up callers (scope creep); bulk delete (irreversible) | Evidence at PR 2 time: all 17 "dead" shims have callers, but the callers are gated `cfg(all(test, feature = "dual-mode"))` while the extern blocks were gated only on `cfg(feature = "dual-mode")`. The cfg mismatch was the real root cause; tightening fixes it honestly. Same effort per block (one line), zero behavior change. |
+| 3 | PR 4: inline `#[allow(clippy::too_many_arguments)]` + rationale on `get_similarity_sse41` / `get_similarity_avx2` | `clippy.toml` threshold raise; bundle args into struct | SIMD signatures locked by runtime-dispatch contract; inline is scoped, threshold raise loses signal everywhere |
+| 4 | Defer `clippy.toml` | Add now with `too-many-arguments-threshold = 9` | Only 2 sites today; revisit when SIMD surface grows in M10+ |
+| 5 | Keep CI on `dtolnay/rust-toolchain@stable` after PR 5 | Pin to a specific rustc version; document known-good rustc | Accept the churn; v0.7.0 is shipped, future rustc lint additions fix in follow-up PRs |
+| 6 | Branch off `dev`, one PR per branch | Stacked PRs; single mega-PR | Project convention (CLAUDE.md §4); keeps reviews focused |
+
+---
+
+## Assumptions
+
+- **A1**: CI keeps `dtolnay/rust-toolchain@stable` after PR 5 tightens the gate. When a new rustc adds lints, fix in a follow-up PR. No rustc pin.
+- **A2**: PR 4 stays its own PR (not folded into PR 1) for review clarity, even though it's only 2 `#[allow]` lines. Revisit at execution time if it feels artificial.
+- **A3**: Branch naming follows `chore/clippy-180-pr-` — branched off `dev`, one PR per branch, conventional commits per CLAUDE.md §6.
+- **A4**: Re-baseline the lint inventory at the start of PR 1, not now. Counts may differ from #180's ~70 due to rustc drift since #180 was filed.
+
+---
+
+## Open Risks
+
+- **R1** — *Rustc drift since #180*: lints may have been promoted/demoted between #180's filing and today. PR 1's scope could expand or contract. **Mitigation**: re-baseline before opening PR 1; update #180 with actual counts.
+- **R2** — *FFI shim mislabeled*: a `webarkit_cpp_*` extern annotated as "kept for dual-mode parity" might actually be orphaned. **Mitigation**: run `cargo test --features dual-mode` and `cargo test --features ffi-backend` after PR 2; any annotated shim that has no caller under any feature combo gets deleted in the same PR.
+- **R3** — *`ar/marker.rs` init-order dependency*: 44 mechanical `field-reassign-with-default` fixes touch many lines. Struct-init syntax could surface a subtle init-order dependency in `ARHandle` construction. **Mitigation**: rely on existing tests; if any fail, revert that specific site to the default+reassign pattern with an `#[allow]` + rationale.
+- **R4** — *CI tightening fails after merge*: PR 5 flips the gate, then unrelated work introduces new lints under `--all-features` that the contributor didn't catch locally. **Mitigation**: PR 5 description includes a one-liner of the exact local command (`cargo clippy --all-targets --all-features -- -D warnings`); update CLAUDE.md §5 to match (already aligned) and add a §6 checklist note.
+
+---
+
+## Implementation Plan — 5 PRs
+
+All PRs: branched off `dev`, opened against `dev`, conventional commits, pre-commit checklist (CLAUDE.md §5) green before push.
+
+### PR 1 — Trivial wins (~10 lints)
+
+**Branch**: `chore/clippy-180-pr1-trivial-wins`
+**Scope**:
+- Unused variables: `crates/core/src/ar/image_proc.rs:217` (`image_temp_u16`), `:218` (`image2`) — prefix `_` or remove.
+- `items_after_test_module`: move items above `#[cfg(test)] mod tests` in `crates/core/src/ar/math.rs:831` and `ar/pattern.rs:1050`.
+- `needless_deref`: `crates/core/src/kpm/freak/homography.rs:2645`.
+- `needless_range_loop` (4 sites): convert to `iter().enumerate()` in `ar2/feature_map.rs`, `ar2/image_set.rs`, `ar/pattern.rs`.
+
+**Verification**:
+```bash
+cargo fmt --all -- --check
+cargo clippy --all-targets --all-features -- -D warnings # the new lints here pass
+cargo test --all-features
+```
+
+**Commit**: `fix(core): clean up trivial clippy lints under --all-targets --all-features (#180)`
+
+---
+
+### PR 2 — Dead FFI shim hygiene (~14 lints)
+
+**Branch**: `chore/clippy-180-pr2-ffi-shims`
+**Scope**: `crates/core/src/kpm/freak/{clustering,homography,math,hough,matcher}.rs` — for each dead `extern "C" { fn webarkit_cpp_*(...) }` block:
+
+**Implemented (Decision #2 updated)**: change each block's cfg gate from `#[cfg(feature = "dual-mode")]` to `#[cfg(all(test, feature = "dual-mode"))]` so the extern only exists when its caller (also `cfg(all(test, feature = "dual-mode"))`) does. Add a one-line comment above each block referencing #180 for traceability. Verified all 17 callers still resolve and dual-mode tests still pass.
+
+**Verification**: same triad as PR 1, plus `cargo test --features dual-mode --lib` and `cargo test --features ffi-backend` green.
+
+**Commit**: `chore(kpm): document or remove dead webarkit_cpp_* FFI shims (#180)`
+
+---
+
+### PR 3 — `ar/marker.rs` field-reassign-with-default (44 lints)
+
+**Branch**: `chore/clippy-180-pr3-ar-marker-init`
+**Scope**: `crates/core/src/ar/marker.rs` — mechanical conversion of `let mut h = ARHandle::default(); h.field = x; h.other = y;` to struct-init syntax `let h = ARHandle { field: x, other: y, ..Default::default() };`.
+
+**Process**:
+- One commit per logical group (e.g. one per call site / constructor) to make review tractable.
+- If any site fails tests after conversion, revert that specific site with `#[allow(clippy::field_reassign_with_default)]` + `// rationale: init order matters here because `.
+
+**Verification**: same triad as PR 1, with extra attention to `cargo test --all-features` since `ARHandle` is core path.
+
+**Commit**: `refactor(ar): convert ARHandle construction to struct-init syntax (#180)`
+
+---
+
+### PR 4 — SIMD `too_many_arguments` `#[allow]` (2 lints)
+
+**Branch**: `chore/clippy-180-pr4-simd-too-many-args`
+**Scope**: `crates/core/src/ar2/feature_map.rs:275` and `:387` — add to each:
+
+```rust
+#[allow(clippy::too_many_arguments)]
+// rationale: SIMD variant of get_similarity; signature locked to match
+// scalar fallback and sibling SIMD impl for runtime dispatch via
+// is_x86_feature_detected!.
+```
+
+**Verification**: same triad as PR 1.
+
+**Commit**: `chore(ar2): allow too_many_arguments on SIMD get_similarity variants (#180)`
+
+---
+
+### PR 4.5 — Examples/tests/benches cleanup (unplanned, discovered during PR 4)
+
+**Branch**: `chore/clippy-180-pr4-5-examples-tests-benches`
+
+**Surfaced**: PR 4 cleared the final two **library** lints. Cargo had been halting on lib errors before checking `--all-targets`, so 26 examples/tests/benches lints were hidden until the lib went strict-clean. PR 5 couldn't tighten CI safely until these were also cleared.
+
+**Categories**:
+- 26 `excessive_precision` in `tests/kpm_regression.rs` — handled via file-scoped `#![allow(clippy::excessive_precision)]` with rationale (C++ baseline fixtures, extra digits document source-traceability).
+- 9 `field_reassign_with_default` in examples + benches — same struct-init conversion as PR 3.
+- 7 `unnecessary_cast usize→usize` in `generate_patt.rs` — drop redundant `as usize`.
+- 3 `or_fun_call inside expect` — `unwrap_or_else(|_| panic!(...))`.
+- 3 `ptr_arg &Vec` → `&[u8]`.
+- 3 `redundant_closure` (`|| String::new()` → `String::new`).
+- 5 `needless_range_loop` — iterator forms.
+- 5 singletons (`type_complexity` via `type RunReport = ...`, `redundant_pattern_matching` (`if let Ok(_)` → `.is_ok()`), `manual_range_contains`, `manual_is_multiple_of`, `let_unit_value`).
+
+**Coverage workflow tangent**: PR 4.5 also fixed an unrelated bug in `.github/workflows/coverage.yml` — tarpaulin's `--exclude-files "tests/*"` glob was workspace-root-relative and never matched our nested `crates/core/tests/`. Fixed to `**/tests/*`, and added a project `codecov.yml` mirroring the exclusions so the patch gate doesn't fail on PRs that land entirely in uninstrumented paths.
+
+**Commits**:
+- `chore: clear strict clippy lints in examples/tests/benches (#180)`
+- `chore(codecov): exclude examples/benches from coverage targets (#180)`
+- `ci(coverage): fix tarpaulin tests/ glob and mirror in codecov (#180)`
+
+---
+
+### PR 5 — Tighten CI
+
+**Branch**: `chore/clippy-180-pr5-ci-tighten`
+**Pre-req**: PRs 1–4.5 merged; `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean on `dev`.
+
+**Scope**: `.github/workflows/ci.yml`
+- `kpm-build (ubuntu-latest)` job: add a new step `cargo clippy --workspace --all-targets --all-features -- -D warnings` (Linux-only). This job already has submodules + libclang, which `--all-features` requires (`ffi-backend` triggers C++ compilation in `build.rs`).
+- `build-and-test` job: keep `cargo clippy --workspace -- -D warnings` (default features, no submodules). This job's checkout intentionally skips submodules to verify the pure-Rust path doesn't accidentally depend on them — adding `--all-features` here would conflict with that invariant.
+- `pure-rust-build` job: keep `cargo clippy -p webarkitlib-rs -- -D warnings` (no-features path stays clean — a third invariant).
+
+**Discovery**: the original plan had the strict gate going into `build-and-test`. First push of PR 5 failed because that job's checkout doesn't include git submodules, so `--all-features` enabled `ffi-backend` and `build.rs` panicked looking for the C++ sources. Moved to `kpm-build (ubuntu-latest)` where the prerequisites already exist. Lints are OS-agnostic, so Linux-only is sufficient.
+
+**Verification**: open PR, watch CI go green. If a lint slips in between PR 4 merge and PR 5 (rustc drift, A1), fix in this PR.
+
+**Commit**: `ci: tighten clippy gate to --all-targets --all-features (#180)`
+
+---
+
+## Closure
+
+When PR 5 merges:
+- Close issue #180.
+- Note in the closing comment whether actual lint count matched #180's ~70 estimate (data point for future audits).
+- Confirm CLAUDE.md §5 is still accurate (it already prescribes the strict command — no update needed).
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d7ed67d..3fea445 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -93,12 +93,50 @@ jobs:
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libclang-dev
+ # cache-bin: false avoids caching ~/.cargo/bin. On macOS this job
+ # intermittently restored a stale `cargo` shim resolving to
+ # `rustup-init`, so `cargo build` was parsed as `rustup-init build`
+ # and failed (#134). This job installs no cargo-managed binaries, so
+ # disabling bin caching costs nothing while removing the flake.
- name: Rust Cache
uses: Swatinem/rust-cache@v2
+ with:
+ cache-bin: false
+
+ # Strict clippy gate (#180): --all-targets covers examples/tests/benches;
+ # --all-features exercises ffi-backend, dual-mode, simd-* paths the
+ # default-features check skipped. Ubuntu-only because this is the job
+ # that already has submodules + libclang; lints are OS-agnostic so
+ # running it on one platform is sufficient.
+ - name: Run cargo clippy (strict, --all-targets --all-features)
+ if: runner.os == 'Linux'
+ run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- name: Build kpm backend (FFI backend)
run: cargo build -p webarkitlib-rs --features ffi-backend
+ # macOS ffi-backend link verification (#119). PR #117 fixed build.rs to
+ # link libc++ (not libstdc++) on Apple targets, but the dual-mode CI step
+ # was scoped to --lib, so the integration tests and the generate_patt
+ # example were never re-built on macOS after the fix. Build them here to
+ # confirm they link against libc++.
+ #
+ # --no-run (not a full run): this verifies the linker fix only. Actually
+ # executing the pose-sensitive integration tests cross-platform is out of
+ # scope — that float-drift question is tracked separately in #118, which
+ # keeps test_full_pipeline_pose gated to Linux.
+ - name: Verify ffi-backend tests + example link (macOS)
+ if: runner.os == 'macOS'
+ run: |
+ cargo test -p webarkitlib-rs \
+ --test kpm_regression \
+ --test nft_pipeline \
+ --test ar2_pinball_io \
+ --test cross_stack_parity \
+ --features ffi-backend --no-run
+ cargo build -p webarkitlib-rs \
+ --example generate_patt --features log-helpers,ffi-backend
+
# Dual-mode tests run pure-Rust ports against the C++ baseline via the
# FFI backend (webarkit_cpp_* shims) and assert byte-level parity.
# Catches regressions that would otherwise only surface in local runs.
@@ -153,6 +191,10 @@ jobs:
--test absolute_corner_error \
--features dual-mode -- --nocapture
+ # Miri UB validation (#182) lives in its own workflow at
+ # .github/workflows/miri.yml — split out so it can carry a dedicated
+ # status badge and run on its own cadence.
+
submodule-drift-check:
runs-on: ubuntu-latest
steps:
@@ -219,24 +261,38 @@ jobs:
working-directory: crates/core
run: cargo run --example simple --features log-helpers -- ../../benchmarks/data/camera_para.dat ../../benchmarks/data/patt.hiro ../../benchmarks/data/img.jpg
- - name: Run benchmarks
+ - name: Install C benchmark deps (svn for bootstrap)
+ run: sudo apt-get update && sudo apt-get install -y subversion
+
+ - name: Cache C benchmark downloaded sources
+ uses: actions/cache@v4
+ with:
+ path: benchmarks/c_benchmark/archives
+ key: c-bench-archives-${{ runner.os }}-${{ hashFiles('benchmarks/c_benchmark/libraries.json') }}
+
+ # The C baseline pulls external sources (jpeg-9f from ijg.org, WebARKitLib)
+ # whose download occasionally fails with TLS/network errors (#204). It only
+ # produces an optional comparison log, so keep it NON-BLOCKING: a flake must
+ # not fail the benchmarks gate. The required signal is the Rust bench below.
+ - name: Build & run C baseline benchmark (non-blocking)
+ continue-on-error: true
run: |
- # Download and Build C benchmark
+ set -e
cd benchmarks/c_benchmark
- python ../bootstrap.py --bootstrap-file libraries.json
-
+ for attempt in 1 2 3; do
+ python ../bootstrap.py --bootstrap-file libraries.json && break
+ echo "bootstrap attempt $attempt failed; retrying in 15s..." >&2
+ sleep 15
+ done
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make
cd ..
-
- # Run C benchmark for baseline
mkdir -p ../../target/criterion
./build/c_benchmark ../data/camera_para.dat ../data/patt.hiro ../data/hiro.raw 429 317 > ../../target/criterion/c_benchmark.log
-
- # Run Rust benchmarks
- cd ../..
- cargo bench -p webarkitlib-rs --bench marker_bench
+
+ - name: Run Rust benchmarks
+ run: cargo bench -p webarkitlib-rs --bench marker_bench
- name: Upload benchmark reports
uses: actions/upload-artifact@v5
with:
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index ad750a0..488aaef 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -32,7 +32,8 @@ jobs:
--out Xml \
--output-dir coverage \
--timeout 300 \
- --exclude-files "tests/*" \
+ --exclude-files "**/tests/*" \
+ --exclude-files "**/examples/*" \
--ignore-panics
- name: Upload coverage reports to Codecov
diff --git a/.github/workflows/miri-skip.yml b/.github/workflows/miri-skip.yml
new file mode 100644
index 0000000..3544a35
--- /dev/null
+++ b/.github/workflows/miri-skip.yml
@@ -0,0 +1,54 @@
+name: Miri
+
+# Skip-shim for the Miri required gate.
+#
+# The real Miri workflow (.github/workflows/miri.yml) uses `paths-ignore`
+# to skip docs-only changes. But the `protect-main` / `protect-dev`
+# rulesets require a check named `miri (pure-Rust UB validation)` to
+# pass on every PR — including docs-only ones. Without this shim a
+# README-only PR would have a "missing" required check and could not
+# merge.
+#
+# This workflow declares the same `name:` and the same job `name:` as
+# miri.yml, but triggers ONLY on the paths the real workflow ignores.
+# It does no actual work — it just emits a passing check with the
+# expected name so branch protection is satisfied.
+#
+# IMPORTANT: keep the workflow `name:` and job `name:` here in sync
+# with miri.yml. GitHub matches required checks on those names
+# exactly.
+
+on:
+ pull_request:
+ branches:
+ - dev
+ - main
+ paths:
+ - '**/*.md'
+ - 'docs/**'
+ - 'assets/**'
+ - 'LICENSE*'
+ - '.gitignore'
+ - '.gitattributes'
+ push:
+ branches:
+ - dev
+ - main
+ paths:
+ - '**/*.md'
+ - 'docs/**'
+ - 'assets/**'
+ - 'LICENSE*'
+ - '.gitignore'
+ - '.gitattributes'
+
+jobs:
+ miri:
+ name: miri (pure-Rust UB validation)
+ runs-on: ubuntu-latest
+ steps:
+ - name: Skip — docs-only change, no Rust touched
+ run: |
+ echo "This change touches only docs / assets / LICENSE."
+ echo "The real Miri workflow ran zero seconds because there"
+ echo "is no Rust code change to validate."
diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml
new file mode 100644
index 0000000..8487b48
--- /dev/null
+++ b/.github/workflows/miri.yml
@@ -0,0 +1,87 @@
+name: Miri
+
+# Miri UB validation (#182): runs the pure-Rust lib under the MIR
+# interpreter to catch use-after-free, OOB reads, invalid `unsafe`
+# invariants, uninitialized-memory reads, and reference-aliasing
+# violations that escape regular tests.
+#
+# Excludes `ffi-backend` (Miri can't execute C++) and `simd-x86-*`
+# (Miri's x86 SIMD support is incomplete). Scalar fallbacks ARE
+# validated.
+#
+# Heavy algorithmic / pipeline tests are skipped via
+# `#[cfg_attr(miri, ignore)]` (#194) — they don't exercise unsafe
+# boundaries and would dominate runtime. See docs/miri.md.
+#
+# Required gate: this workflow MUST pass for PRs targeting dev/main.
+
+on:
+ pull_request:
+ branches:
+ - dev
+ - main
+ paths-ignore:
+ - '**/*.md'
+ - 'docs/**'
+ - 'assets/**'
+ - 'LICENSE*'
+ - '.gitignore'
+ - '.gitattributes'
+ push:
+ branches:
+ - dev
+ - main
+ paths-ignore:
+ - '**/*.md'
+ - 'docs/**'
+ - 'assets/**'
+ - 'LICENSE*'
+ - '.gitignore'
+ - '.gitattributes'
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ miri:
+ name: miri (pure-Rust UB validation)
+ runs-on: ubuntu-latest
+ env:
+ # Pinned nightly — bump manually if Miri breaks on this date or
+ # every ~3 months for freshness. See docs/miri.md.
+ MIRI_NIGHTLY: nightly-2026-06-01
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Install pinned nightly + Miri
+ run: |
+ rustup toolchain install $MIRI_NIGHTLY --component miri --profile minimal
+ rustup default $MIRI_NIGHTLY
+ cargo miri setup
+
+ - name: Rust Cache
+ uses: Swatinem/rust-cache@v2
+ with:
+ key: miri-${{ env.MIRI_NIGHTLY }}
+ shared-key: miri
+
+ # NOTES on MIRIFLAGS:
+ # * -Zmiri-tree-borrows: use the Tree Borrows aliasing model instead
+ # of the default Stacked Borrows. Stacked Borrows trips inside
+ # crossbeam-epoch (a transitive dep of rayon, reached from our
+ # ar2::feature_map par_chunks_mut) on patterns that are sound in
+ # practice. Tree Borrows accepts them while still catching real
+ # UB in our own code.
+ # * -Zmiri-disable-isolation: allow filesystem ops so tests that
+ # use `tempfile` (e.g. ar2::feature_set roundtrip) can run.
+ # * -Zmiri-ignore-leaks: rayon's global thread pool spawns workers
+ # that aren't joined at process exit. Miri flags that as a leak
+ # by default; ignoring it is the recommended setting for rayon-
+ # using test suites and does NOT weaken UB detection.
+ # * -Zmiri-strict-provenance is intentionally NOT enabled — it also
+ # trips inside crossbeam-epoch (integer-to-pointer casts). Re-
+ # evaluate once the ecosystem migrates. See docs/miri.md.
+ - name: Run Miri (pure-Rust, lib targets only)
+ run: cargo miri test -p webarkitlib-rs --lib
+ env:
+ MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-tree-borrows -Zmiri-disable-isolation -Zmiri-ignore-leaks
diff --git a/.gitignore b/.gitignore
index b9d78af..ed53543 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,9 +13,6 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
-# Google Antigravity local agent state (optional, but usually good to keep out of repos)
-.agents/
-
# VSCode files
.vscode/
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 7a7f4e2..37b40ac 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -36,12 +36,14 @@ The unified core library containing all AR functionality:
- `kpm::ref_data_set` — `.fset3` reference data I/O and compression modes.
- `kpm::types` — KPM data structures and constants.
- `kpm::freak` — FREAK descriptor math, homography, and matching utilities (pure Rust port of `WebARKitLib/lib/SRC/KPM/FreakMatcher`):
+ - `freak::visual_database` — top-level per-frame query orchestrator (M9-1): builds the query keyframe, then runs the two-pass pipeline (feature match → Hough voting → homography → inlier filter, then a homography-guided re-match) across stored reference keyframes. Exposes `query` / `query_from_keyframe` and facade-parity accessors on the database + `FeatureStore` slices (#147, #148).
- `freak::math` — linear algebra (matrix operations, linear solvers) and Padé matrix exponential.
- `freak::homography` — homography estimation and refinement pipeline.
- `freak::hough` — Hough similarity voting (4D bin discretization over translation × angle × scale) for filtering matches by transformation consistency.
- `freak::clustering` — K-Medoids partitioning + Binary Hierarchical Clustering (BHC) vocabulary tree for fast approximate-NN search on 96-byte FREAK descriptors. Hamming distance via 24×32-bit bit-magic. Includes a byte-identical port of C++ `vision::FastRandom` / `vision::ArrayShuffle` so the BHC tree topology matches the C++ baseline given the same seed.
- `freak::matcher` — `FeatureStore` (points + flat descriptor buffer) and `FeatureMatcher` with three match variants: brute force, BHC-indexed (fast path), and homography-guided (spatial filter via 3×3 inverse + `tr` radius). All three apply the C++ ratio test (default 0.7) and filter by `FeaturePoint::maxima`.
- `freak::image_pyramid` — image pyramid construction via `BoxFilterPyramid8u` (box filtering for 8-bit grayscale) and `BinomialPyramid32f` (32-bit floating-point binomial pyramid with scale-space interpolation).
+ - `freak::gaussian_pyramid` — Gaussian scale-space pyramid driving the DoG detector. Hot binomial-filter passes have NO-FMA SSE4.1 / AVX2 / wasm SIMD paths (runtime-detected, bit-exact against the scalar fallback for dual-mode parity) and rayon-parallelized rows (v0.8.0, #200–#207).
- `freak::feature_extraction` — keypoint detection via Difference-of-Gaussians (DoG), dominant orientation assignment via circular gradient voting, and FREAK descriptor computation with native Rust implementation of the FREAK binary pattern and bit-pair comparison.
- **Types** (`types`): core data structures (`ARHandle`, `ARParam`, etc.).
@@ -50,6 +52,13 @@ The unified core library containing all AR functionality:
WASM wrapper and JavaScript/TypeScript glue code for browser targets.
Depends only on `webarkitlib-rs` (the core crate).
+Since **v0.8.0 (#161)** it exposes the pure-Rust NFT pipeline to the
+browser: a `WasmKpmHandle` binding for KPM detection (load reference data
+from bytes, detect), `console_log` wiring via `ar_log_init_wasm()`, a dual
+**standard + SIMD** build pipeline (`npm run build:wasm`), and an
+end-to-end browser NFT demo under `crates/wasm/www`. The `cc`/`bindgen`
+build dependencies are optional so the wasm target builds pure-Rust.
+
## Feature Flags
| Feature | Description |
@@ -58,6 +67,7 @@ Depends only on `webarkitlib-rs` (the core crate).
| `simd` | Umbrella: enables all SIMD sub-features |
| `simd-wasm32` | WASM SIMD128 intrinsics |
| `simd-x86-sse41` | x86 SSE4.1 intrinsics |
+| `simd-x86-avx2` | x86 AVX2 intrinsics (runtime-detected; NO-FMA for bit-exact scalar parity) |
| `log-helpers` | Enable logging infrastructure (installs `env_logger` for desktop/tests, `console_log` for WASM) |
| `ffi-backend` | **Opt-in** — compile the C++ FreakMatcher library and generate FFI bindings. Used for cross-validation and the regression-test suite; not required for production tracking. |
| `dual-mode` | Enables FFI-based parity tests that validate pure-Rust ports against the live C++ baseline (M6 math/solvers/homography, M7 BHC/matcher, PRNG). Transitively enables `ffi-backend`. Run in CI on Linux/macOS/Windows. |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7756c7e..5f07f4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,76 @@
-# Changelog - webarkit/webarkitlib-rs
+# Changelog - webarkit/webarkitlib-rs
All notable changes to this project will be documented in this file.
-## [0.7.0] - 2026-06-05
+## [0.8.0] - 2026-06-26
+
+## Milestone — Pure-Rust completeness, validation & polish
+
+### ⚙️ Miscellaneous Tasks
+
+- *(ar2)* Allow too_many_arguments on SIMD get_similarity variants (#180)
+- Clear strict clippy lints in examples/tests/benches (#180)
+- *(codecov)* Exclude examples/benches from coverage targets (#180)
+- *(coverage)* Fix tarpaulin tests/ glob and mirror in codecov (#180)
+- Tighten clippy gate to --all-targets --all-features (#180)
+- Relocate strict clippy gate to kpm-build (ubuntu) job (#180)
+- *(miri)* Add Miri UB validation for pure-Rust code paths (#182)
+- *(miri)* Drop -Zmiri-strict-provenance (trips on third-party deps) (#182)
+- *(miri)* Switch to Tree Borrows to avoid crossbeam-epoch false positive (#182)
+- *(miri)* Enable -Zmiri-disable-isolation for tempfile-using tests (#182)
+- *(miri)* Enable -Zmiri-ignore-leaks for rayon thread pool (#194)
+- *(miri)* Promote to required gate, split workflow, add README badge (#182)
+- *(miri)* Skip docs-only changes via paths-ignore + same-name shim
+- *(benchmarks)* Harden against flaky external C-library downloads (#204)
+
+### ⚡ Performance
+
+- *(ci)* Scope Miri job to unsafe boundaries via cfg_attr(miri, ignore) (#194)
+- *(ci)* Annotate remaining real-image tests (#194 follow-up)
+- *(kpm)* Gaussian scale-space pyramid — benchmark + rayon parallelization (#209)
+- *(kpm)* Box-filter Pyramid downsample — criterion benchmark + SIMD (#211)
+
+### 🐛 Bug Fixes
+
+- *(core)* Clean up trivial clippy lints under --all-targets --all-features (#180)
+- *(kpm)* Tighten dual-mode FFI extern cfg to match caller cfg (#180)
+- *(kpm)* Replace unaligned transmute in hamming_distance_96 (#192)
+- *(ci)* Disable cargo-bin cache on kpm-build to stop stale rustup-init shim (#134)
+
+### 💼 Other
+
+- *(wasm)* Make cc/bindgen optional, fix wasm dead-code (#161 goal 1)
+
+### 📚 Documentation
+
+- *(benches)* Refresh BENCHMARKS.md for criterion 0.8
+- *(agents)* Codify #180 clippy conventions and un-ignore .agents/
+- *(kpm)* Mark box-filter Pyramid as kept-but-unused reference (#203)
+- *(wasm)* Note .fset3 / KPM detection in the NFT demo description (#161)
+- Update README + ARCHITECTURE for the v0.8.0 milestone
+
+### 🕸️ WebAssembly & Emscripten
+
+- *(wasm)* Wire console logger + clean SIMD wasm build; add .fset3 asset (#161 goals 2,3)
+- *(wasm)* KPM detection binding + browser NFT demo (#161 goal 4)
+
+### 🚀 Features
+
+- *(kpm)* Facade-parity convenience accessors on VisualDatabase + FeatureStore slices (#148)
+- *(example)* Nft_marker_gen .fset3 via RustFreakMatcher — drop ffi-backend (#179)
+
+### 🚜 Refactor
+
+- *(ar)* Convert Default::default() + field reassign to struct-init (#180)
+- *(kpm)* Factor VisualDatabase::query matching loop + add query_from_keyframe (#147) (#217)
+
+### 🧪 Testing
+
+- *(core)* Add unit tests for relocated math/pattern helpers (#180)
+- *(homography)* Widen cauchy_cost tolerance to 1e-5 for Miri (#194)
+- *(kpm)* Raise M9 coverage — exclude examples + edge tests (#177) (#218)
+- *(ci)* Verify ffi-backend tests + example link on macOS (#119)
+## [0.7.0] - 2026-06-05
## Milestone 9 — VisualDatabase & pure Rust backend
diff --git a/CLAUDE.md b/CLAUDE.md
index 149095d..e2567a0 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -196,6 +196,123 @@ cargo test --all-features
- [ ] CHANGELOG.md **not** touched
- [ ] Branch is off current `dev`
+## 7. Codebase clippy conventions (#180 lessons)
+
+The strict clippy gate (`--all-targets --all-features -- -D warnings`)
+is enforced in CI by the `kpm-build (ubuntu-latest)` job. The patterns
+below are non-obvious — they were discovered during the #180 cleanup
+series and aren't taught by clippy's lint messages alone.
+
+### 7.1 Struct-init over `Default::default()` + field reassign
+
+Clippy's `field_reassign_with_default` fires on the C-style pattern.
+
+```rust
+// ❌ Don't
+let mut h = ARHandle::default();
+h.xsize = w;
+h.ysize = h;
+
+// ✅ Do
+let mut h = ARHandle {
+ xsize: w,
+ ysize: h,
+ ..Default::default()
+};
+```
+
+If subsequent code mutates other fields (e.g. `h.o2i[i] = ...`), keep
+the binding `mut`. Method calls (`h.set_pixel_format(...)`) stay
+after the initializer — they're not field reassigns.
+
+Reference: PR #186, PR #188.
+
+### 7.2 FFI shim cfg must match its caller's cfg
+
+Dual-mode parity tests live under `#[cfg(all(test, feature = "dual-mode"))]`.
+The `extern "C"` block declaring the `webarkit_cpp_*` shims it calls
+must use the **same** cfg — not just `#[cfg(feature = "dual-mode")]` —
+otherwise the lib target sees an extern with no caller and clippy
+fires `dead_code` under `--all-features`.
+
+```rust
+// ❌ Don't (extern declared in non-test builds where it has no caller)
+#[cfg(feature = "dual-mode")]
+extern "C" {
+ fn webarkit_cpp_foo(...) -> i32;
+}
+
+#[cfg(all(test, feature = "dual-mode"))]
+mod dual_mode_tests {
+ use super::*;
+ #[test] fn foo_matches_cpp() { unsafe { webarkit_cpp_foo(...) }; }
+}
+
+// ✅ Do (extern and caller share the same gate)
+#[cfg(all(test, feature = "dual-mode"))]
+extern "C" {
+ fn webarkit_cpp_foo(...) -> i32;
+}
+```
+
+Reference: PR #185.
+
+### 7.3 Items must come **above** `#[cfg(test)] mod tests`
+
+Clippy's `items_after_test_module` fires when `pub fn ...` or any item
+appears after the test module. When adding helpers to a file that
+already has a `mod tests`, insert them above the `#[cfg(test)]` line.
+
+Reference: PR #184 (`mat_mul_dff`, `dot_product*`).
+
+### 7.4 SIMD runtime-dispatch variants: inline `#[allow]` + rationale
+
+Functions like `get_similarity_sse41` / `get_similarity_avx2` are
+runtime-dispatched alternatives to a scalar fallback. Their
+signatures are **locked** to match each other and the scalar version.
+Prefer inline `#[allow(clippy::too_many_arguments)]` with a
+`// rationale:` line over raising the threshold in `clippy.toml` — the
+allow scopes to exactly the sites that need it.
+
+```rust
+#[cfg(all(feature = "simd-x86-sse41", target_arch = "x86_64"))]
+#[target_feature(enable = "sse4.1")]
+// rationale: SIMD variant of get_similarity; signature locked to match
+// the scalar fallback and sibling SIMD impl for runtime dispatch via
+// is_x86_feature_detected!.
+#[allow(clippy::too_many_arguments)]
+unsafe fn get_similarity_sse41(...) -> Option { ... }
+```
+
+Reference: PR #187.
+
+### 7.5 Auto-generated fixtures: file-scoped `#![allow]` + rationale
+
+For test files containing constants captured verbatim from external
+sources (e.g. C++ baseline output dumped by `kpm_dump_fixtures`),
+preserve source-traceability with a file-scoped allow rather than
+truncating the literals.
+
+```rust
+//! KPM regression tests — numerical validation against C++ baseline.
+
+#![allow(dead_code)]
+// rationale: SCREEN/WORLD/pose constants below were generated by the
+// C++ kpm_dump_fixtures tool. Extra decimal digits document the
+// captured C++ output verbatim; truncating to f32-exact would
+// round-trip to the same bit pattern but lose traceability (#180).
+#![allow(clippy::excessive_precision)]
+```
+
+Reference: PR #188 (`tests/kpm_regression.rs`).
+
+### 7.6 Re-run strict clippy after the lib goes clean
+
+`cargo clippy --all-targets` halts on lib errors before checking
+examples/tests/benches. After clearing the last lib lint, **re-run**
+the strict command — previously masked lints in non-lib targets will
+surface. PR #188 cleaned up 26 such lints unmasked by PR #187.
+
## 📝 Documentation & Style
- All comments, docstrings, and commit messages must be in **English**.
- Use `Result` for error handling instead of `panic!`.
diff --git a/Cargo.toml b/Cargo.toml
index c00e669..3f4caa2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,7 @@ exclude = [
resolver = "2"
[workspace.package]
-version = "0.7.0"
+version = "0.8.0"
authors = ["kalwalt "]
edition = "2021"
description = "A high-performance, memory-safe Rust port of WebARKitLib (ARToolKit) for native and WASM."
diff --git a/README.md b/README.md
index 7684391..2e08be4 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@

[](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/ci.yml)
+[](https://github.com/webarkit/WebARKitLib-rs/actions/workflows/miri.yml)
[](https://codecov.io/gh/webarkit/WebARKitLib-rs)
[](https://crates.io/crates/webarkitlib-rs)
[](https://www.npmjs.com/package/@webarkit/webarkitlib-wasm)
@@ -13,7 +14,7 @@
This project aims to provide a pure-Rust implementation of the core ARToolKit algorithms, targeting both **native** systems and **WebAssembly (WASM)** for high-performance augmented reality in the browser.
> [!NOTE]
-> This project is currently a **Work in Progress**. Core marker detection and pose estimation are functional. KPM/NFT (Natural Feature Tracking) is in an early stage with partial Rust porting -- a fully idiomatic Rust KPM pipeline with a working example is the primary goal for v1.0.0.
+> Core marker detection and pose estimation are functional, and as of **v0.8.0** the **KPM/NFT (Natural Feature Tracking) pipeline is fully ported to pure Rust** — end-to-end keypoint matching, marker generation (`.iset`/`.fset`/`.fset3`), and runtime tracking all run with **no C++ toolchain**, natively and in the browser via WebAssembly. The project remains a **Work in Progress** toward v1.0.0 (broader API docs, multi-marker support, and a dedicated KPM benchmark are the remaining focus areas).
## 🌟 Key Features
@@ -39,7 +40,7 @@ Add `webarkitlib-rs` to your `Cargo.toml`:
```toml
[dependencies]
-webarkitlib-rs = "0.7"
+webarkitlib-rs = "0.8"
```
### Pure Rust tracking (no C++ compiler required)
@@ -65,7 +66,7 @@ need to do anything special:
```toml
[dependencies]
-webarkitlib-rs = "0.7" # default backend is pure Rust
+webarkitlib-rs = "0.8" # default backend is pure Rust
```
```bash
@@ -82,7 +83,7 @@ NFT tracking**:
```toml
[dependencies]
-webarkitlib-rs = { version = "0.7", features = ["ffi-backend"] }
+webarkitlib-rs = { version = "0.8", features = ["ffi-backend"] }
```
> When installing from crates.io, no extra setup is required — the C++
@@ -122,42 +123,33 @@ This example loads a camera parameter file, a marker (pattern or barcode), and a
Generate NFT (Natural Feature Tracking) marker files compatible with ARnft and NFT-Marker-Creator-App:
```bash
-# Pure-Rust default (no C++ toolchain required) — produces .iset + .fset.
-# Pass --yes to skip the interactive "no .fset3" confirmation prompt
-# (recommended for scripted / CI use):
+# Pure Rust, no C++ toolchain — produces a COMPLETE marker (.iset + .fset + .fset3):
cargo run --release --example nft_marker_gen -- \
- --input path/to/image.jpg \
- --output path/to/output_name \
- --dpi 220 \
- --yes
-
-# Full output including .fset3 (FREAK descriptors) — opt-in C++ backend:
-# .fset3 generation still uses CppFreakMatcher today; the runtime KPM
-# tracker reads .fset3 files in pure Rust, only the marker-creation step
-# needs the C++ FREAK extractor. Wiring nft_marker_gen to RustFreakMatcher
-# for .fset3 is a follow-up task.
-cargo run --release --features ffi-backend --example nft_marker_gen -- \
--input path/to/image.jpg \
--output path/to/output_name \
--dpi 220
# With SIMD acceleration + Rayon parallelism (recommended for x86_64):
-cargo run --release --features "ffi-backend,simd-x86-sse41,simd-x86-avx2" \
+cargo run --release --features "simd-x86-sse41,simd-x86-avx2" \
--example nft_marker_gen -- \
--input path/to/image.jpg \
--output path/to/output_name \
--dpi 220
```
+> Since #179, `.fset3` (FREAK descriptors) is generated by the pure-Rust
+> `RustFreakMatcher` — a single invocation produces a complete marker with no
+> C++ toolchain.
+
> **Important:** Always use the `--release` flag. Debug builds are 5–10× slower due to missing compiler optimizations.
-Output files (depends on whether `ffi-backend` is enabled):
+Output files (all produced on the pure-Rust default):
-| File | Default (pure Rust) | With `ffi-backend` | Description |
-|---|:---:|:---:|---|
-| `