Headerless 16bpp stills Confirmed
Two archive entries hold a picture with nothing in the file that says so - no header, no magic, no width. They are raw 15-bit pixels and a length. The rectangle that makes them an image lives in the code that uploads them, as four immediates, and this page writes those immediates down. They are the Muscle Dome's ringside panel, int.tim and int2.tim, and which of the two you see depends on the party leader's health.
At a glance
- What it is
- Extraction PROT
1221/1222, each exactly0x28000bytes of raw BGR555 with no TIM header - Shape
- One 320 × 256 image, uploaded as four 320 × 64 bands to VRAM
(384, 0) - Who reads it
FUN_801F6B24in the PROT0978field_back_readoverlay, at slot-B base0x801F69D8- Which entry
- Chosen at load time from the party leader's live HP - below
- Parser
legaia_asset::ringside_still
The behavioural side - what the stills are for, which module owns them and what else is in that bundle - is on the minigame-muscle-dome.md.
Layout: four bands, no remainder
A band is 320 × 64 × 2 = 0xA000 bytes, which is also 20 sectors exactly. Those are two independent statements of the same size and they agree, which is most of the reason to believe the shape at all. Four bands tile the entry with nothing left over, and pixels run row-major inside a band, so the entry is one continuous 320 × 256 picture cut into four horizontal strips.
rect.y changes between the four uploads; x, w and h are written once.| Field | Value | Where it comes from |
|---|---|---|
| width | 320 (0x140) | rect.w immediate |
| height | 256 = 4 × 64 | four uploads of rect.h = 0x40 |
| VRAM x | 384 (0x180) | rect.x immediate |
| VRAM y | 0, 64, 128, 192 | the per-band rect.y store |
| pixel | BGR555, STP clear | the residue classifier's bgr555 test |
What names the rectangle
The rect lives at 0x801F735C and is built by one arm of FUN_801F6B24. Three of its four halfwords are written once and never touched again:
801f6be4 li v0,0x180 ; rect.x = 384
801f6bec sh v0,0x735c(at)
801f6bf0 li v0,0x140 ; rect.w = 320
801f6bf8 sh v0,0x7360(at)
801f6bfc li v0,0x40 ; rect.h = 64
801f6c04 sh v0,0x7362(at)
801f6c20 sh zero,0x735e(at) ; rect.y = 0, then 0x40 / 0x80 / 0xC0
The read is FUN_8003E964(sector, 0) seeking 0 / 0x14 / 0x28 / 0x3C, then FUN_8003E800(dst, 0x14, 1) for 20 sectors; the upload is FUN_800583C8, which is LoadImage - it hands the literal at 0x800156D4 to the debug hook before the libgpu vtable call.
Those arms are one of two jump tables in the same routine. A beqz on the special-battle word _DAT_8007BAC0 at 0x801F6BA8 picks between them: non-zero takes the 12-arm panel-still table at 0x801F6AA8 (these stills), zero takes the 19-arm field-restore table at 0x801F6AD8, which uploads the field party's texture pages instead. So an ordinary battle teardown never reaches this rectangle - it reads a different table.
Which entry is which
The PROT index is computed, not a literal, which is why a sweep for literal references finds no loader for either entry:
801f6b90 lhu v0,0x4824(v0) ; party slot 0 hp_max_record (record +0x11C)
801f6b98 lhu v1,0x480e(v1) ; party slot 0 hp_curr_live (record +0x106)
801f6ba4 srl v0,v0,0x1
801f6bac sltu s0,v1,v0 ; s0 = current HP < max / 2
801f6c3c addiu a0,s0,0x4c7 ; raw TOC 0x4C7 + s0
801f6c40 jal 0x8003e8a8 ; the LBA resolver
Raw TOC 0x4C7 / 0x4C8 are extraction 1221 / 1222 under the +2 correction, and the same s0 picks between the two dev-path strings on the dev-file branch - which is what ties each index to a filename. 1221 is int.tim, 1222 is int2.tim, and int2.tim is the below-half-HP variant. The record offsets are the live ones from the per-character save record.
Two details there are the kind a backward-only or literal-only scan loses: the index is formed by addiu in the jal's delay slot, and the comparison reads the character record through lhu at a gp-free absolute pair rather than through any table.
What draws it, and when
The still is loaded at a battle's end and drawn a screen later, by the Muscle Dome's contest hub: two textured quads that address it by texture page (0x106 and 0x109, VRAM x 384 and 576) rather than by pixel column, one 320x240 image split at x = 192. The hub draws them only when it is re-entered after a finished leg, as the backdrop of the INTERVAL score tally and the ROUND card that follows - the first visit draws a different backdrop. Its brightness is the hub's backdrop fade level, which climbs to full with the INTERVAL heading, dims to half while the tally rolls, returns to full, and fades out with the ROUND card. Checkpoints from a dome contest played through three hub visits show both packets in the frame's primitive pool on the second and third visits, at the level the first of those arms had reached.
In the port both play hosts draw it the same way, from one set of engine kernels: the pick at the leg's end, the fade envelope, the two quads, and the sheet assembled band by band at the loader's own rectangles. The standalone minigames page does not.
Why it has no magic
Nothing in the entry identifies it. Length is the only structural statement it makes, and 0x28000 is not distinctive on its own, so legaia_asset::ringside_still::has_still_shape is a length test and the selection is by PROT index. That is not a shortcut around a detector - there is nothing to detect. A still and any other raw 16bpp VRAM region are the same bytes, and the only thing that distinguishes them is which code uploads them where.