SWI 0x0F — ObjAffineSet¶
- Entry:
0x00000CE0(ARM) - Status: verified (hardware-checked 2026-07-08: cycle counts and return values measured on real GBA via the worker ROM)
Summary¶
Computes the 2×2 affine matrix (PA/PB/PC/PD) for one or more rotated/scaled OBJs (sprites) from a rotation angle and independent X/Y scale factors. Like BgAffineSet but with no reference-point calculation and a caller-supplied output stride, so results can be written directly into OAM (where the four matrix entries are spaced 8 bytes apart) or into a packed array.
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | ptr | Source array of ObjAffineSource structs (8 bytes each). |
| r1 | ptr | Destination base address for the first PA value. |
| r2 | u32 | Number of entries to process. |
| r3 | u32 | Output stride in bytes between successive halfword writes. Use 2 for a packed array, 8 to write straight into OAM matrix slots. |
ObjAffineSource (input, 8 bytes)¶
| Offset | Type | Meaning |
|---|---|---|
| 0x00 | s16 | X scale, 8.8 fixed point. |
| 0x02 | s16 | Y scale, 8.8 fixed point. |
| 0x04 | u16 | Rotation angle; only the high 8 bits used (0–255 = full circle). |
| 0x06 | — | Padding. |
Output¶
Four s16 values in 8.8 fixed point written at r1, r1+r3, r1+2·r3,
r1+3·r3: PA, PB, PC, PD in that order.
Returns¶
None (results written via r1/r3).
Clobbered registers¶
r8, r9, sl, fp, ip used internally (restored from frame). r0 advances by 8 per entry; r1 advances by 4·r3 per entry.
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0/r1 advanced past the processed structs; r2 (count) and r3 (offset) preserved. 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¶
For each entry (count in r2, decremented, exits when negative):
- Angle → sin/cos using the shared internal sine table at
0x00000D5C(256-entry, 1.14 fixed point). Index = high byte of the angle field.sin = SineTable[index],cos = SineTable[(index + 64) & 255]. - Matrix (each product
>> 14, giving 8.8 output): - PA = (cos × scaleX) >> 14 → store, advance r1 by r3
- PB = −(sin × scaleX) >> 14 → store, advance
- PC = (sin × scaleY) >> 14 → store, advance
- PD = (cos × scaleY) >> 14 → store, advance
- Advance source by 8; repeat.
This is the same matrix as BgAffineSet; only the storage (strided post-increment
via STRH [r1], r3) and the absence of the reference-point step differ.
Edge cases & known bugs¶
- Count 0 / negative: initial
count − 1 < 0test exits immediately. - Only the high byte of the angle is used (256 steps).
- No overflow saturation on the 8.8 results.
- The stride
r3is a raw byte offset applied after every halfword store; a value < 2 would overlap successive entries.
Rounding & saturation (static analysis)¶
Ghidra 12.1.2 decompilation confirms: Each of the four written halfwords is (sin/cos × scale) >> 14. The >> 14 is an arithmetic shift on a signed int (sign-preserving, truncating toward −∞ — negative products round down, not toward zero), and results are written through a plain (short)/(int) store that wraps on overflow with no saturation. The sin/cos values come from a 256-entry signed table at 0x00000D5C indexed by the angle's high byte (cos = (angle>>8)+0x40).
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).
n=1: 127 (identical for offset=2 and offset=8); n=4: 313 (EWRAM) →
≈62 cycles per entry + ≈65 fixed.
Open questions (need hardware verification)¶
- Confirm hardware treats the OAM-stride (r3 = 8) case as expected end-to-end.
GBATEK cross-reference¶
Matches GBATEK's ObjAffineSet (8-byte source, stride parameter, shared sine table). Documents the shared sine table at 0x00000D5C in 1.14 format.