# Seeing Without Dependencies: A Measurement of a Hand-Written JPEG and DEFLATE Image Path, Its Sampling Bias, and Its Abstention Contract

Daozhendong Yang, School of Intelligent Science and Engineering,
Yunnan Technology and Business University, Kunming, China — <hi@yxpil.com>

Paper about **styx-vision**, a two-crate Rust workspace that gives a local-first
assistant a way to look at images without a model and without a decoder
dependency: a hand-written baseline {JPEG} decoder, a hand-written {DEFLATE}
inflater for {PNG}, a {BMP} reader, a strided picture-statistics layer, a
Chinese sentence renderer with an explicit abstention contract, and three
optional remote backends behind one `Composer`.

## arXiv classification

- **Primary:** `cs.SE` — Software Engineering
- **Cross-lists:** `cs.CV` (Computer Vision and Pattern Recognition), `cs.PL`

The same classification is stated in the header comment of `main.tex`.

## Abstract (plain-text rendering)

A local-first assistant that cannot see images has two unattractive options: ship
a vision model, or ship a dependency. styx-vision rejects both. It hand-writes a
baseline JPEG decoder, a DEFLATE inflater for PNG, and a BMP reader in pure safe
Rust, computes a fixed set of picture statistics from the decoded pixels, and
renders them into a structured Chinese sentence for a text-only language model.
The claimed cost of the design is two runtime crates; the claimed benefit is that
the layer can never be absent, so the assistant can always be told something —
including, when the pixels cannot be decoded, that nothing can be told.

The decoder is real. All 10 reference JPEGs land inside the project's published
tolerance (worst mean per-channel difference 0.0938 levels, worst single-pixel
difference 3, no pixel differing by more than 8), and all 52/52 synthesized PNGs
and 5/5 BMPs round-trip bit-exactly, including streams built from each of the
three DEFLATE block types and each of the five PNG scanline filters. The
abstention contract holds: a progressive JPEG is refused, yet its 64×48
dimensions are still reported, and in all 10 remote deployment states the
description is non-empty. The gaps are in the parts nobody unit-tests. The
≤120,000 sampling stride changes mean saturation by 1.7e-4 at 3.84 megapixels
while visiting 3.1% of the pixels, but inflates the colourfulness estimate by a
factor of 32. All five injected one-line decoder defects are caught — though the
upsampling defect slips past the criterion's mean bound and is caught only by its
outlier condition, and the DEFLATE defect is invisible to the pixel-reference
suite by construction. On the remote path, every image costs two detector
requests because one backend is invoked twice, an 8 s per-request budget becomes
33.6 seconds against a slow sidecar, a 16-megabyte model reply is passed into the
prompt verbatim as 16,777,385 characters, and the release itself does not pass
its own cargo fmt or clippy gates — while 127 tests do pass.

## Abstract for the arXiv form (1,920-character limit)

The abstract printed in the paper is longer than the form accepts; paste this
version into the arXiv metadata field instead. It keeps every measured number
and drops only prose.

> A local-first assistant that cannot see images has two unattractive options:
> ship a vision model, or ship a dependency. styx-vision rejects both. It
> hand-writes a baseline JPEG decoder, a DEFLATE inflater for PNG, and a BMP
> reader in safe Rust, computes fixed picture statistics from the decoded
> pixels and renders them into a structured Chinese sentence for a text-only
> model. The claimed cost is two runtime crates; the benefit is that the layer
> can never be absent, so the assistant is always told something — including
> that nothing can be told.
>
> The decoder is real. All 10 reference JPEGs land inside the project's
> published tolerance (worst mean per-channel difference 0.0938 levels, worst
> pixel difference 3, none above 8), and all 52/52 synthesized PNGs and 5/5
> BMPs round-trip bit-exactly, including streams from all three DEFLATE block
> types and all five PNG scanline filters. The abstention contract holds: a
> progressive JPEG is refused, yet its 64×48 dimensions are still reported,
> and in all 10 remote deployment states the description is non-empty. The
> gaps are in the parts nobody unit-tests. The ≤120,000 sampling stride
> changes mean saturation by 1.7e-4 at 3.84 megapixels while visiting 3.1% of
> the pixels, but inflates the colourfulness estimate by a factor of 32. All
> five injected one-line decoder defects are caught — though the upsampling
> defect slips past the criterion's mean bound and is caught only by its
> outlier condition, and the DEFLATE defect is invisible to the
> pixel-reference suite by construction. On the remote path, every image costs
> two detector requests because one backend is invoked twice, an 8 s
> per-request budget becomes 33.6 seconds against a slow sidecar, a
> 16-megabyte model reply enters the prompt verbatim as 16,777,385 characters,
> and the release itself does not pass its own cargo fmt or clippy gates —
> while 127 tests do pass.

