SWI 0x15 — RLUnCompVram¶
- Entry:
0x000012C1(THUMB, real entry0x000012C0) - Status: verified (hardware-checked 2026-07-08: cycle counts measured and output round-trip-verified on real GBA via the worker ROM)
Summary¶
Decompresses an RLE stream into a 16-bit-only destination (VRAM/OAM/palette). It decodes the same block format as the Wram variant but buffers two output bytes and writes them as one halfword.
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | ptr | Source address pointing at the 4-byte compression header. |
| r1 | ptr | Destination address (halfword-aligned, halfword-writable). |
Returns¶
No return value.
Clobbered registers¶
r0–r3 and 12 bytes of stack scratch. r4–r7 saved/restored.
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0/r1 advanced past stream/output; r3 = 0x170 leftover. r2, r4–r12, r13, and CPSR (flags and mode) came back bit-identical on every tested path. r11/r12 are explained by the SWI dispatcher (it pushes {r11, r12, lr} — see 10_irq_boot_and_iwram.md) and CPSR by the SPSR restore on return; r2 and r4–r10 must be preserved or restored by the routine itself — where a static note above claims r2 is destroyed, the hardware disagrees at the caller level. Raw data: results/clobber_audit_pass2.csv.
Algorithm¶
Header handling and block decoding match SWI 0x14: a length byte whose bit 7
selects uncompressed (count+1 literal bytes) or compressed (count+3
copies of one byte).
Output differs: each decoded byte is OR-ed into a 16-bit accumulator at the current byte slot; the slot toggles every byte; when it returns to the low slot (every second byte) the halfword is stored and the accumulator cleared. This keeps all destination writes 16-bit wide.
Edge cases & known bugs¶
- Decompressed size 0: shared header check returns "skip"; nothing written.
- Odd decompressed size: a store occurs only on a completed pair, so an odd final byte is left pending in the accumulator and is never written. RLE output for VRAM is normally even-sized; an odd declared size loses the last byte.
- Type nibble not enforced; source-region protection identical to the
other decompression SWIs (source in
0x00000000–0x01FFFFFFrefused). -
Overshoot of the declared size on a too-long block behaves as in the Wram variant (measured in logical bytes, then rounded down to whole halfwords by the pairing).
-
Odd-size drop hardware-confirmed (2026-07-08 anomaly tests,
results/cycles_phase3_anomaly.csv): declared size 255 wrote bytes 0–253; byte 254 was never written (sentinel intact).
Cycle count¶
Hardware-measured net CPU cycles (worker-ROM harness, TM0/TM1 cascade at F/1,
13-cycle baseline subtracted; identical across 3 runs — see
02_hardware_verification_checklist.md § Measurement setup).
Same 180-byte stream as SWI 0x14, 256-byte output (EWRAM): 5734 cycles (≈1.5× the Wram variant — halfword buffering).
Open questions (need hardware verification)¶
- Whether any shipped asset declares an odd size for this SWI (would drop a byte).
GBATEK cross-reference¶
Matches GBATEK RLE format. Adds the halfword-buffering description and the dropped-final-byte behaviour on odd sizes.