# The reference renderer can now express the blend the disc declares — and 8 claims re-open **Status: ✅ done, with controls.** Instrument: ⟨our-renderer⟩, deliberately — this page is about a change to *our* instrument, not a claim about the game. 2026-09-01. --- ## Why `T8aD +0x04` bit `0x02` has been **decoded** since 2026-08-31 — 35 elements over three screens against `RB_BLENDCONTROL0` read out of the guest command stream, zero errors, plus an out-of-sample hit on `GP_OPTIONS` ([`structures/ui-blend-mode-decoded.md`](structures/ui-blend-mode-decoded.md)). `ui_layout::blit` could not draw it. The code said so in a comment that had gone stale: > *"Straight alpha-over. `T8aD +0x04` bit 0x02 was tested as an ADDITIVE selector > and REFUTED — it moved every metric against the title capture the wrong way … > so the bit is carried but not acted on."* That refutation is `⟨render-vs-capture⟩`: it was **this renderer disagreeing with itself**, recorded while the same renderer had a stale keyframe association, no leaf geometry and no rotation. The corpus's own rule — *a claim resting on our renderer is a claim about our renderer* — applies to it, and R1 is exactly the machinery for noticing. The port raised the consequence: `verify-screen` compares two renderers, and if one of them **structurally cannot** express a field the disc declares, the check is incapable on every screen that uses it — **12 of 16** — and its tolerance silently excuses all of them. A quiet check is worse than a failing one. ## What changed One function. Both the rotated and unrotated paths now route their per-pixel combine through: ```rust fn combine(additive: bool, sc: u32, sa: u32, dc: u32) -> u8 { if additive { (dc + sc * sa / 255).min(255) as u8 } else { ((sc * sa + dc * (255 - sa)) / 255) as u8 } } ``` **Both equations are read off the game's own pixel shader**, dumped from the running guest and disassembled in [`ui-splash-draw-pass.md`](ui-splash-draw-pass.md). The shader premultiplies — `oC0 = (rgb·A, A)` — so only the blend register differs: | `RB_BLENDCONTROL0` | is | gives | |---|---|---| | `0x07010701` | `ONE / ONE_MINUS_SRC_ALPHA` | `dst' = rgb·A + dst·(1 − A)` — source-over | | `0x01010101` | `ONE / ONE` | `dst' = rgb·A + dst` — additive | So the additive case **saturates rather than wrapping**, and a transparent or black source is the identity in it. Neither is a choice; both fall out of the equation. The flag needs no plumbing: `t8ad::parse` already stores the `+0x04` word as `T8adImage::flags`, so both call sites read `img.flags & 0x02 != 0`. ## The controls Following the precedent of `rotation_control_known_angles` — pin against answers that are **arithmetic**, not opinions. | test | pins | |---|---| | `additive_control_black_source_is_identity` | adding zero changes nothing | | `additive_control_alpha_zero_is_identity` | a transparent source is the identity | | `additive_control_known_sums` | `40+100=140`, `200+100=255`, `250+10=255` — it **saturates** | | 🔴 `additive_control_bit_actually_selects` | **the discriminator** | ⚠️ **The fourth one is the only one that can fail for the right reason.** The first three pass just as well if `blit` ignores the flag and draws everything additive. The discriminator takes one sprite, one pose, one canvas, flips only the blend, and requires **two different answers**, each equal to its own equation: alpha-over `(100·128 + 80·127)/255 = 90`, additive `80 + 100·128/255 = 130`. This is the failure class the port hit the same day — *"removing the latch does not remove the threshold, so the row could never invert"* — and one I hit in the pad decode, where a backward scan silently resolved every guard to "internal". A control that removes the mechanism but not the observable tests nothing. `120 passed; 0 failed` on the full library suite, so no unrotated screen regresses. ## What it changes, per screen [`data/additive-elements-per-screen.txt`](data/additive-elements-per-screen.txt) — **67 sprites over 14 screens** that our renderer was drawing with the wrong blend, including **10 of 18 on the title** (`pteff01`, `pteff03`, `pteff03a`, the five `ptlogo_back2eff*`, `ptlogoall_eff`) and `ptbtn00f`, the `PRESS Ⓐ` plate's own highlight. ## 🔴 R1: what this re-opens `tools/stale-instrument render-vs-capture` — **8 claims**, and they are not peripheral. Three bear on the current focus: * *"`rest()` for a plateau-less element should be the last keyframe"* — 🟡, and its sibling, **both legs of the pair the R1 pass re-opened in both directions**; * *"an element with no held pose should be drawn as NOTHING rather than at a guessed endpoint"* — 🟡; * *"the plate-free title capture (t ≈ 4.0 s) may be too early to be settled"* — 🟡, which sits directly on play-test finding 3; * *"the shifted time reading implies `rest` = the last keyframe"* — 🟡. Every one of them died to a renderer that drew ten of the title's eighteen sprites with the wrong blend. **None is re-derived here** — this page only records that the instrument that killed them no longer exists in that form. ## Corroboration of another agent's claim, recorded per the adversarial duty **Target:** the port's *"H5 closes on your bit — `pgloading_loop5` is additive."* I could not find `loop5` as a sprite **anywhere in any pak**, which looked like a contradiction. It is not: `pgloading_loop5.rat` is an **element**, and it resolves to the sprite `pgloading_ring.t32`, which the bit marks additive. ``` GP_TITLE.pak entry 12 element 'pgloading_loop5.rat' sprite pgloading_ring.t32 additive=true GP_TITLE.pak entry 15 ditto GP_SAVE_LOAD.pak entry 46 ditto GP_SAVE_LOAD.pak entry 69 ditto ``` **Their claim stands.** Recorded because the element/sprite name split is a real trap — a census keyed by sprite name will not find an element by its own name, and I nearly reported a false contradiction from exactly that. ## Reach, and what this is NOT ⟨our-renderer⟩. This makes our renderer **able** to express a decoded field; it does not make it right, and it is **not** evidence about the game. The evidence for the field is `ui-blend-mode-decoded.md`'s GPU measurement, which predates this and does not depend on it. ⚠️ `.prm` primitives carry no `T8aD` header, so the bit cannot speak for them and this path never fires for one. ⚠️ **Not re-run:** `verify-screen` across the 16 screens. The numbers there will move, and the port's own generalisation applies to reading them — `raw-rmse` is **area-weighted**, so ask "broad or deep?" of any row that moves before calling it a regression.