Field Menu - Windows + Status Panel
How the pause menu is built - the screen you open in the field to check Status, change Equipment, or flip Options. It is data-driven: a 52-entry window-descriptor table on the disc places every screen's bordered windows and names each one's content-renderer, and the renderers draw window contents only - the gold 9-slice frame is emitted by the caller. This page pins the table, every screen's layout, and the from-scratch port that redraws it all.
At a glance
- Window table
- 52 records at menu-overlay VA
0x801E4738(PROT 0899 file0x15F20); parserlegaia_asset::menu_windows - Record
- 0x10 bytes: content id, park edge, window class (2 tab / 3 standard / 4 list), content rect
x,y,w,h, renderer VA - Choreography
- Windows slide in / out via the window-widget VM; a closed window parks offscreen at its park edge
- Data read
- The live
0x414-byte character records at0x80084708- the same block the save file copies - Engine port
engine-uidraw builders (wgpu-free, shared by native window and browser) overengine-core::pause_screens- Confidence
- Confirmed - table byte-matched disc-vs-RAM; layouts pinned by GPU-prim scans of menu captures (how we know)
| Question | Short answer | Section |
|---|---|---|
| Where do the windows come from? | One table row per window: rect + content renderer. | Window table |
| How does a screen get picked? | A submenu index through the same sub-screen pointer table the save screen uses. | Dispatch |
| How does "Best Equipment" choose? | A bag scan with two surprising ranking laws. | Equip |
| Where are the options stored? | Straight into the live config words the audio and pad code read. | Options |
Window descriptor table
Press the menu button and a set of bordered gold windows slides in from the screen edges. None is hand-placed in code: each screen is a list of window ids, each id a row in the table giving the window's home rectangle and content renderer. The renderers only ever draw inside; the frame - which extends 8 px past the content rect on every side - is drawn by whoever opened the window.
| Screen | Windows (draw order) |
|---|---|
| Top-level pause menu | 50 command list (24,24,104,94); 49 money / play-time box (24,178,104,24); 51 right party panel (144,24,152,180) |
| Status | tab 3; 26 party list (14,38,60,38); 27 "Condition" pager (14,92,60,10); 30 summary (14,134,60,70); 28 main panel (90,16,218,188) |
| Equip | tab 2; 21 party (14,42,80,38); 23 item list (174,22,132,182) (renderer-less); 22 main (14,96,292,108) |
| Options | tab 4; 48 settings (24,40,256,148); 47 value popup (170, *, 128, *) - y/h stamped per open |
Live windows are doubly-linked structs carrying the descriptor id and an animated copy of the rect: on screen exit a window slides to its park edge and sits offscreen. The top-level windows stay parked in every sub-screen capture - which is how the top-level set was pinned without a top-level capture.
Equip screen and "Best Equipment"
Four windows; the main window draws "Best Equipment" plus seven slot rows (pictogram, equipped name), a stat-compare block when previewing, and the item's description panel when a slot row is focused. The interesting part is the candidate scan behind "Best Equipment": it seeds with what the character wears, walks the bag keeping one winner per armament slot, and obeys two ranking laws the screen never reveals:
- Armour ranks on UDF + LDF only. The INT and SPD bonuses are never read - a pure-INT head accessory or a pure-SPD boot can never displace an incumbent.
- A weapon's category check dominates its attack. The score is
attack + (favored ? 1000 : 0), and an attack byte cannot reach 1000 - so a category-favoured weapon outranks every unfavoured one regardless of raw attack. - An empty slot is filled by the first eligible entry; ties keep the incumbent. The whole scan is a trial equip the routine undoes before returning.
An item competes only when its record marks it equipment and its equip-character mask matches the character; the slot it competes for comes from the equipment record's slot bits, permuted weapon-first. Engine port: equip_session::best_equipment_candidates.
Options screen
The settings rows are data-driven too: a display-layout table joined to 8-byte row descriptors, each naming the live config word the row edits. Confirm opens a value popup positioned under the row; a choice commits directly into the config word - edits are already live, backing out reverts nothing.
| Row | Choices | Config word |
|---|---|---|
| Battle Camera | Close / Normal / Far | 0x800846C0 |
| Battle Select Attack | Select / Automatic / Command | 0x800846C4 |
| Battle Command | Directional Buttons / button glyph | 0x800846C8 |
| Field Move | Walk / Run | 0x800846CC |
| Field HP Display | Immediate / Gradual / Display Off | 0x800845C4 |
| Sound | Stereo / Monaural | 0x800846BC |
| Dual Shock: Battles / Events / Encounters | Vibration On / Off | 0x800845C8 / 0x800845A8 / 0x800845CC |
A hidden "Battle Voices" row (0x800845AC) exists in the descriptor list but not in the layout table. Engine port: engine-core::options + engine-ui::options_draws_for; the Sound row drives the mixer's monaural downmix.
Deep dive: popup geometry and the debug flag editor
Popup (window 47): x/w static (170,128); y = settings-window y + 0x16 + Σ advances above the cursor row, h = choices × 13 - 4, flipped up when the bottom would pass 0xB0. Rows advance 14 px, 20 px across the two group separators; labels ink 7 white (ink 5 teal on the indented Dual Shock sub-rows), values ink 6 gold at x+140. Committing Events to Off also zeroes the live rumble state.
The retail debug build's developer menu (overlay 0897) carries a raw EVENT FLAG index/value poke tool - not part of the retail pause menu. Two kernels are ported for completeness: the value step (Up/Down ±0x8, ±0x80 with Triangle, Left/Right ±1, clamped [0, 0xFFF]) and the wrapping list cursor. Engine: engine-core::dev_menu.
List pages: one kernel
Every class-4 list page - the Items and Magic lists, the Equip candidates, shop rows - is paged by one executable-resident kernel. What a player feels as "fixed 12-row pages" is its navigation law:
- Up / Down move the selection within the page, wrapping at the edges - they never scroll.
- Left / Right are the only scroll, one whole page at a time.
- Confirming a disabled (grey) row buzzes; a list parked behind the command window draws every row white.
The menu overlay builds each row as a packed u16 - class nibble, disabled bit, alt-ink bit, 12-bit payload - and the kernel only reads them; a small global protocol carries mode, selection and scroll between the two. The per-row draw switches on the class nibble: bag rows with counts, equip candidates with slot pictograms, shop rows with prices, plain names. Engine port: engine-core::pause_screens::list_kernel_navigate + engine-ui::pause_lists.
Items screen: Use / Throw Out / Arrange
- Use dispatches on the item's effect class from the item-effect table: three class items get their own routes - Door of Light (confirm, defaulting Yes; consumes one and exits the menu with the dungeon-escape code), Door of Wind (a destination list over the 6-byte quick-travel records; exits with the world-map warp), Incense (confirm, applies encounter suppression, stays on the screen). Everything else opens the party target panel - all-party when the effect's flag says so, single-target otherwise - and applies through the executable's item-effect applier, consuming one bag copy per use.
- Throw Out re-points the list and confirms (Yes/No seeded to No); Yes zeroes both bytes of the bag-slot pair - the whole stack is discarded, with no compaction.
- Arrange inverts the overlay's display-order table into an id → rank map and selection-sorts the bag pairs; emptied slots sink behind the occupied run.
The target panel (window 14) draws one block per member; for the permanent-stat Waters it previews the changed maxima or stat rows rather than plain cur / max pairs. Engine: PauseItemsSession, SpecialUseSession, target_panel_model + their engine-ui draw builders.
History: the mode-1 target panel
An earlier reading called the target panel's mode 1 an "HP-restore preview". Restore items use the plain mode-0 panel; the non-zero modes belong to the permanent-stat Waters (Life / Magic / Power / Swift / Wisdom / Guardian).
Record fields consumed
Into the 0x414-stride live record: experience +0x0, next-level threshold +0x4, HP +0x104/+0x106/+0x11C, MP +0x108/+0x10A/+0x11E, persistent AP +0x10E, growth stats +0x122..+0x12C, displayed level +0x130, spells +0x13C/+0x13D/+0x161, equipped ids +0x196.., name +0x2A7. External tables: item names 0x8007436C, spell names 0x800754D0, equipment stats 0x80074F68, accessory passives 0x8007625C, arts 0x80075EC4.
Engine port
Every draw builder lives in legaia-engine-ui - the renderer-agnostic, wgpu-free leaf - which is what lets the browser play page build these same menus without linking wgpu (engine-render re-exports the crate). The engine parses the window table off the user's disc at boot, frames each screen with the reusable 9-slice chrome, and renders:
- The status main panel and satellites at the byte-pinned offsets, icon sprites ported verbatim from the UI-icon table and pixel-verified against golden menu captures - LV / HP / MP labels, the AP gauge with its gouraud meter fill, the equipment-slot pictograms, tab plaques, element badges.
- The top-level menu (command rows, money box, party panel) and the Equip screen's four-window set at the traced offsets, HP / MP numbers through the ported health-tier inks (red at 0, orange ≤ max/4, gold ≤ max/2, white above).
- The Items and Magic screens with real bag counts and the executable's text tables - 12-row pages, PAGE header, page-turn arrows, description windows - on both hosts; the Use flow's target panel with the Water previews; Throw Out / Arrange through their traced state machines; the Door of Light and Incense confirm windows.
- Engine-styled rather than pinned: the Equip item-picker rows. Unrouted: Door of Wind's destination list.
Deep dive: shared draw primitives and CLUT staging
Three primitives render everything: the proportional string (FUN_80036888, MES control tokens 0x7C/0xCE/0xCF), the UI-icon sprite (FUN_8002C488, UV/CLUT from the 12-byte-stride table at 0x800732A4), and decimal digits (FUN_80034B78). The palette-staging global DAT_8007B454 is read only by the string primitive - icons and numbers carry their own CLUT, so a write before an icon draw is really staging the next string's palette. Staged index N selects the 16-colour CLUT at VRAM (16×(6+N), 510); main ink is entry 15 - 7 white (206,206,206), 5 teal (66,222,222), 6 gold (231,173,0), 9 orange, 2 red.
How we know
| Function / data | Address | What it proves |
|---|---|---|
| Window table | 0x801E4738 (PROT 0899 0x15F20) | 52 records, byte-matched disc-vs-resident across six catalogued menu-open save states; only two fields differ at runtime. |
| Status panel renderer | FUN_801D33D8 | Every position an offset from the caller-supplied window origin; the submenu fold. |
| Satellites | FUN_801D2094 / FUN_801D30A4 / FUN_801D31EC | Party list, pager, summary layouts and sprites. |
| Top-level windows | FUN_801CFD68 / FUN_801D0148 / FUN_801D030C | Command rows, money / clock box, party panel. |
| Equip screen | FUN_801D21C0 / FUN_801CF88C | Slot rows, stat compare; the Best Equipment scan and both ranking laws. |
| Options | FUN_801DCEF0 / FUN_801D2910 / FUN_801DA9F8 | Layout + row-descriptor tables at 0x801E4404 / 0x801E44B8; live config-word commits. |
| List kernel | FUN_80032A44 | Page-local navigation, packed row entries, the parked-white rule. |
| Items flows | FUN_801D7C00 / FUN_801D7E50 / FUN_801D8734 / FUN_801D64A8 | Use routing, Throw Out stack discard, Arrange sort. |
| Pixel pins | GPU-prim scans | Frame = content − 8 px; plaque tiles; cursor sprites; AP-gauge quads - from the menu_status_town capture family. |
The full doc also pins the live window structs, every submenu's state machine, the Items / Magic screens in full, the prize-exchange screen, the equip stat-compare panels and the overlay-identity notes: docs/subsystems/field-menu.md. There is no inn screen in this overlay - an inn stay is field-VM script, see inn.