Files
Syplheed-Reborn/docs/re/structures/ui-rat-layout.md
Claude (auto-RE) e3f3d55d29 re: ui layout generalizes — the title main menu rebuilds too
Second screen, independent of the pause work: GP_TITLE.pak's ptbtn01..05.rat give
X=542 for all five items and Y=162/242/322/402/482, an 80px pitch where the pause
menu used 70. Measured against the main-menu screenshot the sprite tops sit at a
constant +46px for all five, which is exactly the 45px of Xenia window chrome plus
one — so the record's Y is the sprite's top edge in the guest framebuffer, to the
pixel, on a screen the format was not derived from.

GP_TITLE also splits by sub-screen the way the pause pak splits by context:
ptbtn00 (PRESS A BUTTON), ptbtn01..05 (main menu), ptbtn11..13 (EXTRAS submenu),
pgloading_* (loading screen).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 19:44:29 +00:00

10 KiB
Raw Blame History

.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.

real vs rebuilt

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 screenGP_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. Its own header is the screen's element declaration table, and the elements themselves follow as children:

child what it is
<name>.t32 the sprite (T8aD)
<name>.rat that sprite's layout record (this document)
<screen>loop1.rat a looping sprite animation (see below)

The bundle header — element declaration table

0x14  u32   entry count
0x20  entry[count], 60 bytes each:
        +0   char[28]  element name, NUL-padded ("pgp_ttrl_eff10.t32", "pgp_ttrl_btn10.rat")
        +28  u32 ×4    flags (0xffffffff / 0xffffffff / 0 / 0xffffffff on every entry seen)
        +48  u32       pivot X
        +52  u32       pivot Y
        +56  u32       0

The table lists both sprites and .rat records — it is the screen's element list. pgp_ttrl declares 11: six eff*, msg, and four .rats (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 → 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 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 (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 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 pgpbtn00f.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.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).
  • The split is visible inside a single bundle: in the English tutorial build, every .t32 declaration carries the correct English pivot (7/7), while the .rat declarations carry Japanese-derived ones (btn10 → 43,21 = 86/2, the Japanese sprite). So the .t32 table is regenerated per language and the .rat layer is inherited from the Japanese master.
  • 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.

  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 all four. A vertical menu: constant X, evenly spaced Y.
  2. 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).
  3. 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.

It generalizes — the title screen

The same method run against GP_TITLE.pak reproduces the main menu, which is a different screen with a different item count and a different pitch:

main menu real vs rebuilt

ptbtn01..05.rat give X = 542 for all five and Y = 162 / 242 / 322 / 402 / 482 — an 80 px pitch, where the pause menu used 70. Measured against the screenshot, the sprite tops land at a constant +46 px for all five (one reads 45, a 1-px edge-detection wobble), and 46 is exactly the 45 px of Xenia window chrome plus one. So the record's Y is the sprite's top edge in the guest framebuffer, to the pixel, on a second screen.

GP_TITLE.pak also splits by sub-screen the way the pause pak splits by context: ptbtn00 alone (the PRESS Ⓐ BUTTON prompt), ptbtn01..05 (main menu), ptbtn11..13 (the EXTRAS submenu), plus pgloading_* for the loading screen.

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 is RESUME / 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* / msg placements are still missing — those sprites have no .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 (789 T8aD + 510 RATC) is the big one, and the ARSENAL DATA SHEET panel 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).