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>
7.9 KiB
.rat — the UI element layout / animation record
Status: ✅ CONFIRMED for placement (2026-07-28). The retail UI can be
reassembled from the disc: the tutorial PAUSE menu rebuilds pixel-accurately from its
sprites placed at the coordinates in their .rat records — no fitting, no manual nudging.
Left: the running game (Canary screenshot). Right: rebuilt from GP_PAUSE_MENU.pak alone.
The remaining differences are the animated frame/glow sprites (*eff*) that were not
placed, and the live 3D background.
Screen composition
The UI is one pak per screen — GP_TITLE, GP_PAUSE_MENU, GP_READY_ROOM,
GP_SAVE_LOAD, GP_MISSION_SELECT, GP_OPTIONS, GP_SYSTEM, GP_TUTORIAL,
GP_STAGE_CLEAR, GP_MOVIE_THEATER, GP_DEBRIEFING_PILOTLOG, GP_LEADERBOARD,
GP_MISSION_LOG, GP_BUNK, GP_DIALOG, GP_GAMEOVER, GP_CHALLENGE,
GP_HANGAR_ARSENAL.
Inside a screen pak, each top-level RATC bundle is one (context × language) build of that screen, and its children come in pairs:
| child | what it is |
|---|---|
<name>.t32 |
the sprite (T8aD) |
<name>.rat |
that sprite's layout record (this document) |
<screen>loop1.rat |
a screen-level record (larger; not yet decoded) |
GP_PAUSE_MENU.pak's six bundles are {in-mission, tutorial} × {English, Japanese}, with
the two in-mission builds each present twice at identical size. The purpose of that
duplicate is NEEDS-HUMAN (resolution or aspect variant?), and where the other four
shipped languages live is likewise unresolved — this pak holds only EN and JP.
Naming is transparent: pgp = pause screen, pgp_ttrl_ = its tutorial context, btnNN =
menu item, btnNNf = that item's focused sprite, eff* = frame/glow decoration,
deli* = the item divider, title, msg.
Record layout
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
0x08 u32 payload size
0x14 u32 entry count (loop1.rat: 3, matching its 3 sprite names)
0x18 u32 design width = 1280
0x1c u32 design height = 720
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 ──
u32 scale X = 100 (percent)
u32 scale Y = 100
u32 tint = 0xffffffff (RGBA, white = untinted)
u32 X ← top-left position
u32 Y ←
u32 time (keyframe records only)
...
"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 (
pgpbtn0086×42 → 43,21;pgpbtn05221×42 → 110,21;pgptitle202×73 → 101,36;pgpbtn04172×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 block repeated with a varying trailingtimefield — the PAUSE title's fly-in. optcarries a length-prefixed record name. Onpgpbtn00.ratit points atpgpbtn00f.rat, i.e. normal state → focused state. Focused records place their sprite 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.ratis 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
PRMDprimitives, 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 byend.
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
Positions were read out of the records and checked against a screenshot of the running game, twice, in that order — the records were never fitted to the picture.
- Differential. Across
pgpbtn00/01/04/05.rat, exactly one field varies and it steps268 → 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. - Absolute placement — the decisive test. The tutorial build's records give 546/288, 546/358, 546/428 and 540/119. Compositing its sprites at exactly those numbers, with no offset and no fitting, reproduces the screenshot (image above).
- Pivot. 4 of 5 plain sprites carry exactly half their texture's dimensions at 0x50/0x54 (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
Both were mistakes made during this analysis, caught by comparing against the real game — recording them because a static-only reading would have shipped them:
- 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 English label. The first language assignment made here was wrong; rendering the sprites 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
re-packed. In-mission is
RESUME / RADIO LOG / OPTIONS / BACK TO TITLE(4 items,pgpbtnNN); the tutorial isRESUME / OPTIONS / BACK TO MENU(3 items,pgp_ttrl_btn1N). Matching the 3-item screenshot against the 4-item set suggests a runtime slot-packing rule that does not exist.
Next
- The
eff*/deli*/msgplacements are still missing — those sprites have no.ratof their own, andloop1.ratturned 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(789 T8aD + 510 RATC) is the big one, and the ARSENALDATA SHEETpanel documented in weapon-datasheet-runtime.md is a ready-made oracle for it. - Tooling:
crates/sylpheed-formats/examples/ui_screen.rs(inventory a screen pak, carve a named RATC child),sylpheed-cli pak textures(decode every sprite).
