At a glance

Placement table
SCUS_942.54 0x80076C10, 24-byte records, 103 initialised; parser legaia_ui_formats::screen_elements
Widget-class table
SCUS_942.54 0x800732A4, 12-byte records, 0x9D of them; parser legaia_ui_formats::ui_widgets
Sub-draw script
Fifty steps in the battle overlay's rodata (PTR_DAT_801F4D34): which records are up in which phase
Element raiser
FUN_801D8DE8(record, mode) (PROT 0898)
Text actors
Spawner FUN_8003541C; retained handle list ctx[+0x1074], forty handles
Art
The resident system-UI sheet at VRAM page (896, 256), palette row 511; text from the menu-glyph atlas at (896, 0), row 510
Port
Model engine-battle-hud, draw builder engine-ui::battle_hud; both hosts draw one shared surface

Screen layout

display window 320 × 228 (drawn at 2.5×) message banner (8, 4) 296 × 28 shares its seat with the actor-name plaque at (8, 8) Item Attack magic Spirit D-pad glyph, centre (228, 70) Begin Run round prompt, centre (160, 92) intro enemy-name labels, pen y = 48 move name, y = 150, centred on x = 160 target-select plaque, y = 162, centred x = 232 roster panelx = 7, y = 164, 102 × 48 roster panelx = 109 roster panelx = 211 active-actor bar (8, 188) 304 × 20 replaces the roster panels during an action; combo cluster at (168, 170), AP plate at (208, 172)
Seats are plate origins. The plaque and the message banner share one seat, and the roster panels and the active-actor bar are mutually exclusive (the bar is drawn below the frame here only to keep both visible). Elements park just below the visible window, at rows 230 to 236.

How an element reaches the screen

Retail’s battle HUD is a list of retained text actors - ctx[+0x1074], forty handles - that the two battle state machines rebuild at every transition. Three pieces of disc data decide everything the list holds and how it looks.

Menu SM edgeFUN_801D0748, ctx+0x06 Sub-draw script step(record, mode) pairs Action SMseed opens, 0x51 closes Raise FUN_801D8DE8spawn at one seat Placement record0x80076C10 + id * 24 Glide to other seat16 vsyncs, linear Text actor, per framedraw dispatcher Widget-class recordsprites, CLUT, seat bias reads frame style
A step flagged to reset first clears the handle list, so after it the live elements are exactly the pairs it lists. The placement record supplies the seats, content box, frame style and string; the frame style indexes the widget-class table.
  • Mode bits. Bit 0 picks the spawn seat (A for 0, B for 1) and the glide runs to the other; bit 1 suppresses the glide. Which seat is on screen is per record, so mode 0 means appear for the bar and unfold for a chip.
  • The glide counts vsyncs. Its elapsed counter grows by the frame step each battle pass, so the sixteen-frame raise lasts sixteen vsyncs at any cadence. The port steps every tracked glide by one a tick.

Who holds the screen in each phase

The menu state machine runs one sub-draw step per state edge. The steps decide which of the four HUD surfaces is up - and because they are disc data, the table below is read straight off them and agrees, element for element, with the handle lists of the catalogued save states.

TransitionRoster panelsActive-actor barBreadcrumb tab + plaqueAP plate
round prompt opensup---
command ring opensparkupboth slide inslides in
item / magic windowback upparksnapleaves
item / magic target stepparkup, pointed membersnap-
attack-mode prompt (Auto / Command)-snapsnapsnap
target cursor cancelled back to the prompt-upsnapslides in
attack target cursor-parksnapleaves
arts entry-park (the AP bar takes the seat)snapsnap
all members committed--tab onlyleaves

The roster panels belong to the round prompt and the browsed windows; the full-width bar to the ring, the attack-mode prompt and the target steps; the AP plate to the ring, the attack-mode prompt and the arts entry. The attack-mode prompt is the ring with its arms swapped: its step opens the Auto and Command chips on the left and right arm seats, sends the other three arms off and keeps everything else where it stands.

Parked is below the screen, not gone. When the bar takes over, retail moves the whole panel cluster to y = 230, just under the 228-line display window. The port’s stage is 240 lines tall, so it simply emits no panel draws while retail parks them. Inside an action the panels come back for a party-wide target, and a cast module can raise them itself - Orb’s heal glides them up so its HP readout counts up in them.

The placement record

