ADR-0020 follow-on (task #35). Two-pronged fix:
1. Kernel: ported ADR-0019 Stage 1 diagonal-metric kernel to
core-rs/src/vault.rs. Per-versor scoring is now 32 multiplies
+ 32 adds via the precomputed Cl(4,1) metric, not the
1024-op full geometric_product the prior path computed.
Bit-identity preserved by serial fold order matching Python.
2. Zero-copy marshalling: replaced Vec<&PyAny> + extract-per-
versor with PyReadonlyArray2<f32> via the numpy Rust crate.
The Rust binding now reads a slice view directly into the
numpy buffer — no Python→Rust copy, no Vec<[f32;32]>
re-chunk. Python caller passes the (N, 32) ndarray as-is
(ascontiguousarray ensures C-contiguous f32).
Result:
N python rust speedup
1k 0.20ms 0.26ms 0.77x (Python wins on fixed overhead)
10k 1.62ms 1.45ms 1.12x
100k 19.22ms 12.93ms 1.49x
1M 251.50ms 131.36ms 1.91x
Parity bit-identical (raw f32 bytes) at every scale across the
parameterised test in tests/test_vault_recall_rust_parity.py.
Both ADR-0020 first-surface gates now pass: parity AND
performance at the scales where Rust is meant to win. Python
remains the default per CLAUDE.md sequencing rule 5;
CORE_BACKEND=rust is now a legitimate opt-in acceleration.
Smoke 27/27, algebra 70/70, runtime 19/19 all green.