## The probe, and why it is disclosed

The crate's public API is enough to measure but not to automate: pixel access and
machine-readable output are needed. `scripts/probe/harness_probe.rs` is an
`examples/` binary **we wrote** that calls only public functions
(`decode`, `sniff`, `dimensions`, `analyze`, `analyze_bitmap`, `Composer`) and
prints one JSON object per invocation, in five modes (facts, decode,
dump-to-PPM, roundtrip, remote).

`scripts/build_probe.py` copies the repository (excluding `target/` and `.git/`)
to `scripts/build/<label>/`, drops the probe in as an example, and builds it
there; with `--variant` it also applies one of five one-line source mutations.
**The repository is never modified.** Build exit codes, the probe's SHA-256 and
the mutation anchors are recorded in `scripts/build/builds.json`.

Every claim that depends on the probe is labelled in the paper (§4). The probe
adds no policy and supports no claim about shipped code — it is a measurement
adapter, and the released tree does not contain it.

## What is measured

| Scenario | Fixture | What it answers |
| --- | --- | --- |
| `builds` | base, `http`, five mutated trees | does the instrumented artifact build at all |
| `fixtures` | 10 committed `.jpg` + `.raw` pairs | pixel-for-pixel conformance against libjpeg |
| `progressive` | `progressive.jpg`, a non-image | is a refusal rendered, and is it honest |
| `lossless` | 5 pixel patterns × 2 sizes × 5 PNG filters + 5 BMPs | is the inflater and the unfilter path exact |
| `sampling` | 4 BMPs from 0.12 to 3.84 MP | what the `MAX_SAMPLES` stride actually costs |
| `footprint` | same four sizes | peak RSS and decode time per megapixel |
| `robustness` | 240 mutated inputs, 8 mutation kinds | panics, typed errors, worst-case time |
| `http` | mock sidecar in 10 deployment states | budget, retries, body caps, health semantics |
| `own_tests` | pristine copy of the release | do the repository's five CI gates pass |
| `ablations` | five one-line decoder defects | does the project's own criterion notice |

## File manifest

| Path | Contents |
| --- | --- |
| `main.tex` | Paper source: 10 pages, two columns, seven figures, thirteen tables |
| `refs.bib` | Bibliography (standards, RFCs, and named public artifacts only) |
| `common.tex` | Shared preamble, copied from `papers/_shared/common.tex` |
| `numbers.tex` | Generated `\newcommand` definitions for every inline number |
| `tables/*.tex` | Generated table environments (artifact, conformance, lossless, markers, sampling, robustness, robustnessout, ablations, remote, footprint, timing, gates, vocab) |
| `figures/fig_pipeline.pdf` | The four layers and the abstention branch |
| `figures/fig_conformance.pdf` | Error distribution and per-fixture margin |
| `figures/fig_sampling.pdf` | Stride cost and sampling error |
| `figures/fig_footprint.pdf` | Peak RSS and time against megapixels |
| `figures/fig_ablations.pdf` | Mutation detection and which condition fires |
| `figures/fig_remote.pdf` | Remote state cost and content kept |
| `figures/fig_vocab.pdf` | Tag grouping vocabulary sizes |
| `scripts/styx_lab.py` | Probe runner, PNG/BMP synthesis with known pixels, port of `facts.rs` statistics, corruption corpus, mock sidecar |
| `scripts/build_probe.py` | Scratch-tree copy, the five ablations, cargo build |
| `scripts/measure.py` | Scenario driver, writes the JSON artifact |
| `scripts/audit_repo.py` | Static audit (lines, deps, markers, vocabulary, CI file, `cargo tree`) |
| `scripts/make_tables.py` | Generates `numbers.tex` and `tables/*.tex` |
| `scripts/make_figures.py` | Generates `figures/*.pdf` |
| `scripts/_check_refs.py` | Consistency check: macros, table inputs, figure references, citations |
| `scripts/probe/harness_probe.rs` | The measurement adapter, disclosed above |
| `scripts/results/styx_measure.json` | Every measured number in the paper |
| `scripts/data/audit.json` | Static audit artifact |
| `scripts/build/builds.json` | Build evidence for all seven trees |
| `README.md` | This file |

## Build

Requires a TeX distribution with `pdflatex`, `bibtex`, `natbib` and
`abbrvnat.bst` (TeX Live or MiKTeX), or Tectonic. The paper uses `xurl`,
`enumitem`, `array`, `pifont` and `balance`.