Each record is one element's pair of seats plus a shared content box. The two seats are the element's from and to: which one is the parked seat is per record, so some elements slide in from the right and others from the left or the top.

Mode bit 0 picks the spawn seat (A for 0, B for 1) and the glide runs to the other; bit 1 suppresses the glide.

  • The pen is (x, y - 2). Both seat arms subtract two from the seat's y before spawning the text actor.
  • +0x14 is the string being drawn. Name banners measure their string, write the width into +0x06 and centre both seats on it - which is how the move name stays centred on the screen whatever its length.
  • The plate is not stored anywhere. It is derived from the content box; one record derives every plate.

Three movers

Three small routines copy geometry and content between records - never the id, the box height or the frame - which is how one row can borrow another's content:

RoutineRole
FUN_801D5718lands an element: both seats become the source's seat B, so nothing is left to glide
FUN_801D57E8adopts a source's whole geometry into a row that keeps its identity
FUN_801D5778launches it: seat B becomes the source's seat B minus 0x140 - one display width, so it glides a screen to the left

The commit log uses all three: each committed member's name, command and target are landed into a row of three records, and earlier rows are launched off as the party commits.

The widget-class table

Every sprite the chrome draws comes out of one array. A placement record's frame-style bytes are indices into it: style 0x01 is the blue plate body, style 0x02 the carved-gold one. The join holds for all 103 placement records.

  • A widget is a run of records. The chain delta steps to the next record of the same widget; zero ends the run. A roster panel is such a chain: plate, labels and separators.
  • The palette byte is a packed CLUT address, with bit 7 marking semi-transparency.
  • Two readers, one difference. FUN_8002C488 draws exactly one sprite at the caller's position and never applies the bias; FUN_8002C69C draws a sized widget, applies the bias, then follows the chain. The bias converts the glyph pen to the frame origin - the plate run's (-8, -4) takes the plaque's pen (16, 12) to a plate at (8, 8).

The table's end is structural: it runs exactly up to the frame tile-set pool the class arms read next.

Chrome facts worth knowing

  • One sheet, one three-slice, two palettes. The name plaque, the party bar and every command chip are the same three tiles (left cap, body, right cap) at two sheet rows: blue for the bar and chips, carved gold for the plaque. The gold row is the same art as the pause menu's title tab.
  • Numbers are cells, names are glyphs. Every number is a run of fixed 8×12 cells; only names use the proportional dialog font. Both halves of a current / maximum pair are right-aligned.
  • Status and element badges are widget records too: 48×16 word cells for the status element, 20×12 cells for the element badge, which is selected by markup in the name string rather than a placement record.
  • Four badge ids are not on the sheet at all - they resolve to the save-slot portraits.
  • No gauge anywhere. Neither the roster panels nor the active-actor bar draws an HP or MP meter, for the party or for monsters: a label sprite, numerals and a separator, nothing else. Retail draws no monster readout at all, so the acting monster’s name on the plaque is everything an enemy contributes to the HUD.
  • The plaques slide. An action’s name plaque and its target plaque are raised by the action seed in mode 0, so they glide in over sixteen frames - the plaque from above the screen, the target plaque from the right. A frame saved on the step the seed ran shows neither.
  • The breadcrumb trail. From the command ring on, the round prompt’s Begin chip glides to the top-left as a gold tab, the plaque rests beside it, and choosing a ring arm glides that arm’s chip on as a third tab - Attack, the member’s Ra-Seru name for magic, or Item.
  • The ring’s magic arm is a name. Its chip reads the member’s Ra-Seru when the party battle-actor init found Ra-Seru equipment on the record, and a lone - otherwise. An unavailable command still gets its chip; a forbidden one wears a red cross-out mark instead.
  • The selection cue is the plate, not a tint. Every chip draws at neutral grey. Only under the Battle Command option’s cross-button setting does the highlighted arm switch frame style, to the carved-gold run; on the default directional setting all chips stay blue.

