# 📦 WP-155 — `refine(data): letterbox and instance filter come from upstream`

> Engineering log · [Phase 14 — Augmentation upstreaming to `fuse`, v0.7.0](https://borda.github.io/lucid-YOLO/engineering/phase-14-augmentation-upstreaming/)
>
> 🔗 Depends on [WP-154b](https://borda.github.io/lucid-YOLO/work_packages/wp-154b/) · Needed by WP-155b
>
> ⬅️ [WP-154b](https://borda.github.io/lucid-YOLO/work_packages/wp-154b/) · [Roadmap](https://borda.github.io/lucid-YOLO/ROADMAP/) · [WP-156](https://borda.github.io/lucid-YOLO/work_packages/wp-156/) ➡️


## WP-155 and WP-155b — the letterbox and the keep mask delegate, and the one pixel metric that moved

<a id="wp-155"></a> <a id="wp-155b"></a>

The two rows land as one commit. They were split so a delegation swap would never travel with a frozen value (§6.5), and the split cannot be honoured here: the swap moves a value, so WP-155 alone cannot pass `make gate` and WP-155b alone has nothing to measure. The choice was between a commit that mixes them and a red `main` between two commits; the principal took the mixed commit. What §6.5 buys — the ability to name the row that moved a value — is preserved by the measurement below rather than by the commit boundary.

**What delegates.** `Letterbox` (`data/letterbox.py`) drops `_LetterboxGeom`, `_resolve_geometry` and `_resize_pad`. The fit comes from `letterbox_geometry`, the forward affine from `letterbox_matrix`, and the image from a letterbox-only `Compose.from_params(letterbox=..., allow_upscale=..., fill=..., padding_mode="zeros")` built once at construction — one `grid_sample` from the source canvas straight to the letterboxed one instead of an `F.interpolate` followed by an `F.pad`. `inverse_map` and `forward_affine` keep their signatures and their semantics, so the six evaluation and prediction call sites are untouched. The inverse stays local and closed-form (`inv_r`, `-pad * inv_r`) rather than being obtained by inverting the forward matrix numerically, because A10 requires the round trip to be exact and a numerical inversion leaves a residual in it. `RandomAffine` and `MosaicAssembly` drop their two byte-identical `_keep_mask` copies for upstream's `instance_keep_mask` at all six call sites.

**The threshold trap, made loud.** Upstream defaults `min_size` and `min_visibility` to `0.0` — deliberately, so the package holds no opinion about a training recipe's numbers — against this project's `2.0` and `0.1`. Both are passed explicitly at every one of the six call sites, and `TestKeepThresholdsReachUpstream` in `test_affine.py` and `test_mosaic.py` covers each of the three routing sites per class with a size case, a visibility case and a control. The mosaic control states the cost of an omission exactly: at `0.0`/`0.0` not only the 4%-visible sliver survives but the three instances the placement clips away to nothing survive too, a zero-width box being `>= 0.0` on both rules — three empty instances carried down the pipeline with every shape lining up and nothing raising.

**The measurement WP-155b owns.** Geometry is bit-unchanged: `letterbox_pad_fraction` holds at `0.4`, every coordinate round-trip case passes, `goldens/data_checksums.json` does not move, and 35 of 36 goldens pass untouched. Exactly one value moved — `aug_invariants`' `letterbox_image_mean`, `0.5024 → 0.4994`, against a `0.002` tolerance, so 1.5× tolerance and no more. That is the whole eval-path cost of the resampling swap, and it is one number rather than a class of drift because the geometry did not move with it. Upstream's opt-in `antialias=True` was measured rather than assumed: it recovers nothing here (still `0.4994`), its Gaussian mipmap prefilter not engaging at this producer's downscale, so keeping the antialiased filter was not available short of keeping the local implementation. A32 is revised rather than contradicted — non-antialiased resampling is now the whole pipeline's filter rather than the train path's deviation from the eval path, and the two paths differ only in warp count.

**The frozen golden, overridden a second time.** `goldens/frozen/0.6/aug_invariants.json` pins that same metric, so the same §7 wall as [WP-154b](https://borda.github.io/lucid-YOLO/work_packages/wp-154b/), on the same file, eight commits later. The alternatives were put again — remove the file on the [WP-132](https://borda.github.io/lucid-YOLO/work_packages/wp-132/)/WP-154 precedent, or narrow the row to the keep mask and keep the local resample, running two resample implementations and deferring the delegation to 0.7 — and the principal chose the override again: `letterbox_image_mean` updated in place to `0.4994` in both the live and the frozen copy. `docs/ESCALATION.md` (2026-09-03, second entry) carries it. That this is the second override of the same file in one session is the reason it is recorded separately rather than folded into the first: two dated exceptions, still not a standing waiver, and the next one is a fresh decision rather than a precedent already set.