```bash
tectonic -r 12 main.tex
# or
pdflatex main && bibtex main && pdflatex main && pdflatex main
```

The bibliography needs two passes to settle, so Tectonic is given `-r 12`; a
single default run leaves `\cite` placeholders.

## arXiv submission

Upload the contents of `dist/styx-vision.tar.gz` (built with
`bash ../make_package.sh styx-vision`) at <https://arxiv.org/submit>. The archive
contains `main.tex`, `refs.bib`, `common.tex`, `numbers.tex`, `tables/` and
`figures/`; include the generated `main.bbl`. Choose `cs.SE` as the primary
category and `cs.CV` plus `cs.PL` as cross-lists. Suggested comments: *10 pages,
7 figures, 13 tables; measurement paper with a reproduction script set*.

## Reproduce the measurements

Requirements: Windows (the harness samples child RSS through `psutil`), Python
3.13 with matplotlib and psutil, a Rust toolchain (`rustc` 1.98 or newer), and a
clone of the repository.

```bash
# 1. static audit (read-only over the repository)
python scripts/audit_repo.py

# 2. scratch trees: base, the http feature, and the five ablations
python scripts/build_probe.py --repo /path/to/styx-vision --all

# 3. every measurement; --own-tests replays the five CI gates, ~2 min extra,
#    --ablations runs cargo test inside each mutated tree, ~5 min extra
python scripts/measure.py --own-tests --ablations

# 4. regenerate every number, table and figure, then rebuild
python scripts/make_tables.py
python scripts/make_figures.py
python scripts/_check_refs.py
```

A full run takes about ten minutes, most of it in cargo. `make_tables.py` refuses
to emit a table whose header and row cell counts disagree, or an unescaped TeX
special in a header cell, so a stale artifact fails the build rather than
silently printing `??`.

`scripts/results/run/` holds the synthesized images the harness compares against;
the four largest BMPs (up to 11.5 MB each) are regenerated by `measure.py` and are
not shipped in the package.

## Key results

- Conformance: 10/10 committed fixtures inside the project's own criterion;
  worst mean per-channel difference 0.0938, worst single pixel 3 levels, 0 pixels
  differing by more than 8, worst |Δ mean luma| 5.8e-5; 2/10 bit-identical.
- Lossless: 52/52 synthesized PNGs exact across all five scanline filters and
  both stored and dynamic DEFLATE; 5/5 BMPs exact.
- Abstention: `progressive.jpg` is sniffed, sized (64×48) and then refused with a
  typed `Unsupported`; the rendered description keeps the dimensions and carries
  the refusal marker. A non-image produces a 34-character description that
  claims nothing.
- Sampling: at 3.84 MP the stride is 32 (3.1% of pixels visited); |Δ| mean luma
  2.5e-4, |Δ| saturation 1.7e-4, |Δ| contrast 1.0e-3, but colourfulness reads
  2.25e-4 against 7e-6 exhaustive — a factor of 32.
- Robustness: 0 panics in 240 mutated inputs; every non-success is a typed
  `DecodeError` (Truncated 109, Corrupt 55, Unsupported 1, NotAnImage 1) and 74
  mutated files still decode.
- Ablations: 5/5 caught by both channels; `nearest_upsample` is inside the mean
  bound (1.114 ≤ 2.0) and caught only by the 2% outlier condition (2.637%);
  `inflate_overlap_break` is invisible to the pixel-reference suite (its fixtures
  are JPEGs) and caught by the inflater's own unit test.
- Remote: 2 `/detect` calls per image; a 9.5 s sidecar turns the 8 s per-request
  budget into 33.6 s; a 16 MiB reply is accepted in 0.48 s at 50.1 MiB peak RSS
  and all 16,777,385 characters reach the description; `ok:false` health skips
  the backends, plain-text health does not; no redirect following; no private or
  loopback filtering in shipped code.
- Gates: `cargo build --all-targets --all-features` and both test runs pass
  (104 default, 127 with `http`, 0 failed); `cargo fmt --check` fails with 3
  diffs in 2 files and `cargo clippy -D warnings` fails with 18 lints.
- Statics: 6,306 + 1,028 source lines, 0 `unsafe`, 98 unit and 9 integration
  tests, 73 lock-file packages, 11 crates resolved with default features and 58
  with `http`.

## Honest limitations of this paper

The fixture set is 10 synthetic images up to 160×120 generated by one Pillow
build; the reference bytes are one implementation, not the specification; the
remote layer is measured against a mock rather than a real detector or VLM; five
mutations is a detectability study, not a defect-density estimate; RSS is
sampled at 1 ms granularity and is therefore a lower bound. See §7 of the paper.
