At a glance

Where
Extraction PROT entry 888 (raw TOC index 0x37A); 297 records
Loader
FUN_8001FA88, whose only caller on the disc is battle init FUN_800513F0
Record
8 bytes, the same columns as the static SFX descriptor
Index
Row = cue id − 0x200
Session handle
gp+0x678 (0x8007B990): pointer to this bank's record table
Parser
legaia_container_formats::bse_bank; detection class bse_bank
Used by
battle cue router FUN_8004FE5C, the sound drainer FUN_80016B6C

A battle load, not a boot load

The loader looks like a sound-init routine, and it has exactly one caller anywhere on the disc: battle init, run once at battle-scene setup. So the bank is loaded for each battle rather than once at start-up.

The loader names the file two ways on a dev / retail flag - by path string in the dev branch, by archive index in the retail branch - and both write the same buffer. That is what ties the file name bse.dat to the archive entry.

Two pointers, two jobs

PointerWhat it isWho uses it
gp+0x5B8 (0x8007B8D0)the sound subsystem's shared current-bundle slot; whatever loaded last owns itthe drainer reads a descriptor for a cue >= 0x200 out of whatever bundle is current
gp+0x678 (0x8007B990)the session handle to this bank's record tablethe battle cue router writes one descriptor column before enqueueing the cue

The current-bundle slot holds this bank only until the next field scene loads and repoints it. Because every caller of the router is battle-side, the write and the read land on the same row.

The second pointer looks consumer-less to an ordinary address sweep: a gp-relative access carries no address at all. Finding its readers takes a scan for that form - see the address-reference scan.

Layout

The second header word is consumed as a byte offset, not a count. The walk ends at a record whose first four bytes are zero.

Record columns

These are the static table's columns not by analogy but because one block of code decodes both tables: the drainer picks the static row or this bank's row on a single compare against 0x200, then falls into the same field reads. In n the low five bits are the voice count and bit 0x20 marks a sustained cue. The category selects a mixer record whose own fields are the enable gate and the VAB slot the cue keys.

An earlier reading took +4 for a 32-bit value. It is a one-byte category followed by three bytes nothing reads.

The category is rewritten per cue

The authored category column is a default. Before it enqueues a cue, the battle cue router writes that row's category from a byte on the actor that fired it, so a live cue's sound-bank slot is chosen by the actor - which is how one cue plays from a party member's bank or a monster's. Both of the router's legs reduce to the same expression: byte +4 of row cue_id - 0x200, exactly the row the drainer will resolve.

One format, two occupants

The >= 0x200 bank is a role, and this format is what fills it; which file fills it depends on the mode. In battle it is bse.dat. In the field it is the scene's own prescript record 0, authored per scene. A second archive entry the detector matches (extraction 1195, seven rows) is one such per-scene bank, not a rival copy of bse.dat.

The bytes past the table are not this bank's

The table ends well before the entry does, and the remaining bytes are neither padding nor a second record family. They are sound-bank tone-attribute rows left in the sector by whatever occupied this disc slot before bse.dat was written over its head - byte-identical to a neighbouring entry's tone table at the same file offsets. Nothing in them indexes the bank above.

One consequence: the entry has no authored terminator. The walk stops on four zero bytes that are really a field of the first leftover row - a field that is structurally zero on every retail tone, so the stop is reliable, but it is a foreign row's field.

See also

Source of record: docs/formats/bse-dat.md.