SWI 0x0E — BgAffineSet¶
- Entry:
0x00000C2C(ARM) - Status: verified (hardware-checked 2026-07-08: cycle counts and return values measured on real GBA via the worker ROM)
Summary¶
Builds the affine transformation parameters (PA/PB/PC/PD) and the internal reference-point offset for one or more rotated/scaled backgrounds. For each input entry it computes the 2×2 matrix from a rotation angle and independent X/Y scale factors, then derives the background reference point so that a chosen on-screen "display center" maps to a chosen texture-space "origin".
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | ptr | Source array of BgAffineSource structs (20 bytes each). |
| r1 | ptr | Destination array of BgAffineDest structs (16 bytes each). |
| r2 | u32 | Number of entries to process. |
BgAffineSource (input, 20 bytes)¶
| Offset | Type | Meaning |
|---|---|---|
| 0x00 | s32 | Texture-space origin X, 24.8 fixed point (signed). |
| 0x04 | s32 | Texture-space origin Y, 24.8 fixed point. |
| 0x08 | s16 | On-screen display center X (integer pixels). |
| 0x0A | s16 | On-screen display center Y. |
| 0x0C | s16 | X scale, 8.8 fixed point. |
| 0x0E | s16 | Y scale, 8.8 fixed point. |
| 0x10 | u16 | Rotation angle; only the high 8 bits are used (0–255 = full circle). |
| 0x12 | — | Padding. |
BgAffineDest (output, 16 bytes)¶
| Offset | Type | Meaning |
|---|---|---|
| 0x00 | s16 | PA (dx), 8.8. |
| 0x02 | s16 | PB (dmx), 8.8. |
| 0x04 | s16 | PC (dy), 8.8. |
| 0x06 | s16 | PD (dmy), 8.8. |
| 0x08 | s32 | Reference point X, 24.8. |
| 0x0C | s32 | Reference point Y, 24.8. |
Returns¶
None (results written to r1 array).
Clobbered registers¶
r3–r9, sl, fp, ip used internally (r4–fp restored from frame). r0/r1 advance.
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0/r1 advanced past the processed structs; r2 (count) preserved; r3 = 0. 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 (loop count in r2, decremented, exits when it goes negative):
- Angle → sin/cos. Take the high byte of the angle field as an index
0–255.
sin = SineTable[index],cos = SineTable[(index + 64) & 255](a 64-entry = 90° phase shift). See the trig table note below. - Matrix (each product arithmetic-shifted right by 14):
- PA = (cos × scaleX) >> 14
- PB = −(sin × scaleX) >> 14
- PC = (sin × scaleY) >> 14
- PD = (cos × scaleY) >> 14
The sine table is 1.14 fixed point and the scales are 8.8, so the >>14 yields 8.8 results as required by the hardware BGxPA–PD registers. 3. Reference point. With display center (dx, dy) and origin (ox, oy): - RefX = ox − (PA × dx + PB × dy) - RefY = oy − (PC × dx + PD × dy)
Computed with MLA chains; PA–PD are 8.8, the display centers are integer,
so products are .8 and add to the 24.8 origin, giving 24.8 reference points.
4. Advance source by 20 and destination by 16; repeat.
Internal sine table (0x00000D5C)¶
256 signed 16-bit entries in 1.14 fixed point (0x4000 = 1.0). Entry i =
round(16384 × sin(i · 2π/256)). Verified against a true sine: max error ≈ 6e-5.
Sample values: [0]=0, [32]=11585 (≈0.707), [64]=16384 (=1.0), [128]=0,
[192]=−16384. Cosine is obtained by indexing +64. The same table is shared by
ObjAffineSet (SWI 0x0F).
Edge cases & known bugs¶
- Count 0 (or negative when treated as signed): the initial
count − 1 < 0test exits immediately; nothing written. - Only the high byte of the 16-bit angle contributes; the low 8 bits are ignored, giving 256 angular steps (1.406° each).
- No overflow saturation: large scale values can overflow the 8.8 PA–PD range and wrap.
Rounding & saturation (static analysis)¶
Ghidra 12.1.2 decompilation confirms: Each matrix element is (sin/cos × scale) >> 14 and each reference-point term is a sum of (element × coord) >> 14 products. 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: 188; n=4: 533 (EWRAM src/dst) → ≈115 cycles per entry + ≈73 fixed.
Open questions (need hardware verification)¶
- Confirm the 24.8 interpretation of the origin/reference fields on hardware.
- Rounding behavior (truncation via ASR) at negative products.
GBATEK cross-reference¶
Matches GBATEK's BgAffineSet struct layout and matrix. Adds the concrete sine
table location (0x00000D5C), its 1.14 format and the +64 cosine indexing.