# 📦 WP-178 — `fix(keypoints): R1 does publish the pose objective`

> Engineering log · [Phase 17 — R1 pose-objective parity](https://borda.github.io/lucid-YOLO/engineering/phase-17-pose-objective-parity/)
>
> 🔗 Depends on [WP-177](https://borda.github.io/lucid-YOLO/work_packages/wp-177/) · Needed by WP-179, WP-180, WP-181, WP-182
>
> ⬅️ [WP-177](https://borda.github.io/lucid-YOLO/work_packages/wp-177/) · [Roadmap](https://borda.github.io/lucid-YOLO/ROADMAP/) · [WP-183](https://borda.github.io/lucid-YOLO/work_packages/wp-183/) ➡️


## WP-178 — the pose section was there all along

<a id="wp-178"></a>

**A false premise, found from outside.** Issue 2 on the tracker reports what the record should have caught itself: R1 sec. 3.4.2 is a pose-estimation section — a sigmoid-bounded sigma branch, Eq. 10's normalized residual, Eq. 11's RLE term and an OKS-based loss the two are combined with — Table 9 ablates the OKS/RLE split and Table S10 publishes every pose scale's size, all inside R1's allowlisted role. D21, A65, A68, `_build_keypoint_stem`'s docstring, `test_param_flops.py`'s comment, the [WP-139](https://borda.github.io/lucid-YOLO/work_packages/wp-139/) entry above and both keypoint sections of the reproduction report say R1 has no such section. The citation map shows how: sec. 3.4.1 and 3.4.3 are cited across the tree, Eq. 7–9 and 13–15 likewise, and nothing between them — the gap was visible without opening the paper, and `model_cards/keypoints.md` had quoted Table 9's 63.0 since 0.5.0 while calling it a different paper's number. Confirmed 2026-09-17 against the arXiv PDF, not against the issue's paraphrase.

**What changes and what does not.** D22 withdraws D21's rationale clause and nothing else — the tier stays keypoints-not-pose, classification and YOLOE-26 stay out — and states what shipped as a *partial* reproduction of R1's objective: Eq. 11's RLE term, transcribed from R14 where R1 itself draws it, without the OKS term. In the registers and logs — DECISIONS, ASSUMPTIONS, this log, PROVENANCE — every stale sentence is struck in place with the date, on D21's own reasoning about records; the reproduction report, append-only under D10, gets a correcting note after its last section and its 0.5.0 section stays byte-identical; the model card, two docstrings and a test comment are rewritten outright with a dated note, since none of those is a record of a decision. Three findings the section forces are registered rather than resolved. A75: R1 gives the OKS loss no formula and cites it to a paper outside the allowlist, so `losses/oks_loss.py` derives `1 - OKS` from R12's own definition — box area for `s^2`, since a keypoints batch carries no segment area — and enters at `oks_gain = 0.0`, keeping the shipped objective bit-exact and [WP-125](https://borda.github.io/lucid-YOLO/work_packages/wp-125/)'s paired-run evidence meaningful; Table 9's `(24, 1)` is the published split no run here has tested. A76: R1's printed Eq. 11 carries `log sigma` twice, once as the reparameterization Jacobian and once inside a Laplace term written in the un-normalized frame, where R14 Eq. 8 — what the code carries — has it once; the two differ by a `1/sigma` gradient on every scale, and which R1 means is not settled by its text, so the form stays and the paired run that would settle it is a roadmap item. A77: measured at Table S10's protocol the point stem is 13.0% / 26.5% under the paper's `n` params / FLOPs and converges by `l`/`x` — the same finding A20 made for the angle stem before its width was chosen against Table S11, and it gets the same treatment: recorded, gate deferred until a width is selected against the table, nothing tuned in a correction commit.

**What the term costs switched off.** `_score_branch` gathers each branch's assigned positives once and scores them with both terms, so the added cost at `oks_gain = 0` is one OKS evaluation per branch per step over already-gathered tensors — an exponential over the labelled points, no decode and no second argsort. It is logged pre-gain as `train/oks` for the same reason every other term is, so a run can see the number it is not yet optimizing.

**The lesson is a check, not a resolution.** Before the record says a source does not cover something, grep what the record already cites from that source and look for the hole: two adjacent subsections cited and the one between them never is not an absence in the paper. `.notes/lessons.md` carries it as a rule.

