docs/re: the title-screen oracle — what it settled, and what it refuted
`BACKLOG.md`'s "the declaration table is not a paint order on every screen" had been reasoned about but never measured. It is now measured, and the entry says so both ways. Confirmed: a framebuffer capture of Canary on the title screen shows the `PROJECT SYLPHEED` wordmarks over the full-screen background, so declaration order is not the paint order there. Refuted, and recorded rather than quietly dropped: * the `0x10`-adjacency first step this entry proposed. The background *is* next to a `kind = 0x10` `.prm` on both screens that have one — but on opposite sides (`GP_TITLE` puts the background after it, `GP_MISSION_SELECT` before), so no rule keyed on the `.prm` orders both. `GP_READY_ROOM` and `GP_OPTIONS` cannot serve as the third and fourth witnesses: neither carries a `.prm` or a full-screen background at all. * the placement-region group order and the RATC child order, as above. * reverse declaration order, by the same capture — it would draw the background over the copyright line, which is visible. And separated out: part of what the bad render showed was not ordering at all but the pivot/scale defect fixed two commits back. `ui-rat-layout.md` gains that rule, the correlation evidence, the unmeasured pivot-vs-centre gap, and the disc-wide demotion of "pivot is exactly half the texture" (49 % agree, 37 % are off by more than 16 px — `ptcopyright` is 694 px wide in the framebuffer and `pivot*2` says 618). Captures: `title-screen-oracle.png` (the game surface cropped out of the emulator window at (1,45), 1:1, bottom 45 rows clipped by the display) and `title-screen-composite-2026-08-18.png` beside it.
This commit is contained in:
@@ -51,6 +51,55 @@ screen, and the per-element visibility toggles isolate a suspect element.
|
||||
otherwise — a screen whose background lands on top is showing you this bug, not
|
||||
a decode failure.
|
||||
|
||||
### 2026-08-18 — measured against the running game; three orderings refuted, and half the symptom was a different bug
|
||||
|
||||
**The premise is confirmed by the oracle**, which this entry had not had: a
|
||||
framebuffer capture of Canary on the title screen
|
||||
([`captures/title-screen-oracle.png`](captures/title-screen-oracle.png)) shows
|
||||
the `PROJECT SYLPHEED` wordmarks (elements 0–5) drawn **over** `ptbase2.t32`
|
||||
(element 13), which is a full-screen background. Declaration order is therefore
|
||||
not the paint order on this screen, and no reading of the element table changes
|
||||
that.
|
||||
|
||||
**But part of what the render showed was not the paint order at all.** In the
|
||||
capture `ptbase2` covers the whole screen; the compositor drew it as a
|
||||
960×540-visible slab starting at (320,180), because a keyframe's `scale` was
|
||||
being grown from the keyframe's corner instead of about the declared **pivot**.
|
||||
Fixed, and pinned against the capture by cross-correlation (peak at (0,0)) — see
|
||||
[`structures/ui-rat-layout.md`](structures/ui-rat-layout.md). That was a real
|
||||
defect worth separating out: it moves **865** of the disc's 5 130 resting
|
||||
placements, on every screen, independently of any ordering question.
|
||||
|
||||
**Three candidate orderings are now dead**, all cheaply:
|
||||
|
||||
- **The placement region is not a second ordering.** Its keyframe groups carry an
|
||||
explicit element index, so they *could* be stored in a different order — they
|
||||
are not, on **every** build on the disc (`placement_region_order_is_never_a_second_ordering`,
|
||||
>500 builds, identity every time).
|
||||
- **The RATC child order is not it either.** For the title build it is the
|
||||
declaration order with the `.prm` elements absent — strictly less information,
|
||||
and it has no place to put `ptbase2` other than where the table already puts it.
|
||||
- **Reverse declaration order is refuted by the same capture**: it would draw
|
||||
`ptbase2` (13) over `ptcopyright` (28), and the copyright line is visible.
|
||||
|
||||
**The `0x10`-adjacency first step was run, and it does not survive.** The
|
||||
background *is* adjacent to a `kind = 0x10` `.prm` element on both screens that
|
||||
have one — but on **opposite sides**. `GP_TITLE` build 7 is
|
||||
`12 pteff00.prm (0x10)`, `13 ptbase2.t32`, `14 pteff04.t32`;
|
||||
`GP_MISSION_SELECT` build 0 is `0 px_mission_base.tbm`, `1 px_mission_eff00.prm
|
||||
(0x10)`. So "the `.prm` opens a layer that draws beneath what precedes it" cannot
|
||||
place both, and no rule keyed on the `.prm`'s position orders the background.
|
||||
`GP_READY_ROOM` and `GP_OPTIONS` turned out not to be the third and fourth
|
||||
witnesses this entry hoped for: neither of their largest builds carries a `.prm`
|
||||
or a full-screen background at all, so they cannot discriminate.
|
||||
|
||||
**What is still open, stated plainly:** nothing in the bundle has been found that
|
||||
orders element 13 behind elements 0–5. Every ordering the file itself carries is
|
||||
now either identical to the declaration table or refuted by the capture. The next
|
||||
step is no longer static — it is either the guest code that walks this table, or a
|
||||
per-draw capture of the title screen showing the order the game submits.
|
||||
|
||||
|
||||
## Capital ships assemble wrong in the viewer
|
||||
|
||||
**Reported:** 2026-07-30, by the user. **Status:** ✅ **format-side cause found and
|
||||
|
||||
BIN
docs/re/captures/title-screen-composite-2026-08-18.png
Normal file
BIN
docs/re/captures/title-screen-composite-2026-08-18.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 621 KiB |
BIN
docs/re/captures/title-screen-oracle.png
Normal file
BIN
docs/re/captures/title-screen-oracle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 999 KiB |
@@ -58,8 +58,9 @@ out-of-range value anywhere in any bundle), and the tutorial bundle, which has n
|
||||
The table lists **both** sprites and `.rat` records — it is the screen's element list.
|
||||
`pgp_ttrl` declares 11: six `eff*`, `msg`, and four `.rat`s (`title`, `btn10..12`).
|
||||
|
||||
✅ **Verified:** for all 7 `.t32` entries the declared pivot is *exactly* half the decoded
|
||||
texture's dimensions — `eff10` 408×120 → 204,60; `eff21` 428×360 → 214,180; `msg` 381×38 →
|
||||
✅ **Verified — but see the disc-wide correction at the end of this document, which
|
||||
demotes this to a property of *this bundle*:** for all 7 `.t32` entries the declared
|
||||
pivot is *exactly* half the decoded texture's dimensions — `eff10` 408×120 → 204,60; `eff21` 428×360 → 214,180; `msg` 381×38 →
|
||||
190,19; and so on, 7/7 with no mismatch (`tools/re-capture/ratc_decls.py`).
|
||||
|
||||
`GP_PAUSE_MENU.pak`'s six bundles are `{in-mission, tutorial} × {English, Japanese}`, with
|
||||
@@ -191,7 +192,11 @@ recording them because a static-only reading would have shipped them:
|
||||
screen's draw list lives. **It is the bundle's own declaration table** (above): that
|
||||
table is not the child listing — the children are grouped by type (every `.t32`, then
|
||||
every `.rat`), while the declaration table names *elements*, in a plausible back-to-front
|
||||
order, and it is exactly the missing set that appears there. For the in-mission pause
|
||||
order, and it is exactly the missing set that appears there. **"Plausible" is
|
||||
as far as that goes** — a framebuffer capture of the title screen refutes it on
|
||||
that screen (its background is element 13 and it is drawn behind elements 0–5),
|
||||
and no other ordering in the bundle replaces it; see
|
||||
[`BACKLOG.md`](../BACKLOG.md). For the in-mission pause
|
||||
menu it lists 23: `eff11/12/10`, `eff02(+a)`, `eff03(+a)`, `eff01`, `title.rat`,
|
||||
`btn00/01/04/05.rat`, `msg`, `deli1…4`, `eff30…33`, `loop1.rat`. Note what it does
|
||||
**not** list: the focused button variants, which are reached through each base record's
|
||||
@@ -338,3 +343,82 @@ and that sprite exists **nowhere** — not as a pak member, not as a child of th
|
||||
bundles. It is the second such dangling asset found, after
|
||||
[`SUBTITLE_S12B.tbl`](../movie-subtitle-link.md); a reimplementation should skip
|
||||
a missing sprite rather than treat it as a decode failure.
|
||||
|
||||
## The pivot is the scale centre — measured against the framebuffer (2026-08-18)
|
||||
|
||||
**Status:** ✅ `CONFIRMED` that a keyframe's scale grows the element **about its
|
||||
declared pivot** rather than about the keyframe's own corner. 🟡 the pivot and
|
||||
the sprite centre are *not* told apart by this measurement — see the scope below.
|
||||
|
||||
The compositor had been reading a keyframe as `top-left = (X,Y)` and
|
||||
`size = decoded · scale`, ignoring the pivot entirely. That is right for every
|
||||
element at 100 %, which is why the pause menu and the ARSENAL rulers above never
|
||||
caught it, and wrong for every element that is scaled.
|
||||
|
||||
### The measurement
|
||||
|
||||
`GP_TITLE.pak` build 7, element 13:
|
||||
|
||||
```
|
||||
ptbase2.t32 decoded 640x360 pivot (320,180) one keyframe: (320,180) 200%,200%
|
||||
```
|
||||
|
||||
Composited from the corner that is a 1280×720 rect at 320..1600 × 180..900 — a
|
||||
quarter-screen slab hanging off the bottom-right, with the top-left quadrant
|
||||
bare. Anchored at the pivot it is `(320,180) − (320,180)·(2−1) = (0,0)`, i.e.
|
||||
exactly the screen.
|
||||
|
||||
The oracle is a framebuffer capture of Canary sitting on the title screen
|
||||
([`captures/title-screen-oracle.png`](../captures/title-screen-oracle.png), the
|
||||
game surface cropped out of the window at offset (1,45), 1:1, the bottom 45 rows
|
||||
clipped by the display):
|
||||
|
||||
- the background art reaches all four edges, so the corner reading is refuted
|
||||
outright;
|
||||
- normalised cross-correlation of the composite's background against the capture,
|
||||
searched over ±40 px in both axes, peaks at **(0,0)** — 0.90 on the planet limb,
|
||||
0.72 on the lower-left ship. Not "close": the correct shift is the argmax.
|
||||
|
||||
Calibration for that crop is `ptcopyright.t32`, which is unscaled: 694×20 placed
|
||||
at (293,655), and the capture's glyph run is x 295…986, y 700…718 — inside that
|
||||
rect to the pixel once the 45 px of window chrome is taken off. So at 100 % the
|
||||
keyframe really is the top-left, as this document already said.
|
||||
|
||||
Residual, stated rather than glossed: the composite is uniformly brighter than
|
||||
the capture (about +14 in the dark starfield) and less blue in the planet. That
|
||||
is **not** placement — it is the two full-screen `PRMD` elements (`pteff00.prm`,
|
||||
`pteff02.prm` at fade-alpha ≈ 204) and `pteff04.t32` that the compositor does not
|
||||
draw at all. Their geometry is known (a colour and four corners); their blend is
|
||||
not.
|
||||
|
||||
### Scope, and what this does *not* settle
|
||||
|
||||
Swept over every screen build on the disc (`tests/ui_paint_order_disc.rs`):
|
||||
|
||||
| | count | of 5 130 resting placements with a decoded sprite |
|
||||
|---|---|---|
|
||||
| scaled ≠ 100 % — i.e. moved by this fix | **865** | 17 % |
|
||||
| of those, where "about the pivot" and "about the sprite centre" differ by ≥2 px | **213** | 4 % |
|
||||
|
||||
`ptbase2`'s pivot **is** half its size, so the capture proves only that scaling is
|
||||
not about the keyframe corner. Pivot-anchoring is chosen because that is the field
|
||||
the format carries and this document already called it "a rotation/scale centre";
|
||||
the 213 placements that could tell the two apart are **unmeasured**, and a capture
|
||||
of any one of them mid-animation would settle it.
|
||||
|
||||
### Correction: "pivot = half the texture" does not generalise
|
||||
|
||||
The ✅ above — *"for all 7 `.t32` entries the declared pivot is exactly half the
|
||||
decoded texture's dimensions, 7/7 with no mismatch"* — is a property of the
|
||||
tutorial pause bundle, not of the disc. Across all 5 130 placements:
|
||||
|
||||
- **2 521 (49 %)** have `pivot·2` equal to the decoded size within 1 px;
|
||||
- **1 884 (37 %)** are off by more than 16 px.
|
||||
|
||||
Some of that is already explained here — the `.rat` layer is inherited from the
|
||||
Japanese master, so its baked pivot belongs to another language's sprite. But it
|
||||
also happens on `.t32` declarations (`pgpeff02.t32` in the in-mission pause build:
|
||||
decoded 265×198, declared pivot 19,18), so the rule is not merely a
|
||||
language-inheritance artefact. **Do not derive a texture size from a pivot**; the
|
||||
capture backs the decoded size, not the pivot — `ptcopyright` is 694 px wide in
|
||||
the framebuffer, and `pivot·2` says 618.
|
||||
|
||||
Reference in New Issue
Block a user