re(ui): scale 0 means collapsed, not "unset" -- stop drawing it full size

blit() and fill_quad() both opened with

    let sx_pct = if kf.scale_x == 0 { 100 } else { kf.scale_x };

so an element whose pose is collapsed to nothing rendered at FULL SIZE.
I first described this as "a 1-pixel sliver", reading the .max(1) in the
size arithmetic two lines below -- the guard above it meant .max(1) never
saw a zero. Read the whole function, not the lines you went looking for.

Control run before the change: if 0 meant "unset", something on the disc
would use it throughout. Nothing does.

  elements with a keyframe group                    15 493
  at least one keyframe at scale 0                   2 166
  EVERY keyframe at scale 0                              0
  zero is a transient the element grows out of       1 762

ptlogo_eff3.t32 runs 0% -> 200%. An "unset" marker no element ever uses
throughout is not a marker.

Both functions now return without drawing when either scale is 0.

Reach: 24 of 24 renders byte-identical across GP_TITLE (all 16 builds),
GP_PAUSE_MENU and GP_OPTIONS -- additive on every screen the port needs.
195 elements have a guessed rest pose at scale 0 and 126 of those have a
non-zero alpha, so the old code painted them; all 126 are in
GP_READY_ROOM.pak, which S1 already declared a no-go. They are
tactical-map sprites at sx=0/sy=100, mid-horizontal-wipe, that the
coercion drew full-width.

So: a real correctness fix with no visible effect on the menu port.
Suite green, 122 passed / 0 failed across 3 suites at commit time.
This commit is contained in:
Sylpheed RE agent
2026-08-28 23:38:37 +00:00
parent c2c69b02be
commit b973d8cde3
7 changed files with 187 additions and 4 deletions

View File

@@ -711,3 +711,48 @@ full cycle length has to come from measurement.
❔ The word at `+0x004` is **not** the keyframe count: `0x003c0000` (60) with 8
keyframes here, `0x001e0000` (30) with 3 in `ptloop01.rat`/`ptloop02.rat`.
## ✅ `scale = 0` means COLLAPSED, not "unset" (2026-08-28)
`ui_layout::blit` and `fill_quad` both used to open with
```rust
let sx_pct = if kf.scale_x == 0 { 100 } else { kf.scale_x };
```
so an element whose pose is *collapsed to nothing* was drawn at **full size**.
(The `.max(1)` two lines below never saw a zero — see [METHOD.md](../METHOD.md).)
### The control, run before the change
If `0` meant "unset", some element would use it throughout. Over the disc
([`data/zero-scale-census.txt`](../data/zero-scale-census.txt)):
| | |
|---|---|
| elements with a keyframe group | 15 493 |
| at least one keyframe at scale 0 | 2 166 |
| **every** keyframe at scale 0 | **0** |
| zero is a transient the element grows back out of | **1 762** |
Not one element on the disc is zero-scale throughout, and 1 762 grow out of zero
— `ptlogo_eff3.t32` runs `0 % → 200 %`. **An "unset" marker that nothing ever
uses throughout is not a marker.** Zero means zero.
### The fix, and its reach
Both functions now return without drawing when either scale is 0.
* **Renders: 24 of 24 byte-identical** across `GP_TITLE` (all 16 builds),
`GP_PAUSE_MENU` and `GP_OPTIONS`. The change is additive
on every screen the port needs.
* 195 elements have a guessed rest pose at scale 0; **126 of those had a non-zero
alpha**, so the old code painted them at full size.
* ⚠️ **All 126 are in `GP_READY_ROOM.pak`** — the archive
[S1](../ready-room-probe.md) already declared a no-go. They are tactical-map
sprites (`pbacropolis`, `pbFriendly`, `pbr_enemy`) at `sx=0, sy=100`, i.e.
mid-horizontal-wipe, which the old coercion drew full-width.
🟡 So this is a **real correctness fix with no visible effect on the menu port**.
It matters for anything that later reads `GP_READY_ROOM`, and it removes a case
where the renderer contradicted the format.