Message and intro banners

  • The message banner is a class-0 window at (8, 4), 296 wide and 28 tall, with a fixed 280-pixel content box - it is not measured from the message. The frame is filled: under the border sprites the frame emitter FUN_8002BDC4 tiles a blue-marbled 32×32 patch in columns, with a top-to-bottom grey ramp. It shares its seat with the actor-name plaque; the two are alternatives, not layers.
  • The bottom message bar (record 0x5B) carries the steal captions and the petrify result line on the active-actor bar’s seat. It has the bar’s content box but the banner’s frame style, so it is a framed window rather than a plate run.
  • The intro enemy-name labels are not placement records. The battle-open composer FUN_801D9D3C lays them out itself with immediate geometry: one label per run of identical monsters (the second member turns the name into name * 2, each further one bumps the digit), centred over the group’s average battle X, then relaxed apart pair by pair and clamped to the screen until a pass moves nothing. They spawn at their final seat - no slide - and retail sweeps them on the pass the intro timer expires, so no action ever runs under the names. The port does not hold the round prompt back for that span, and sweeps the labels when it hands a round to the action SM early.

The item and magic windows

State 0x3C opens a list window on the right and a description window below-left, both drawn from the system-UI window skin, with a pointing-hand cursor on a 14-pixel row pitch, eight rows a page and a PAGE n/m header. The breadcrumb trail replaces the actor plaque while it is up.

  • A battle row is enabled on one bit. The shared list builder branches on the menu context: in the field, a usable item is enabled only if some member would benefit; in battle, the descriptor’s battle-usable bit alone decides. A Healing Leaf with the party at full HP is pickable in a fight and greyed in the pause menu.
  • Target confirm is the bar. Picking an item closes both windows and the target strip at the foot of the screen appears. It is not a widget of its own: it is the active-actor bar, re-opened by the target step and re-pointed per cursor move.
  • The magic window (0x46) is the same frame with spell content: a leading element icon per row, a two-line description (name, then scope), and two gold tabs top right for the highlighted spell’s level and MP cost.

Both are composed by engine-ui::battle_item_ui through the shared menu-window chrome and drawn by both hosts. The port does not yet draw the element and Ra-Seru icon cells, the world-anchored target name tag or the magic target step’s All plate.

The Arts announcement banner

NEW ARTS!!, HYPER ARTS!!, MIRACLE ARTS!! and SUPER ARTS!! are one banner: two textured quads from the value-readout glyph sheet - a position-selected first word and the shared ARTS!! - whose halves march toward a seam as a slide clock runs. Each live frame draws it four times at four points of its own travel, at 5 / 10 / 20 / 50 percent brightness, so the word arrives trailing ghosts that switch off as it lands.

rsquo;s build loop leaves its mark in the side array
Stage byte ctx[+0x28B]BannerRaised when
1NEWthe queue builderthe queue builder marks a newly learned art
2HYPERthe default: the byte is still zero after the other two writes
3MIRACLEthe queue builder raised the seat’s Miracle flag
4SUPERthe Super tail-replace leaves its mark in the side array
5..=8nonea commit on the running actor retires a live banner

The raises sit in the executable’s anim commit FUN_8004AD80 and fire only for a party actor committing the SpecialStarter clip. The three writes run in sequence and the later one wins - they are not an if / else chain. Each raise restarts the slide and queues the character’s Arts fanfare. Port: engine-battle-vm::battle_action::flash_ramp, stepped once per battle frame and drawn by both hosts.

In the port

engine-battle-hud is the renderer-agnostic model: per-slot HP / MP / AP and status, damage popups and log lines, fed by the world’s strike, status and round events. engine-ui::battle_hud::battle_hud_draws_for turns one frame of it into two draw lists - glyphs off the dialog-font atlas and sprites off the resident system-UI sheet - which both hosts composite in the slot they already use for menu chrome.

  • The surface samples the disc’s own cells: the marbled panel plate, the blue plate three-slice, the separator and the numerals are baked into the shared atlas and drawn 1:1. Without the atlas the plates degrade to solid rects and the numerals to font glyphs on the same cells - letterforms change, layout does not.
  • Numerals follow the displayed HP, the value retail ramps down over frames, through the ports of retail’s readout-tint laws; a dead member’s row dims.
  • Slot indices are absolute actor-table indices, so a host passes inactive slots through as empty rows rather than compacting the list.
  • A diagnostic surface (monster HP rows and gauge bars, K.O. tags, an encounter banner) exists for development behind LEGAIA_DIAG_HUD; it is off on both hosts, and retail draws none of it.

See also

The full write-up - each plate's packet-pinned derivation, the opening glides, the badge sheet and per-badge palettes, the commit log, the record-41/42 shift and every capture behind them - is in docs/subsystems/battle-hud.md.