re: ui .rat — pivot field, language-independence, PRMD, and a tightened claim
Follow-up decoding on the layout record:
- 0x50/0x54 is the sprite PIVOT: exactly half the texture size on 4 of 5 plain
sprites. It is a rotation/scale centre, not a draw offset — X/Y remains the
top-left, which is what actually reproduces the screenshot.
- The records are byte-identical across the English and Japanese bundles: layout
is authored once and only the sprites are swapped. That explains pgpbtn01's
pivot implying a 226px texture that neither shipped language has.
- loop1.rat is NOT the screen composition — it is a looping sprite animation
(3-name table + ~30 keyframes at one position). The eff*/deli*/msg draw list is
therefore still unfound, and that gap is now stated plainly.
- The 380-byte top-level entries are PRMD primitives: a colour plus four explicit
corner coordinates for a full-screen quad — the pause dim overlay. The format
is tag-driven ('opt ', 'PRMD', 'end '), not a fixed struct.
Also demotes one piece of evidence. The screenshot is of the TUTORIAL pause menu,
so only pgp_ttrl_* can be checked against it; the in-mission records mapping onto
the same screenshot under a constant offset works only because both builds share
the 70px pitch and proves nothing. In-mission coordinates are now marked
unverified.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,31 +38,57 @@ menu item, `btnNNf` = that item's **focused** sprite, `eff*` = frame/glow decora
|
|||||||
|
|
||||||
## Record layout
|
## Record layout
|
||||||
|
|
||||||
Big-endian u32 throughout (Xbox 360). A minimal record (a static button) is 165 bytes:
|
Big-endian u32 throughout (Xbox 360), and **tag-driven**: 4-char ASCII tags (`opt `,
|
||||||
|
`PRMD`, `end `) mark sections, so a record is a stream of blocks rather than a fixed struct.
|
||||||
|
A minimal record (a static button) is 165 bytes:
|
||||||
|
|
||||||
```
|
```
|
||||||
0x00 "RATC" magic — a RATC bundle reused as a data record
|
0x00 "RATC" magic — a RATC bundle reused as a data record
|
||||||
0x08 u32 payload size
|
0x08 u32 payload size
|
||||||
|
0x14 u32 entry count (loop1.rat: 3, matching its 3 sprite names)
|
||||||
0x18 u32 design width = 1280
|
0x18 u32 design width = 1280
|
||||||
0x1c u32 design height = 720
|
0x1c u32 design height = 720
|
||||||
0x20 char[] the sprite this record places, e.g. "pgpbtn00.t32"
|
0x20 char[16] the sprite this record places, e.g. "pgpbtn00.t32"
|
||||||
|
0x50 u32 pivot X = texture width / 2
|
||||||
|
0x54 u32 pivot Y = texture height / 2
|
||||||
...
|
...
|
||||||
── placement block ──
|
── placement block ──
|
||||||
u32 scale X = 100 (percent)
|
u32 scale X = 100 (percent)
|
||||||
u32 scale Y = 100
|
u32 scale Y = 100
|
||||||
u32 tint = 0xffffffff (RGBA, white = untinted)
|
u32 tint = 0xffffffff (RGBA, white = untinted)
|
||||||
u32 X ← position
|
u32 X ← top-left position
|
||||||
u32 Y ←
|
u32 Y ←
|
||||||
u32 time (keyframe records only)
|
u32 time (keyframe records only)
|
||||||
...
|
...
|
||||||
"opt " u32 len char[len] link to another record, e.g. "pgpbtn00f.rat"
|
"opt " u32 len char[len] link to another record, e.g. "pgpbtn00f.rat"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **X/Y is the sprite's top-left**, not its centre: compositing at these coordinates
|
||||||
|
reproduces the screenshot, which drawing centred on them would not.
|
||||||
|
- **The pivot at 0x50/0x54 is half the texture size** — 4 of the 5 plain sprites match
|
||||||
|
exactly (`pgpbtn00` 86×42 → 43,21; `pgpbtn05` 221×42 → 110,21; `pgptitle` 202×73 →
|
||||||
|
101,36; `pgpbtn04` 172×43 → 86,22). It is a rotation/scale centre, not a draw offset.
|
||||||
- **Animated elements are a keyframe list.** `pgptitle.rat` (752 B) is the same placement
|
- **Animated elements are a keyframe list.** `pgptitle.rat` (752 B) is the same placement
|
||||||
block repeated with a varying trailing `time` field — the PAUSE title's fly-in.
|
block repeated with a varying trailing `time` field — the PAUSE title's fly-in.
|
||||||
- **`opt `** carries a length-prefixed record name. On `pgpbtn00.rat` it points at
|
- **`opt `** carries a length-prefixed record name. On `pgpbtn00.rat` it points at
|
||||||
`pgpbtn00f.rat`, i.e. *normal state → focused state*.
|
`pgpbtn00f.rat`, i.e. *normal state → focused state*. Focused records place their sprite
|
||||||
- Two fields (at 0x50/0x54 in the button records) vary per button and are **not identified**.
|
42 px left and 8 px up of the base, because the focused art includes the selection ring
|
||||||
|
that hangs off the left edge; their pivot is a constant (21,25) rather than half-size.
|
||||||
|
- `<screen>loop1.rat` is **not** a screen composition — it is a looping sprite animation:
|
||||||
|
a 3-name table (`pgpeff34/35/36.t32`) plus ~30 keyframes all at one position.
|
||||||
|
- The small (380 B) top-level entries are **`PRMD` primitives**, not sprites: a colour and
|
||||||
|
four explicit corner coordinates `(0,0) (1280,0) (0,720) (1280,720)` — the full-screen
|
||||||
|
quad that dims the scene behind the pause menu — terminated by `end `.
|
||||||
|
|
||||||
|
### The records are language-independent
|
||||||
|
|
||||||
|
`pgpbtn00.rat` is **byte-identical** in the English and Japanese bundles. The layout is
|
||||||
|
authored once and only the `.t32` sprites are swapped, which has two consequences:
|
||||||
|
|
||||||
|
- The baked pivot belongs to *whichever build the record was authored from*, not to the
|
||||||
|
sprite actually shipped beside it. That is why `pgpbtn01`'s pivot (113 → a 226 px wide
|
||||||
|
texture) matches neither the English sprite (207) nor the Japanese one (148).
|
||||||
|
- **Do not infer anything about a language from a texture size** — see the traps below.
|
||||||
|
|
||||||
## Evidence
|
## Evidence
|
||||||
|
|
||||||
@@ -72,12 +98,18 @@ game, twice, in that order — the records were never fitted to the picture.
|
|||||||
1. **Differential.** Across `pgpbtn00/01/04/05.rat`, exactly one field varies and it steps
|
1. **Differential.** Across `pgpbtn00/01/04/05.rat`, exactly one field varies and it steps
|
||||||
`268 → 338 → 408 → 478` — a constant 70 px pitch — while the field before it is 226 in
|
`268 → 338 → 408 → 478` — a constant 70 px pitch — while the field before it is 226 in
|
||||||
all four. A vertical menu: constant X, evenly spaced Y.
|
all four. A vertical menu: constant X, evenly spaced Y.
|
||||||
2. **Against the game.** The three visible items measured at screenshot y-centres
|
2. **Absolute placement — the decisive test.** The *tutorial* build's records give
|
||||||
346.0 / 415.5 / 485.5 — spacing 70.0 and 70.0, and a *single* constant offset maps all
|
546/288, 546/358, 546/428 and 540/119. Compositing its sprites at exactly those numbers,
|
||||||
three onto 268 / 338 / 408 to within 0.5 px.
|
with no offset and no fitting, reproduces the screenshot (image above).
|
||||||
3. **Absolute placement.** The tutorial build's records give 546/288, 546/358, 546/428 and
|
3. **Pivot.** 4 of 5 plain sprites carry exactly half their texture's dimensions at
|
||||||
540/119, which are framebuffer coordinates directly: compositing the sprites there
|
0x50/0x54 (above).
|
||||||
reproduces the screenshot (image above).
|
|
||||||
|
> A caution on step 2, because the first pass here got it subtly wrong: the screenshot is
|
||||||
|
> of the **tutorial** pause menu, so only the `pgp_ttrl_*` records can be checked against
|
||||||
|
> it. The in-mission records (X = 226) also map onto the same screenshot under a single
|
||||||
|
> constant offset — but that only works because both builds share the 70 px pitch, and it
|
||||||
|
> proves nothing. The in-mission coordinates remain **unverified**: confirming them needs a
|
||||||
|
> screenshot of a pause during an actual mission.
|
||||||
|
|
||||||
## Two traps this caught
|
## Two traps this caught
|
||||||
|
|
||||||
@@ -87,7 +119,8 @@ recording them because a static-only reading would have shipped them:
|
|||||||
- **Texture width does not identify a language.** English `RESUME` (166 px) and Japanese
|
- **Texture width does not identify a language.** English `RESUME` (166 px) and Japanese
|
||||||
`再開` (86 px) differ hugely, but Japanese `通信ログ` (148 px) is within a few px of an
|
`再開` (86 px) differ hugely, but Japanese `通信ログ` (148 px) is within a few px of an
|
||||||
English label. The first language assignment made here was wrong; rendering the sprites
|
English label. The first language assignment made here was wrong; rendering the sprites
|
||||||
is the only reliable check.
|
is the only reliable check. (The records being language-independent makes this worse:
|
||||||
|
a record's baked pivot implies a texture width that matches *no* shipped sprite.)
|
||||||
- **The in-mission and tutorial pause menus are different sprite sets, not one set
|
- **The in-mission and tutorial pause menus are different sprite sets, not one set
|
||||||
re-packed.** In-mission is `RESUME / RADIO LOG / OPTIONS / BACK TO TITLE` (4 items,
|
re-packed.** In-mission is `RESUME / RADIO LOG / OPTIONS / BACK TO TITLE` (4 items,
|
||||||
`pgpbtnNN`); the tutorial is `RESUME / OPTIONS / BACK TO MENU` (3 items,
|
`pgpbtnNN`); the tutorial is `RESUME / OPTIONS / BACK TO MENU` (3 items,
|
||||||
@@ -96,8 +129,10 @@ recording them because a static-only reading would have shipped them:
|
|||||||
|
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
- `<screen>loop1.rat` (1320 B) is the screen-level record — decoding it should give the
|
- **The `eff*` / `deli*` / `msg` placements are still missing** — those sprites have no
|
||||||
draw order and the `eff*`/`deli*` placements that are missing from the rebuild above.
|
`.rat` of their own, and `loop1.rat` turned out to be an animation, not a composition.
|
||||||
|
So the screen's draw list lives somewhere not yet found (the parent RATC's own header
|
||||||
|
region, or title code). That is the gap between the rebuild above and a complete screen.
|
||||||
- The same method should now unroll the other screens directly; `GP_HANGAR_ARSENAL.pak`
|
- The same method should now unroll the other screens directly; `GP_HANGAR_ARSENAL.pak`
|
||||||
(789 T8aD + 510 RATC) is the big one, and the ARSENAL `DATA SHEET` panel documented in
|
(789 T8aD + 510 RATC) is the big one, and the ARSENAL `DATA SHEET` panel documented in
|
||||||
[weapon-datasheet-runtime.md](../weapon-datasheet-runtime.md) is a ready-made oracle for it.
|
[weapon-datasheet-runtime.md](../weapon-datasheet-runtime.md) is a ready-made oracle for it.
|
||||||
|
|||||||
Reference in New Issue
Block a user