Table base + record layout

FieldValue
Base address0x80078C4C (Vahn's record)
Stride26 bytes (8×u16 stats + 10-byte name)
Records4 - Vahn, Noa, Gala, Terra, in roster order
+0   u16     hp_max   (also the starting HP)
+2   u16     mp_max   (also the starting MP)
+4   u16     agl      (also seeds the spirit-gauge value + stat cap)
+6   u16     atk
+8   u16     udf      (upper / physical defence)
+10  u16     ldf      (lower / magical defence)
+12  u16     spd      (turn-order initiative seed)
+14  u16     intel
+16  u8[10]  display name, NUL-padded
SlotNameHPMPAGLATKuDEFlDEFSPDINT
0Vahn18020100241612199
1Noa15010120211311303
2Gala21040803043301520
3Terra4002002004520174525

At a true New Game only Vahn has joined; the other rows are the templates the game uses as each character is introduced. The +4 stat fans out to several live fields at once - cross-validated against an early town01 save state, Vahn's 100 lands in the live record as agl, the stat-cap constant, and the initial spirit-gauge value.

Starting inventory (code-built, not a table)

The opening inventory is built in code by the new-game data-init FUN_80034A6C, writing a single slot into the live owned-item array at 0x80085958 (= SC save-context base 0x80084140 + 0x1818; 2 bytes/slot [id: u8][count: u8], id-0 terminator):

0x80034b0c  addiu $v0, $zero, 0x77    ; item id  = Healing Leaf
0x80034b10  sb    $v0, 0x1818($s0)    ; inventory[0].id   ($s0 = SC base)
0x80034b14  addiu $v0, $zero, 5       ; count    = 5
0x80034b18  sb    $v0, 0x1819($s0)    ; inventory[0].count

A vanilla New Game starts with Healing Leaf ×5 and nothing else. The array at +0x1818 is a single ordered (id, count) owned-item list shared by every item category - the inventory menu only filters it into Items / Goods / Key tabs by item id, so a seeded slot can hold any id regardless of category (the randomizer's starting-bag toggles use this same array). legaia_asset::new_game::StartingInventory decodes the region by replaying its $v0-source stores (addiu 0x2402 / sb 0xA202 / sh 0xA602 top-16 signatures) into an SC-offset → byte map, so it reads back either the vanilla byte pair or a randomizer-packed halfword store.

Starting level / XP seed

The seed routine FUN_800560B4 also initialises each live record's progression fields:

Record offsetFieldVanilla seed
+0x0 (u32)cumulative experience0
+0x4 (u32)next-level XP thresholdreach(L2): Vahn/Terra 121, Noa 102, Gala 140
+0x130 (u8)displayed character level (what "LV" shows; maintained +1 per level-up event)1
+0x131 (u8)second per-character byte the seed inits (magic-rank candidate, unconfirmed)1

The shown level is read from +0x130 directly, not re-derived from experience - confirmed live: a record with level-10 experience + stats but +0x130 == 1 still shows LV 1. The per-character +0x4 thresholds differ because of the small FUN_801E9504 per-slot correction, not because the XP curve is per-character. The starting-level randomizer retargets this seed routine with same-size in-place edits (level byte party-wide, in-band experience + threshold for the growth-capable slots).

Parser + provenance

The table base + stride are pinned by byte-search of SCUS_942.54 for Vahn's stat run followed by the "Vahn" name, with every field cross-validated against the live per-character record at 0x80084708 lifted from an early town01 save state. legaia_asset::new_game::StartingParty parses the table from a SCUS_942.54 image at runtime (PSX-EXE t_addr → file-offset map), so the engine seeds a faithful New Game from the user's own disc without committing any Sony bytes. The disc-gated new_game_real test pins the four rows against the real executable. CLI: asset new-game <SCUS> [--json] (dumps the party template + the code-built starting inventory).

See also