# ✅ A nested `.rat` leaf animates on its OWN timeline — the parent's alpha does not multiply in **Status:** ✅ **DECODED**, against a GPU draw capture rather than our renderer. Answers the port's question: it emits both a parent record and its nested leaf, each with its own alpha ramp over a different span, and would not draw the leaf without knowing the composition rule. ## The question For the title's light sweeps, parent and leaf disagree about everything: | | `ptloop01` parent | its leaf `pteff03.t32` | |---|---|---| | alpha | 0 → 255 over t=70…100, held to 238, → 0 at 250 | 255 → 128 at t=150 → 255 at 540 | | scale | (100, 100) | **(100, 600)** | | rotation | 0 | **+30°** | | x | fixed 441 | **−639 → −39 → 1521** | `ptloop02`'s leaf `pteff03a.t32` is the mirror: (100, **800**), **−45°**, x sweeping 1721 → 1111 → −839, alpha 0 → 128 → 255. ## The oracle The per-draw capture records **vertex colours**, and on the title's `ptloop` draw (draw 2) they are `C3FFFFFF` and `B6FFFFFF` — **alpha 195 and 182**, not 255. So the composed alpha the game actually submitted is observable. ## The measurement Fitting **only the two alphas** to the two leaf ramps gives a single consistent time, **t = 355**: | | leaf value at t=355 | observed | |---|---|---| | quad A alpha | **194.8** | **195** | | quad B alpha | **182.2** | **182** | | parent alpha (both) | **0** | — | 🔴 **Multiplying the ramps is refuted.** The parent has expired by t=355 (it returns to 0 at t=250 and a group **holds** at its last keyframe), so `leaf × parent / 255` predicts **0 for both quads** — the sweeps would be invisible. They are drawn, at 195 and 182. ✅ **And the position check was PREDICTED, not fitted.** Nothing about x entered the fit; the same t=355 then places the quads from the leaves' own sweeps: | | from the leaf at t=355 | measured off the capture | |---|---|---| | quad A centre x | **981** | **992.0** | | quad B centre x | **478** | **467.2** | Within ~11 px, on 400-px-wide quads travelling 1 560 and 1 950 px. Four quantities — two alphas and two positions, from two differently-shaped ramps — all agree on one time. ## The rule **A leaf carrying geometry animates on its own timeline. The parent's alpha does not gate it.** For these records the parent is a container: it has no sprite, and its keyframes describe nothing that is drawn. ⚠️ **Reach, and it is not a universal rule about leaves.** This is one draw, one capture, one element pair, and it is specifically the case where **the parent carries no geometry**. The opposite case is already recorded: for a button, a base record's leaf *duplicates* the parent and the **parent wins** ([`ui-button-focus-record.md`](ui-button-focus-record.md)). So the discriminator is which record actually carries the geometry, not a fixed precedence. ❔ What is **not** established: whether the parent alpha would multiply in during a window where it is non-zero. Every observation here has parent = 0, so "the leaf wins" and "the parent is ignored because it has nothing to draw" are not separated. A capture during t=100…238 would separate them. --- ## 🔴 The port's `x = −324` is the OLD keyframe association, applied to leaves Implementing the rule above, the port reported the leaf's top-left at **x ≈ −324** at t=355 — off-screen left — against **781** (centre 980.5) here. Both cannot be right, and the disagreement is not about rotation or pivots. Their stated pairing is *"t=150 at x=−639, t=540 at x=−39"*. On the disc the leaf reads: | pose x | its time | the time it would take under the OLD association | |---|---|---| | −639 | **0** | 150 | | −39 | **150** | 540 | | 1521 | **540** | 600 | | 1521 | **600** | — | **Their pairing is the second column.** Each pose is taking the *next* pose's time — which is exactly the association [`ui-keyframe-record-layout.md`](ui-keyframe-record-layout.md) refuted and HANDOFF carries a red banner about: *a keyframe's time comes **before** its pose*. Feeding their pairing into the same interpolation reproduces **−324** to the digit. ✅ **With the corrected association**, t=355 gives top-left **781**, and centre **980.5** for a 399-wide sprite — against **992.0** measured off the capture. ⚠️ **So this is the same defect as the top-level one, in the leaf path.** The top-level parser was corrected on 2026-08-29; a leaf is read by `parse_build` on a sub-slice, so anything reading leaves through a *separate* path can still carry the old association. That the alphas nonetheless matched is the trap: alpha at t=355 is inside a long segment where a one-keyframe shift barely moves it, while **x is sweeping 1 560 px over the same span** and the shift is glaring. **A rule can look confirmed on the insensitive quantity and be wrong on the sensitive one.** ## ✅ The 11.5 px residual is CLOSED — it was the alpha-only fit's resolution Left open above as *"do not fit to close it"*. It is closed by **adding observables, not by tuning a parameter**. The draw's vertex buffer carries positions *and* colours at the **same instant**, so all four quantities must agree on one `t`. Solving for `t` from each independently: | observable | solved t | sensitivity | its own precision | |---|---|---|---| | quad A x | **357.88** | 4.00 px/unit | ±0.12 units | | quad B x | **357.58** | 4.06 px/unit | ±0.12 units | | quad A alpha | 355.75 | **0.326 levels/unit** | **±1.54 units** | | quad B alpha | 354.09 | **0.265 levels/unit** | **±1.89 units** | ⚠️ **The alphas are ~50× less precise per unit of time**, because alpha is a byte changing by only ~0.3 levels per keyframe unit — so a single level of quantisation is worth **1.5–1.9 units**, which at 4 px/unit is **6–8 px of sweep**. The 11.5 px was that, not geometry. At the position-derived **t = 357.7**, every observable lands: | | predicted | measured | diff | |---|---|---|---| | quad A centre x | 991.30 | 992.0 | **−0.70 px** | | quad B centre x | 466.72 | 467.2 | **−0.48 px** | | quad A alpha | 195.64 | 195 | +0.64 | | quad B alpha | 182.95 | 182 | +0.95 | ✅ **Sub-pixel on both positions, inside one byte on both alphas** — while the parent's alpha is **0** throughout. And the leaf **pivot is (200, 90)** against a 399×180 sprite, so rotation displaces the centre by essentially nothing and there is no pivot/rotation correction to find. ⚠️ **To half a pixel, not exactly** (the port's check, and worth keeping): the sprite is **odd-width**, so its true centre is **199.5**, and the declared pivot is 200. The 0.5 px offset is far inside the −0.70 / −0.48 px agreement above and changes nothing here — **but do not lean on "the pivot IS the centre" for a sub-pixel claim.** It is the centre rounded up. ⚠️ **The methodological point is the same one this exchange started with, inverted.** Earlier, a rule *looked confirmed* because it was checked against alpha — the insensitive field. Here the same insensitivity produced a spurious 11.5 px residual. **The insensitive quantity does not just fail to falsify; it manufactures apparent error.** Solve on the fastest-moving field and check the slow one, never the reverse. --- ## 🔴 Refutation — "125 % is the only non-whole-multiple scale" is WRONG, and by a lot `DECISIONS.md` records `title_jp`'s `ptlogo_eff2` at 125 % as *"the single drawn element in the whole export at a scale that is not a whole multiple of 100 %"*. That census was over **parents only**. Opening the 45 leaves as well (`--example scale_census`, output at [`data/ui-scale-census-with-leaves.txt`](../data/ui-scale-census-with-leaves.txt)): | scale | count | where | |---|---|---| | **75,75** · **96,96** · **99,99** | 4 each | `pgloading_loop4.rat` on all four loading screens | | **75,100** · **96,100** · **99,100** | 4 each | `pgloading_line.t32` | | **101,101** · **103,103** · **112,112** | 12 each | `ptlogo1` / `ptlogo2`, entries 4 **and** 7 | | **150,150** | 28 | `pgloading_loop1.rat` | | **204,208** · **210,220** | 1 each | `ptlogo_eff2` … no: `ptlogoall_eff.t32`, entry 4 | | **250,250** | 2 | `pgloading_loop5.rat` → **LEAF** `pgloading_ring.t32` | | **125,125** | **2** | `ptlogo_eff2.rat`, entry 7 | **Thirteen distinct non-whole-multiple scales, and 125 % is among the rarest at 2 occurrences.** `ptlogo1`/`ptlogo2` carry 101/103/112 on the **English** title too, so this is not a Japanese-build peculiarity. ⚠️ The claim's real content was "the only one *the port draws*", which is a statement about the export's element set, not about the disc. ## ✅ And `ptlogo_eff2` itself is decoded — the 125 % is a POP, not a steady scale ``` PARENT ptlogo_eff2.rat pivot (169,169) t=0 a=0 scale (100,100) pos (412,96) t=50 a=0 scale (0,0) t=59 a=255 scale (125,125) t=71 a=255 scale (125,125) t=107 a=0 scale (0,0) LEAF ptlogo_eff2.t32 kind 0x8 a=160 scale (100,100) rot 0 → 360 over t=0…960 LEAF ptlogo_eff2.t32 kind 0xc a=80 scale (100,100) rot 0 → 360 over t=0…960 ``` * The **125 % lasts 57 units (~0.95 s)** — a scale-0 → 125 % → scale-0 flash between t=50 and t=107. It is a transient, not a steady state, which is why it looks anomalous in a census of resting poses. * The **leaf draws at 100 %**, as **two superimposed copies** of the same sprite at alpha **160** and **80**, each rotating **a full 360° over 960 units** — a slow double-layered spin, 16 s per revolution at 60 units/s. 🔴 **And this is exactly the case my `ptloop` rule could NOT separate.** There the parent had expired (alpha 0, no sprite) so "leaf wins" and "parent ignored" were indistinguishable. Here the parent carries **real geometry** — a scale that reaches 0 twice. If parent scale gates the leaf, the spin is a 0.95 s flash; if the leaf runs on its own timeline, it spins continuously for 16 s. **The two readings differ enormously and nothing on the disc chooses between them.** ❔ **Undecodable here, with reach:** `title_jp` has **no oracle capture**, so this cannot be adjudicated in this container at all. The port is right to withhold it. A capture of the Japanese title would settle it — and that is the same Japanese-locale capture MISSION has parked as 🟡 since 2026-08-29.