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_game_tables::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 - In the port
engine-uidraw builders (wgpu-free, shared by native window and browser) overengine-world-views::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 |
The screen tree
Press Start in the field and the root command list slides in beside a money / play-time box and a party panel. Its seven rows are the whole menu; each opens one screen, and every screen is assembled from the same window table described below.
The menu opens in any scene the field walk controller runs - which includes the three kingdom overworlds, ordinary field scenes as far as the game is concerned - but not while a dialogue is up. While it is open the game is in the CARD mode, whose handler runs no frame driver, so nothing in the world moves (game modes).
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-menus::options + engine-ui::options_draws_for; the Sound row drives libsnd's per-voice monaural fold (movie and voice audio stay stereo, as on the original); the full downmix is the separate Mono output setting.
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-dev-menu::dev_menu_flags.
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-menus::pause_screens::list_kernel_navigate + engine-ui::menu::screens.
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.
In the port
The engine parses the window table off the player's disc at boot and frames each screen with the same 9-slice chrome. The menu's two-level stack lives on the session (engine-menus::field_menu, pause_screens), so both hosts walk identical screens; every draw builder lives in engine-ui, the wgpu-free leaf, which is what lets the browser play page draw the same menus without linking wgpu. Panels are drawn at the byte-pinned offsets and checked against menu captures pixel by pixel.
Opening and closing are one rule too. A Start press goes through the same engine call on the native window, the browser page and a headless run, so a press that retail refuses (during a conversation, say) is refused everywhere. Retail does not cut straight to the menu: the field fades toward black for a few frames and the first window appears only once the fade is deep enough, and on the way out the fade lifts with the field running again. Both hosts keep drawing the field until the menu exists, send it no input before then, and draw the fade as the same subtractive quad the shop's opening uses.
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.