# ljar — reasoning
> **Class:** `reasoning` · **Function:** `ljar`. Why the engine is shaped this way and
> where the real saturation risk lives. The implemented shape is in
> [`ljar-design.md`](ljar-design.md).
## Why JAR is a genuinely parallel problem
Unlike gzip's single continuous LZ77 window, a JAR is a **set of independent members**:
each entry is its own DEFLATE (or STORE) stream with its own CRC-32, indexed by a Central
Directory at the file tail. There is no cross-entry back-reference to decode through. So
after one cheap serial step — parse the Central Directory — every entry can decode on its
own core. That is embarrassingly parallel, and it is the common shape (a fat JAR is
hundreds of `.class` files).
## Why the balanced fan-out, not a plain one
The trap is **skew**. Real archives are lopsided: one 40 MB shaded uber-jar payload among
a thousand tiny stubs, or a single fat resource among the class files. A plain fan-out
hands each worker the next index; whichever worker grabs the monster runs alone long after
its siblings have drained the tail — one busy core, the rest idle, exactly the
core-saturation bug we refuse to ship. Weighting each unit by `uncompressed_size` makes the
gatling schedule **heaviest-first** (LPT / longest-processing-time): the monster is claimed
first, and the cheap tail backfills the workers that finish early. It is a scheduling
change only — `streaming_skewed_roundtrip_byte_identical` (`ljar/src/reader.rs`) proves the
decoded bytes and the output ordering are untouched.
## Why streaming sorts by offset and batches
The streaming reader (`ljar/src/reader.rs:38`) sorts entries by `local_header_offset` so
the compressed reads walk the file forward — sequential I/O, not a seek storm — and it
decodes in batches of 64 (`BATCH_SIZE`). Batching bounds memory: only one batch of
decompressed output is resident at a time, so peak RSS is independent of JAR size and the
file is never fully loaded. The alternative (decode everything into one `Vec`) is an
O(output) peak-RSS regression for no throughput gain.
## Why not a bespoke thread pool
Earlier ljar carried its own rayon `ThreadPool` (`LJAR_THREADS`); it is gone. The
constellation's no-rayon law (`tests/rayon_free_law.rs`) folds every codec onto the one
shared `gatling` engine, so the ordering, self-dispatch, and index-ordered collection are
proven once and reused (`gatling/src/gatling_forkjoin.rs:209`). See
[`gatling-reasoning.md`](gatling-reasoning.md). (The `parallel.rs` module docstring still
*says* "rayon thread pool" — that comment is stale; the code calls gatling.)
## Honest numbers
No `cores_busy` is hard-coded here. The real figures land in the
[`README.md`](README.md) A/B/C mashup on the quiet-box heavy-bench wave.