At a glance

Where
PROT entry 899 (the menu overlay), TIM header at offset 0x1F908; overlay address 0x801EE120
Image
4bpp, 256 × 16 texels, uploaded to VRAM (960, 224)
Palettes
One 256-entry CLUT row at VRAM (0, 1) = sixteen 16-colour palettes
Tiles
Sixteen 16×16 tiles; fifteen portraits and one blank
Parser
legaia_ui_formats::save_icon (finds the sheet by scanning for its two rects, not by offset)
Modding
legaia-patcher save-icon-list / save-icon-export / save-icon-replace
Used by
save screen, memory-card block writer

The image's flag word sets a reserved bit, so a strict TIM reader rejects it; a lenient reader plus the rect fingerprint is the parse path.

The strip

image: 256 × 16 texels, row = 128 bytes 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 blank tile t = bytes t×8 .. t×8+8 of every row palette row: 256 entries = 16 palettes of 16 palette t belongs to tile t: texture u = t × 16, CLUT id = 0x40 + t
Every consumer derives both the texture coordinate and the palette from one index. That is why the sheet cannot go through a generic single-palette texture replacer: a new colour for one portrait has to land in that portrait's palette and no other.

Two consumers, two indices

The tile space is a character space - fifteen different faces - and its two consumers index it by different quantities.

ConsumerIndexHow
Memory-card block icon (FUN_801E1934)card slotgrabs the tile and its palette back out of VRAM into the save block; no modulo, no clamp - a card holds 15 blocks
Save-screen info panel (FUN_801E08D8)party id (0 Vahn, 1 Noa, 2 Gala)one portrait per party member in the slot summary

So the save the game shows as number N carries tile N - 1, checked against real cards.

Why a byte search finds the palette and not the pixels

A save block stores its icon as one contiguous 16×16 tile. The strip stores the same tile as sixteen 8-byte runs spaced 128 bytes apart. Retail never converts in software - it reads a rectangle back out of VRAM, and a rectangular read is contiguous by construction. So searching the disc for a real save's icon finds its palette verbatim in the overlay and its pixels nowhere. That asymmetry is the layout difference, not a missing asset.

All four header bytes of a save block are part of the icon's contract. A writer that stamps only the two magic bytes leaves the icon-frame descriptor as found - zero in a previously free block - and the BIOS card browser reads the block as malformed. The port stamps the whole header.

The second copy

Tiles 0, 1 and 2 also ship as three standalone 16×16 images in the boot-resident system-UI TIM-pack, already contiguous because each is its own image. Their bytes match strip tiles 0 to 2 exactly, which makes them an independent check on the de-interleave. They live in the head region of the archive that the extraction index space skips, which is why no extracted file contains them - the same +2 numbering skew the CDNAME page documents.

Tile 15 and modding

Tile 15 is blank - one flat pixel index and an all-zero palette. It is width padding that rounds the strip to a whole number of VRAM words, and no code path selects it: both index rules are bounded below 15. The patcher therefore exposes 15 slots, not 16.

Replacing one portrait is 17 same-size in-place writes - the tile's palette plus its sixteen pixel runs - so every other portrait stays byte-identical.

See also

Source of record: docs/formats/save-icon.md.