At a glance

In the game
The moment NEW GAME is confirmed - the party's opening stats, names, bag and gold
Where
Table at SCUS VA 0x80078C4C; bag and world-state seeds are instruction immediates in FUN_80034A6C / FUN_800560B4
Size
4 records × 26 bytes (8 × u16 stats + 10-byte name)
Expands into
The live save records at 0x80084708 + n × 0x414
Parser
legaia_asset::new_game (StartingParty, StartingInventory, new_game_seed_words, seed_live_records); CLI asset new-game <SCUS> [--json]
Confidence
Confirmed - byte-search of the executable cross-validated against a live town01 save state; seed stores read off the disassembly
Used by
Boot, Level-up, Randomizer

What a New Game is made of

Confirming NEW GAME launches the field overlay with a fresh game state built from four sources. Only the first is a table.

1
Party template
Four 26-byte rows - stats + name for Vahn, Noa, Gala, Terra - expanded into the live records.
StartingParty
2
Starting bag
Code-built: one (id, count) slot, Healing Leaf ×5, written into the owned-item array.
StartingInventory
3
World-state seed
Fixed immediates into the save context: 500 gold, party-slot bytes, a cleared story-flag bank.
new_game_seed_words
4
Opening scene
town01 (Rim Elm), the default map-name buffer. A developer initmap.txt override exists but retail boots with it gated off.
engine-core::new_game

At a true New Game only Vahn has joined; the other rows are the templates used as each character is introduced.

Template row (26 B) hp mp agl atkudf ldf spd int name Code literals level 1, cap 100, bag, 500 gold Seed routine 4-iteration loop Live record ×4 0x414 B each, stats + level Save context bag, gold, party, flags
Template stats and code literals meet in the seed routine, which writes the four live records and the save context.

Record layout

OffsetSizeFieldMeaning
+02hp_maxalso the starting HP
+22mp_maxalso the starting MP
+42agllands in the live agility cells (+0x110 current, +0x122 max) - not the cap cells, which are a literal 100
+62atk
+82udfupper / physical defence
+102ldflower / magical defence
+122spdturn-order initiative seed
+142intel
+1610display nameNUL-padded

Decoded values

SlotNameHPMPAGLATKuDEFlDEFSPDINT
0Vahn18020100241612199
1Noa15010120211311303
2Gala21040803043301520
3Terra4002002004520174525
History: the “+4 seeds the cap” reading

A Vahn-only save shows 100 in the agility cells, the cap cells and the spirit gauge alike, which once read as the +4 field fanning out to all of them. The seed loop's disassembly stores a literal 100 into the cap cells for every slot; Noa (120) and Gala (80) separate the two. See the cap constant.

Starting bag and world state (code literals)

The new-game data-init routine holds the save-context base in a register for its whole body and writes fixed values at offsets from it. The bag is one (id, count) pair in the owned-item array at SC + 0x1818 - a single ordered list shared by every item category, which the menu merely filters into Items / Goods / Key tabs. A seeded slot can therefore hold any id, which is how the randomizer's starting-bag toggles seed keys and accessories.

SC offsetWidthFieldValue
+0x1818 / +0x18191 + 1bag slot 0: item id, count0x77 Healing Leaf, 5
+0x4541party byte3
+0x457 / +0x4581party bytes0
+0x459 .. +0x45B1party-slot bytes1 / 2 / 3
+0x45C4gold0x1F4 = 500
+0x460 / +0x464 / +0x470 / +0x4784counters0
+0x590 .. +0x59C4words0x44 / 0x21 / 0x10 / 0x48
+0x1618 .. +0x18170x200story flagscleared after the bag write - the Door-of-Wind warp mask at +0x161C is inside, so any warp preset is applied after the clear

Widths come from the store opcodes (sb / sh / sw), not from the decompiler's globals naming. StartingInventory decodes the bag by replaying the routine's register-sourced stores, so it reads back either the retail byte pair or the randomizer's packed halfword (id | count << 8).

Details: the seed instruction shapes
OpTop 16 bitsMeaning
addiu $v0, $zero, imm0x2402load constant into the source register
sb $v0, off($s0)0xA202byte store at SC + off
sh $v0, off($s0)0xA602halfword store at SC + off

The bag write is four instructions at 0x80034B0C. A six-instruction loop after it zeroes the 512 bytes below the bag, but both callers of the routine memset the whole SC[0..0x1A18) first, so that loop is redundant - the reclaimable region is 10 instructions (40 bytes) from 0x80034B04. Three further absolute stores (0x80073EF4 = 0xE40, 0x80073EF8 = 0x2DC0, 0x80073EFC = 0) are not save-context cells.

Starting level and experience

The template expander also initialises each live record's progression cells. The shown level is read from +0x130 directly, never re-derived from experience: a record with level-10 experience but +0x130 == 1 shows LV 1.

Record offsetFieldVanilla seed
+0x0 (u32)cumulative experience0
+0x4 (u32)next-level threshold (the status screen's “next”)Vahn/Terra 121, Noa 102, Gala 140 - the per-slot level-up correction, not a per-character curve
+0x130 (u8)displayed level; the applier adds 1 per level-up1
+0x131 (u8)second per-character byte (magic-rank candidate, unconfirmed)1
Details: how the starting-level option retargets the seed

The routine seeds +0x4 by storing one register literal and reloading it twice for the per-character values. The patch controls that register and reclaims the reload sites, seeding Vahn, Noa and Gala at level N with same-size edits:

  • Level - a packed addiu $v0, (1 << 8) | N; sh $v0, 0x6F8($s0); nop at 0x800561C4..CC sets +0x130 = N and keeps +0x131 = 1.
  • Experience - one addiu $t0 preload at 0x800560FC (midpoint of reach(N)..reach(N+1)) feeds three sw $t0 stores at 0x80056100 / 08 / 18 (Vahn 0x5C8, Noa 0x9DC, Gala 0xDF0).
  • Next threshold - the literal at 0x800560F0 loads reach(N+1); all three stores take it, and the applier re-applies the per-slot correction on the first level-up.
  • Stats - each growth slot's template stats are recomputed to level N.

Seeding only slot 0 would leave Noa at 0 experience and Gala with a stale threshold of 140 (a spurious early level-up); seeding all three keeps the roster coherent.

Parser

legaia_asset::new_game::StartingParty reads the table from the user's own SCUS_942.54 at runtime through the PSX-EXE address-to-file-offset map (the same resolver as the item-name table), so no Sony bytes are committed. seed_live_records ports the expander's stores and the engine's record builder applies them rather than restating the mapping, so the two cannot drift. new_game_seed_words mirrors the world-state table; a disc-gated test re-decodes the routine and fails on any offset, value or width the port gets wrong.

How we know

FunctionAddressWhat it proves
Template expanderFUN_800560B4The 4-iteration loop copying template fields into live records; literal 100 into the cap cells; level / threshold seeds
New-game data initFUN_80034A6CBag pair, gold and party bytes as immediates; story-flag clear
CallersFUN_8001DCF8 (new-game branch, flag 0x8007B7AC), FUN_8001FFA4Both memset the save context first
Boot chainFUN_80025B64FUN_801D6704Field overlay entered with the fresh state
Opening scene0x8007050C; FUN_8001D424 gated by _DAT_8007B8C2Default map buffer town01; the initmap.txt developer override is off in retail
Table base0x80078C4CByte-search for Vahn's stat run + name; every field cross-checked against the live record at 0x80084708 in a town01 save; disc-gated new_game_real

See also