# znippy-zoomies — reasoning (f0, the whole)
> **Class:** `reasoning`. The *why*, the trade-offs, and **every design we rejected**.
> The descriptive shape we ship is in [`design.md`](design.md), which by rule mentions
> none of this. If you want to know "why not X?" — X is here.

## Why this repo exists at all
These primitives were born inside `katana-osm`, the OSM ingestion engine. Two other
repos wanted them: `skade` (for `stree`/`gatling`) and `znippy` (for the codecs and
`gatling`). Leaving them in `katana-osm` would have forced both consumers to depend on
a whole application repo, and every primitive fix would ride a `katana-osm` release —
a **release-cascade cycle**. Extracting the shared parts into a standalone **leaf**
crate breaks the cycle: `skade` and `znippy` both depend on `znippy-zoomies`, neither
depends on the other, and a primitive ships on its own cadence. That is the whole
reason the crate is a separate repo and not a module.
## Why "no rayon" is a law, not a preference
rayon is mature and ubiquitous — so why ban it? Because its **global thread pool** is
exactly what bit `katana-osm`: a planet-scale job silently serialized when heavy work
landed on the wrong side of a fork-join barrier, and the shared pool made the failure
invisible (it looked *faster* while broken). The `gatling` engines are pure
`std::thread::scope` + one atomic cursor: no hidden global state, no barrier, and
scoped threads can borrow caller stack data with no `'static`/`Arc` ceremony. The law
is enforced mechanically by [`tests/rayon_free_law.rs`](../tests/rayon_free_law.rs), so
a future edit that reaches for rayon trips a red test instead of quietly falsifying the
documented claim.
**Workspace-wide now, not just root `src/`.** The law once scoped itself to the crate's
own `src/` and carved out the codec members `ljar` / `lzip-parallel`, which shipped their
own `OnceLock<rayon::ThreadPool>` singletons — the *exact* private-pool shape the law
exists to forbid, quietly excused. That hole is closed: those two were ported onto
`gatling::gatling_forkjoin::gatling_for_each` (rayon deps dropped), `lgz`/`lbzip2` folded
their private scoped-thread clones onto the shared engine, and `tests/rayon_free_law.rs`
was widened to walk **every** workspace member's `src/` + `Cargo.toml`
(`whole_workspace_is_rayon_free`) *and* to ban private pools (`rayon::ThreadPool` + the
pool builders). So the law now holds crate-wide. The only surviving `rayon` is transitive,
as a **dev-dependency of `criterion`** for the benches; `cargo tree -e no-dev -i rayon`
prints nothing, so it never reaches a shipped build. Full dated history of the regression
and fix: [`gatling-reasoning.md`](gatling-reasoning.md).
## Rejected: a generic `STree<T>` over the key width
The tidy version is one `STree<T>` parameterised over `i64`/`u32`. We **rejected** it
(the owner's instruction was explicit: *"klona 64 till 32, ge dig inte på att
parametrisera 64/32 bitar"*). Two hard reasons: (1) the branchless SIMD node compare
uses a **different intrinsic per width** (`_mm256_cmpgt_epi64` vs `_mm256_cmpgt_epi32`)
— not a single monomorphisable op, and a generic defeats the const-folding the hot loop
relies on; (2) the two paths' `B` and batch trade-offs were measured **independently**
(`B=8` for i64, `B=16` for u32). Two readable concrete files beat one cleverly-generic
one. The **one** parameter kept generic is `const P`, the pipeline depth — that is the
MLP knob, not the forbidden key-width parametrization. Full comparison in
[`stree-reasoning.md`](stree-reasoning.md).
## Rejected: approximate vector search (IVF) in `vann`
`vann` briefly shipped a `RagnarsEmbIndex` IVF (k-means + posting lists,
`nprobe`-tunable recall). It was **removed** hours after it landed: nornir requires
**exact** (100 %-recall) search, and IVF is approximate — it skips `nprobe` cells and
can miss the true nearest. `vann` today is the exact flat `VectorIndex` oracle plus a
**provably-lossless** int8/VNNI prefilter → f32 rerank (byte-identical to the flat
scan). The IVF design history is in [`stree-reasoning.md`](stree-reasoning.md) (the
"why they diverged" note); the durable idea it left behind is the shared batch-prefetch
SIMD DNA, kept in the exact rerank path.
## Rejected: widening the ring to fix the writer-bound codecs
The DEFLATE codecs saturated cores unevenly (lgz ~3.1, ljar ~3.5, lzip ~4.0 of 12; lbz2
~10.4). The tempting fix was **more slots** — widen `ring_slots` so more chunks are in
flight. **Refuted:** widening moved nothing, because the bottleneck is not pipeline width
but the **single ordered collector + writer** at the tail. The decode fan-out is already
parallel; one writer thread copying and writing gigabytes is the serial drain. The right
fix removes the writer, not adds slots — the `gatling::parwrite` layer, where each worker
writes its own disjoint output (independent files, or offset-`pwrite`d regions of one
pre-sized stream). See the writer story in [`gatling-reasoning.md`](gatling-reasoning.md).
## The codec pipeline consolidation (done)
The four codec CLIs used to hand-roll the `gatling` + `chunk_revolver` pipeline
(Reader→split→N workers→ordered collector→Writer) with raw `mpsc` wiring — the decode
fan-out is now on the shared `gatling_for_each` engine (no rayon, no private pools). The
**writer tail** is now lifted too: rather than one ordered writer, the parallel-writer
`parwrite` layer lets workers write their own output, and every DEFLATE CLI's writer is
wired onto it on `master` (cores_busy: ljar/lzip ~11.5/12, lgz ~7.8/12, lbz2 held ~10.4).
The history is tracked in the årsstädning
audit ([`arsstadning-history.md`](arsstadning-history.md)) and the
[`chunk_revolver-unsorted.md`](chunk_revolver-unsorted.md) plan.
## Why the bake-off is HEAVY-only
There is no small/smoke mode for the bake-off (heavy-bench law). A per-core throughput
claim measured on a toy corpus is a lie — cache effects, memory bandwidth, and core
scaling only show at full size. So the harness runs one large corpus at all cores with
max memory, records host CPU + cores + peak RSS per rival, and refuses to pretend a
quick run is comparable. The numbers in [`benchmarks.md`](benchmarks.md) are only ever
HEAVY runs.