@raskell/wasm-ad 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,769 @@
1
+ // AssemblyScript SDK for WASM-AD carts.
2
+ //
3
+ // A cart exports `update` (and optionally `start` and `scanline`) and drives
4
+ // the console by writing its memory-mapped hardware, which this module wraps
5
+ // in typed helpers. See SPEC.md for the hardware.
6
+ //
7
+ // AssemblyScript keeps module-level `let`/`var` in WebAssembly globals, but a
8
+ // cart's state must live in linear memory (SPEC.md §12). So this SDK has no
9
+ // mutable globals, its values are plain integers (no allocation), and carts
10
+ // keep their own state in a `@unmanaged` class placed with `memory.data`:
11
+ //
12
+ // @unmanaged class State { x: i32; y: i32 }
13
+ // const state = changetype<State>(memory.data(offsetof<State>()));
14
+
15
+ // ---- Memory map (SPEC.md §4) ------------------------------------------------
16
+
17
+ export const SCREEN_W: i32 = 240;
18
+ export const SCREEN_H: i32 = 160;
19
+
20
+ export const IO: usize = 0x00400;
21
+ export const PAL_BG: usize = 0x00800;
22
+ export const PAL_OBJ: usize = 0x00a00;
23
+ export const OAM: usize = 0x00c00;
24
+ export const VRAM: usize = 0x01000;
25
+ export const VRAM_SIZE: usize = 0x18000;
26
+ /** Sprite tile 0. */
27
+ export const OBJ_VRAM: usize = VRAM + 0x10000;
28
+ export const SAVE: usize = 0x19000;
29
+ export const SAVE_SIZE: usize = 0x8000;
30
+ /** Sample RAM: signed 8-bit samples for the sample channels (SPEC.md §14.6). */
31
+ export const SAMPLE_RAM: usize = 0x21000;
32
+ export const SAMPLE_RAM_SIZE: usize = 0x7000;
33
+ /** Wave RAM: two banks of 16 bytes (SPEC.md §14.5). */
34
+ export const WAVE_RAM: usize = 0x490;
35
+ /** First address the cart may use for its own data, stack and heap. */
36
+ export const CART: usize = 0x28000;
37
+ /** OAM attribute 0 that hides a sprite. */
38
+ export const OBJ_HIDDEN: u16 = 0x0200;
39
+ export const SPEC_VERSION: u16 = 0x0002;
40
+
41
+ // ---- Registers (SPEC.md §5, §6, §14) ----------------------------------------
42
+
43
+ /** Every register address, by its SPEC.md name. Use with `read16`/`write16`. */
44
+ export namespace reg {
45
+ export const DISPCNT: usize = 0x400;
46
+ export const VCOUNT: usize = 0x406;
47
+ export const WIN0H: usize = 0x440;
48
+ export const WIN1H: usize = 0x442;
49
+ export const WIN0V: usize = 0x444;
50
+ export const WIN1V: usize = 0x446;
51
+ export const WININ: usize = 0x448;
52
+ export const WINOUT: usize = 0x44a;
53
+ export const MOSAIC: usize = 0x44c;
54
+ export const BLDCNT: usize = 0x450;
55
+ export const BLDALPHA: usize = 0x452;
56
+ export const BLDY: usize = 0x454;
57
+ export const KEYS: usize = 0x700;
58
+ export const FRAME: usize = 0x704; // u32
59
+ export const SEED: usize = 0x708; // u32
60
+ export const SAVECNT: usize = 0x70c;
61
+ export const SAVESTAT: usize = 0x70e;
62
+ export const SPEC: usize = 0x710;
63
+
64
+ export const BG2PA: usize = 0x420;
65
+ export const BG2PB: usize = 0x422;
66
+ export const BG2PC: usize = 0x424;
67
+ export const BG2PD: usize = 0x426;
68
+ export const BG2X: usize = 0x428; // u32
69
+ export const BG2Y: usize = 0x42c; // u32
70
+ export const BG3PA: usize = 0x430;
71
+ export const BG3PB: usize = 0x432;
72
+ export const BG3PC: usize = 0x434;
73
+ export const BG3PD: usize = 0x436;
74
+ export const BG3X: usize = 0x438; // u32
75
+ export const BG3Y: usize = 0x43c; // u32
76
+
77
+ export const SOUND1CNT_L: usize = 0x460;
78
+ export const SOUND1CNT_H: usize = 0x462;
79
+ export const SOUND1CNT_X: usize = 0x464;
80
+ export const SOUND2CNT_L: usize = 0x468;
81
+ export const SOUND2CNT_H: usize = 0x46c;
82
+ export const SOUND3CNT_L: usize = 0x470;
83
+ export const SOUND3CNT_H: usize = 0x472;
84
+ export const SOUND3CNT_X: usize = 0x474;
85
+ export const SOUND4CNT_L: usize = 0x478;
86
+ export const SOUND4CNT_H: usize = 0x47c;
87
+ export const SOUNDCNT_L: usize = 0x480;
88
+ export const SOUNDCNT_H: usize = 0x482;
89
+ export const SOUNDCNT_X: usize = 0x484;
90
+
91
+ export const PCMA_SAD: usize = 0x4b0;
92
+ export const PCMA_LEN: usize = 0x4b2;
93
+ export const PCMA_LOOP: usize = 0x4b4;
94
+ export const PCMA_RATE: usize = 0x4b6;
95
+ export const PCMA_CNT: usize = 0x4b8;
96
+ export const PCMA_POS: usize = 0x4ba;
97
+ export const PCMB_SAD: usize = 0x4c0;
98
+ export const PCMB_LEN: usize = 0x4c2;
99
+ export const PCMB_LOOP: usize = 0x4c4;
100
+ export const PCMB_RATE: usize = 0x4c6;
101
+ export const PCMB_CNT: usize = 0x4c8;
102
+ export const PCMB_POS: usize = 0x4ca;
103
+
104
+ /** `BGxCNT` for background 0–3. */
105
+ // @ts-ignore: decorator
106
+ @inline export function BGCNT(bg: i32): usize {
107
+ return 0x408 + (<usize>bg << 1);
108
+ }
109
+ /** `BGxHOFS` for background 0–3. */
110
+ // @ts-ignore: decorator
111
+ @inline export function BGHOFS(bg: i32): usize {
112
+ return 0x410 + (<usize>bg << 2);
113
+ }
114
+ /** `BGxVOFS` for background 0–3. */
115
+ // @ts-ignore: decorator
116
+ @inline export function BGVOFS(bg: i32): usize {
117
+ return 0x412 + (<usize>bg << 2);
118
+ }
119
+ }
120
+
121
+ // @ts-ignore: decorator
122
+ @inline export function read16(addr: usize): u16 {
123
+ return load<u16>(addr);
124
+ }
125
+ // @ts-ignore: decorator
126
+ @inline export function write16(addr: usize, v: u16): void {
127
+ store<u16>(addr, v);
128
+ }
129
+ // @ts-ignore: decorator
130
+ @inline export function read32(addr: usize): u32 {
131
+ return load<u32>(addr);
132
+ }
133
+ // @ts-ignore: decorator
134
+ @inline export function write32(addr: usize, v: u32): void {
135
+ store<u32>(addr, v);
136
+ }
137
+
138
+ /** `DISPCNT` bits (SPEC.md §6.1). */
139
+ export namespace dispcnt {
140
+ export const MODE0: u16 = 0;
141
+ /** BG0 and BG1 tiled, BG2 affine. */
142
+ export const MODE1: u16 = 1;
143
+ /** BG2 and BG3 affine. */
144
+ export const MODE2: u16 = 2;
145
+ export const MODE3: u16 = 3;
146
+ export const MODE4: u16 = 4;
147
+ export const MODE5: u16 = 5;
148
+ export const PAGE1: u16 = 1 << 4;
149
+ export const OBJ_1D: u16 = 1 << 6;
150
+ export const FORCED_BLANK: u16 = 1 << 7;
151
+ export const BG0: u16 = 1 << 8;
152
+ export const BG1: u16 = 1 << 9;
153
+ export const BG2: u16 = 1 << 10;
154
+ export const BG3: u16 = 1 << 11;
155
+ export const OBJ: u16 = 1 << 12;
156
+ export const WIN0: u16 = 1 << 13;
157
+ export const WIN1: u16 = 1 << 14;
158
+ export const OBJ_WIN: u16 = 1 << 15;
159
+ }
160
+
161
+ /** Layer bits for `BLDCNT` targets and window masks. */
162
+ export namespace layer {
163
+ export const BG0: u16 = 1 << 0;
164
+ export const BG1: u16 = 1 << 1;
165
+ export const BG2: u16 = 1 << 2;
166
+ export const BG3: u16 = 1 << 3;
167
+ export const OBJ: u16 = 1 << 4;
168
+ export const BACKDROP: u16 = 1 << 5;
169
+ /** In window masks: color effects enabled. */
170
+ export const EFFECTS: u16 = 1 << 5;
171
+ export const ALL: u16 = 0x3f;
172
+ }
173
+
174
+ /** `BLDCNT` effects (SPEC.md §6.5). */
175
+ export namespace blend {
176
+ export const NONE: u16 = 0;
177
+ export const ALPHA: u16 = 1 << 6;
178
+ export const BRIGHTEN: u16 = 2 << 6;
179
+ export const DARKEN: u16 = 3 << 6;
180
+
181
+ /** `BLDCNT` value: `effect` on `first` targets, blending onto `second`. */
182
+ // @ts-ignore: decorator
183
+ @inline export function cnt(effect: u16, first: u16, second: u16): u16 {
184
+ return effect | first | (second << 8);
185
+ }
186
+ /** `BLDALPHA` value; coefficients 0–16. */
187
+ // @ts-ignore: decorator
188
+ @inline export function alpha(eva: u16, evb: u16): u16 {
189
+ return eva | (evb << 8);
190
+ }
191
+ }
192
+
193
+ /** `KEYS` bits (SPEC.md §5). */
194
+ export namespace key {
195
+ export const A: u16 = 1 << 0;
196
+ export const B: u16 = 1 << 1;
197
+ export const SELECT: u16 = 1 << 2;
198
+ export const START: u16 = 1 << 3;
199
+ export const RIGHT: u16 = 1 << 4;
200
+ export const LEFT: u16 = 1 << 5;
201
+ export const UP: u16 = 1 << 6;
202
+ export const DOWN: u16 = 1 << 7;
203
+ export const R: u16 = 1 << 8;
204
+ export const L: u16 = 1 << 9;
205
+ }
206
+
207
+ // ---- Backgrounds (SPEC.md §6.2, §8) -----------------------------------------
208
+
209
+ /** Background map sizes, in tiles. */
210
+ export enum MapSize {
211
+ S32x32 = 0,
212
+ S64x32 = 1,
213
+ S32x64 = 2,
214
+ S64x64 = 3,
215
+ }
216
+
217
+ /** Affine background map sizes, in tiles (SPEC.md §8.4). */
218
+ export enum AffineSize {
219
+ S16x16 = 0,
220
+ S32x32 = 1,
221
+ S64x64 = 2,
222
+ S128x128 = 3,
223
+ }
224
+
225
+ /** Builders for a `BGxCNT` value (SPEC.md §6.2), e.g. `bgcnt.priority(bgcnt.screenBlock(0, 31), 1)`. */
226
+ export namespace bgcnt {
227
+ // @ts-ignore: decorator
228
+ @inline export function priority(c: u16, p: u16): u16 {
229
+ return (c & ~3) | (p & 3);
230
+ }
231
+ // @ts-ignore: decorator
232
+ @inline export function charBlock(c: u16, b: u16): u16 {
233
+ return (c & ~0xc) | ((b & 3) << 2);
234
+ }
235
+ // @ts-ignore: decorator
236
+ @inline export function screenBlock(c: u16, b: u16): u16 {
237
+ return (c & ~0x1f00) | ((b & 31) << 8);
238
+ }
239
+ // @ts-ignore: decorator
240
+ @inline export function size(c: u16, s: MapSize): u16 {
241
+ return (c & 0x3fff) | (<u16>s << 14);
242
+ }
243
+ // @ts-ignore: decorator
244
+ @inline export function bpp8(c: u16): u16 {
245
+ return c | 0x80;
246
+ }
247
+ // @ts-ignore: decorator
248
+ @inline export function mosaic(c: u16): u16 {
249
+ return c | 0x40;
250
+ }
251
+ /** Map size of an affine background. */
252
+ // @ts-ignore: decorator
253
+ @inline export function affineSize(c: u16, s: AffineSize): u16 {
254
+ return (c & 0x3fff) | (<u16>s << 14);
255
+ }
256
+ /** An affine background repeats instead of being transparent outside its map. */
257
+ // @ts-ignore: decorator
258
+ @inline export function wrap(c: u16): u16 {
259
+ return c | 0x2000;
260
+ }
261
+ }
262
+
263
+ /** Packs 8-bit channels into RGB555. */
264
+ // @ts-ignore: decorator
265
+ @inline export function rgb(r: i32, g: i32, b: i32): u16 {
266
+ return <u16>(((r & 255) >> 3) | (((g & 255) >> 3) << 5) | (((b & 255) >> 3) << 10));
267
+ }
268
+
269
+ /** Sets background color `i` (0–255; 16 banks of 16 for 4bpp graphics). */
270
+ // @ts-ignore: decorator
271
+ @inline export function setBgColor(i: i32, c: u16): void {
272
+ store<u16>(PAL_BG + (<usize>i << 1), c);
273
+ }
274
+ /** Sets sprite color `i` (0–255). */
275
+ // @ts-ignore: decorator
276
+ @inline export function setObjColor(i: i32, c: u16): void {
277
+ store<u16>(PAL_OBJ + (<usize>i << 1), c);
278
+ }
279
+
280
+ /** Address of 4bpp background tile `index` counted from character block `block` (0–3). Tiles are 32 bytes. */
281
+ // @ts-ignore: decorator
282
+ @inline export function bgTile(block: i32, index: i32): usize {
283
+ return VRAM + <usize>block * 0x4000 + (<usize>index << 5);
284
+ }
285
+ /** Address of 8bpp background tile `index` counted from character block `block` (0–3). Tiles are 64 bytes;
286
+ * affine backgrounds always use these. */
287
+ // @ts-ignore: decorator
288
+ @inline export function bgTile8(block: i32, index: i32): usize {
289
+ return VRAM + <usize>block * 0x4000 + (<usize>index << 6);
290
+ }
291
+ /** Address of an affine background's map at screen block `block` (0–31): one tile index per byte,
292
+ * row-major (SPEC.md §8.4). */
293
+ // @ts-ignore: decorator
294
+ @inline export function affineMap(block: i32): usize {
295
+ return VRAM + <usize>block * 0x800;
296
+ }
297
+
298
+ /** Address of 4bpp sprite tile `index` (0–1023). */
299
+ // @ts-ignore: decorator
300
+ @inline export function objTile(index: i32): usize {
301
+ return OBJ_VRAM + (<usize>index << 5);
302
+ }
303
+ /** Fills a 32-byte 4bpp tile with one byte value, e.g. 0x11 for solid color 1. */
304
+ // @ts-ignore: decorator
305
+ @inline export function fillTile(tile: usize, byte: u8): void {
306
+ memory.fill(tile, byte, 32);
307
+ }
308
+
309
+ /** Address of screen block `block` (0–31): a 32×32 map of u16 entries, row-major. */
310
+ // @ts-ignore: decorator
311
+ @inline export function screenBlock(block: i32): usize {
312
+ return VRAM + <usize>block * 0x800;
313
+ }
314
+ /** Sets entry `i` (0–1023) of screen block `block`. */
315
+ // @ts-ignore: decorator
316
+ @inline export function setScreenEntry(block: i32, i: i32, e: u16): void {
317
+ store<u16>(screenBlock(block) + (<usize>i << 1), e);
318
+ }
319
+
320
+ /** Background map entries (SPEC.md §8.2). */
321
+ export namespace screenEntry {
322
+ // @ts-ignore: decorator
323
+ @inline export function make(tile: u16, bank: u16): u16 {
324
+ return (tile & 0x3ff) | ((bank & 0xf) << 12);
325
+ }
326
+ // @ts-ignore: decorator
327
+ @inline export function hflip(e: u16): u16 {
328
+ return e | (1 << 10);
329
+ }
330
+ // @ts-ignore: decorator
331
+ @inline export function vflip(e: u16): u16 {
332
+ return e | (1 << 11);
333
+ }
334
+ }
335
+
336
+ // ---- Sprites (SPEC.md §9) ------------------------------------------------------
337
+
338
+ /** Sprite sizes as shape × 4 + size (SPEC.md §9.2). */
339
+ export enum SpriteSize {
340
+ S8x8 = 0,
341
+ S16x16 = 1,
342
+ S32x32 = 2,
343
+ S64x64 = 3,
344
+ W16x8 = 4,
345
+ W32x8 = 5,
346
+ W32x16 = 6,
347
+ W64x32 = 7,
348
+ T8x16 = 8,
349
+ T8x32 = 9,
350
+ T16x32 = 10,
351
+ T32x64 = 11,
352
+ }
353
+
354
+ /** Width in pixels of a sprite size. */
355
+ export function spriteWidth(s: SpriteSize): i32 {
356
+ const shape = <i32>s >> 2, size = <i32>s & 3;
357
+ if (shape == 0) return 8 << size;
358
+ if (shape == 1) return size == 0 ? 16 : size == 3 ? 64 : 32;
359
+ return size == 3 ? 32 : size == 2 ? 16 : 8;
360
+ }
361
+ /** Height in pixels of a sprite size. */
362
+ export function spriteHeight(s: SpriteSize): i32 {
363
+ const shape = <i32>s >> 2, size = <i32>s & 3;
364
+ if (shape == 0) return 8 << size;
365
+ if (shape == 1) return size == 3 ? 32 : size == 2 ? 16 : 8;
366
+ return size == 0 ? 16 : size == 3 ? 64 : 32;
367
+ }
368
+
369
+ /** One OAM entry's three attributes, packed as attr0 | attr1 << 16 | attr2 << 32. */
370
+ export type Sprite = u64;
371
+
372
+ /** Builders for sprites (SPEC.md §9.1), e.g. `sprite.affine(sprite.make(x, y, SpriteSize.S16x16, 0), 0, true)`. */
373
+ export namespace sprite {
374
+ export const HIDDEN: Sprite = <u64>OBJ_HIDDEN;
375
+
376
+ // @ts-ignore: decorator
377
+ @inline export function attr0(s: Sprite): u16 {
378
+ return <u16>s;
379
+ }
380
+ // @ts-ignore: decorator
381
+ @inline export function attr1(s: Sprite): u16 {
382
+ return <u16>(s >> 16);
383
+ }
384
+ // @ts-ignore: decorator
385
+ @inline export function attr2(s: Sprite): u16 {
386
+ return <u16>(s >> 32);
387
+ }
388
+ // @ts-ignore: decorator
389
+ @inline export function pack(a0: u16, a1: u16, a2: u16): Sprite {
390
+ return <u64>a0 | (<u64>a1 << 16) | (<u64>a2 << 32);
391
+ }
392
+
393
+ /** A sprite with its top-left at screen (x, y), using `tile` and bank 0. */
394
+ export function make(x: i32, y: i32, size: SpriteSize, tile: u16): Sprite {
395
+ const shape = <u16>(<i32>size >> 2), sz = <u16>(<i32>size & 3);
396
+ return pack(<u16>(y & 0xff) | (shape << 14), <u16>(x & 0x1ff) | (sz << 14), tile & 0x3ff);
397
+ }
398
+ export function bank(s: Sprite, b: u16): Sprite {
399
+ return pack(attr0(s), attr1(s), (attr2(s) & 0x0fff) | ((b & 0xf) << 12));
400
+ }
401
+ export function priority(s: Sprite, p: u16): Sprite {
402
+ return pack(attr0(s), attr1(s), (attr2(s) & ~0x0c00) | ((p & 3) << 10));
403
+ }
404
+ export function hflip(s: Sprite, on: bool): Sprite {
405
+ return pack(attr0(s), on ? attr1(s) | 0x1000 : attr1(s) & ~0x1000, attr2(s));
406
+ }
407
+ export function vflip(s: Sprite, on: bool): Sprite {
408
+ return pack(attr0(s), on ? attr1(s) | 0x2000 : attr1(s) & ~0x2000, attr2(s));
409
+ }
410
+ export function semiTransparent(s: Sprite): Sprite {
411
+ return pack((attr0(s) & ~0x0c00) | 0x0400, attr1(s), attr2(s));
412
+ }
413
+ export function window(s: Sprite): Sprite {
414
+ return pack((attr0(s) & ~0x0c00) | 0x0800, attr1(s), attr2(s));
415
+ }
416
+ export function mosaic(s: Sprite): Sprite {
417
+ return pack(attr0(s) | 0x1000, attr1(s), attr2(s));
418
+ }
419
+ export function bpp8(s: Sprite): Sprite {
420
+ return pack(attr0(s) | 0x2000, attr1(s), attr2(s));
421
+ }
422
+ /** Uses affine matrix `slot` (0–31). With `doubleSize` the bounding box
423
+ * doubles so rotated or enlarged pixels are not clipped. */
424
+ export function affine(s: Sprite, slot: u16, doubleSize: bool): Sprite {
425
+ const a0 = attr0(s) | 0x0100 | (doubleSize ? 0x0200 : 0);
426
+ const a1 = (attr1(s) & ~0x3e00) | ((slot & 31) << 9);
427
+ return pack(<u16>a0, a1, attr2(s));
428
+ }
429
+ }
430
+
431
+ /** An affine matrix in 8.8 fixed point, mapping screen to texture space,
432
+ * packed as pa | pb << 16 | pc << 32 | pd << 48. */
433
+ export type AffineMatrix = u64;
434
+
435
+ export namespace affine {
436
+ export const IDENTITY: AffineMatrix = 256 | (<u64>256 << 48);
437
+
438
+ // @ts-ignore: decorator
439
+ @inline export function make(pa: i16, pb: i16, pc: i16, pd: i16): AffineMatrix {
440
+ return <u64><u16>pa | (<u64><u16>pb << 16) | (<u64><u16>pc << 32) | (<u64><u16>pd << 48);
441
+ }
442
+ // @ts-ignore: decorator
443
+ @inline export function pa(m: AffineMatrix): i16 {
444
+ return <i16>m;
445
+ }
446
+ // @ts-ignore: decorator
447
+ @inline export function pb(m: AffineMatrix): i16 {
448
+ return <i16>(m >> 16);
449
+ }
450
+ // @ts-ignore: decorator
451
+ @inline export function pc(m: AffineMatrix): i16 {
452
+ return <i16>(m >> 32);
453
+ }
454
+ // @ts-ignore: decorator
455
+ @inline export function pd(m: AffineMatrix): i16 {
456
+ return <i16>(m >> 48);
457
+ }
458
+
459
+ function clamp16(v: i32): i16 {
460
+ return <i16>(v < -32768 ? -32768 : v > 32767 ? 32767 : v);
461
+ }
462
+
463
+ /** Rotates counter-clockwise by `angle` 1/256 turns and scales by (sx, sy)
464
+ * in 8.8 fixed point (256 = 1×, nonzero). Integer math, identical in
465
+ * every WASM-AD SDK. */
466
+ export function rotateScaleFixed(angle: i32, sx: i32, sy: i32): AffineMatrix {
467
+ const s = fixed.sin(angle), c = fixed.cos(angle);
468
+ return make(clamp16((c * 4) / sx), clamp16((-s * 4) / sx), clamp16((s * 4) / sy), clamp16((c * 4) / sy));
469
+ }
470
+ }
471
+
472
+ /** OAM: 128 sprites, plus affine matrices stored in the gaps. */
473
+ export namespace oam {
474
+ export function set(i: i32, s: Sprite): void {
475
+ const at = OAM + (<usize>i << 3);
476
+ store<u16>(at, sprite.attr0(s));
477
+ store<u16>(at + 2, sprite.attr1(s));
478
+ store<u16>(at + 4, sprite.attr2(s));
479
+ }
480
+ export function hide(i: i32): void {
481
+ set(i, sprite.HIDDEN);
482
+ }
483
+ export function hideAll(): void {
484
+ for (let i = 0; i < 128; i++) hide(i);
485
+ }
486
+ export function setAffine(slot: i32, m: AffineMatrix): void {
487
+ const at = OAM + <usize>slot * 32 + 6;
488
+ store<u16>(at, <u16>affine.pa(m));
489
+ store<u16>(at + 8, <u16>affine.pb(m));
490
+ store<u16>(at + 16, <u16>affine.pc(m));
491
+ store<u16>(at + 24, <u16>affine.pd(m));
492
+ }
493
+ }
494
+
495
+ /** Sets affine background `bg` (2 or 3) to matrix `m`, placed so that texture
496
+ * pixel (texX, texY) appears at screen pixel (scrX, scrY). Rotation and
497
+ * scaling then happen around that point. */
498
+ export function setBgAffine(bg: i32, m: AffineMatrix, texX: i32, texY: i32, scrX: i32, scrY: i32): void {
499
+ const at = reg.BG2PA + <usize>(bg - 2) * 0x10;
500
+ const pa = <i32>affine.pa(m), pb = <i32>affine.pb(m), pc = <i32>affine.pc(m), pd = <i32>affine.pd(m);
501
+ store<u16>(at, <u16>pa);
502
+ store<u16>(at + 2, <u16>pb);
503
+ store<u16>(at + 4, <u16>pc);
504
+ store<u16>(at + 6, <u16>pd);
505
+ store<u32>(at + 8, <u32>((texX << 8) - pa * scrX - pb * scrY));
506
+ store<u32>(at + 12, <u32>((texY << 8) - pc * scrX - pd * scrY));
507
+ }
508
+
509
+ // ---- Sound (SPEC.md §14) -------------------------------------------------------
510
+
511
+ /** Sound: four GBA-style channels written through `reg.SOUND*`, with
512
+ * builders for their fields, and setup:
513
+ *
514
+ * sound.enable();
515
+ * write16(reg.SOUND2CNT_L, sound.DUTY_50 | sound.envelope(12, 2, false));
516
+ * write16(reg.SOUND2CNT_H, sound.START | sound.rate(440));
517
+ */
518
+ export namespace sound {
519
+ /** Starts (or restarts) a channel; the host clears it. */
520
+ export const START: u16 = 1 << 15;
521
+ /** Stops the channel when its length runs out. */
522
+ export const LENGTH: u16 = 1 << 14;
523
+
524
+ export const DUTY_12: u16 = 0 << 6;
525
+ export const DUTY_25: u16 = 1 << 6;
526
+ export const DUTY_50: u16 = 2 << 6;
527
+ export const DUTY_75: u16 = 3 << 6;
528
+
529
+ /** `SOUND3CNT_L`: the wave channel is on. */
530
+ export const WAVE_ON: u16 = 1 << 7;
531
+ /** `SOUND3CNT_L`: play bank 1 (or start with it, with `WAVE_64`). */
532
+ export const WAVE_BANK1: u16 = 1 << 6;
533
+ /** `SOUND3CNT_L`: play both banks as one 64-sample wave. */
534
+ export const WAVE_64: u16 = 1 << 5;
535
+ /** `SOUND3CNT_H` volumes. */
536
+ export const WAVE_100: u16 = 1 << 13;
537
+ export const WAVE_50: u16 = 2 << 13;
538
+ export const WAVE_25: u16 = 3 << 13;
539
+ export const WAVE_75: u16 = 1 << 15;
540
+
541
+ /** Turns sound on with every channel on both sides at full volume. */
542
+ export function enable(): void {
543
+ store<u16>(reg.SOUNDCNT_X, 0x80);
544
+ store<u16>(reg.SOUNDCNT_L, 0xff77);
545
+ store<u16>(reg.SOUNDCNT_H, 0x330e);
546
+ }
547
+ /** Turns sound off; every channel stops. */
548
+ export function disable(): void {
549
+ store<u16>(reg.SOUNDCNT_X, 0);
550
+ }
551
+
552
+ /** The rate field for a square channel playing `hz` (64 Hz and up); the
553
+ * wave channel plays an octave lower at the same rate. */
554
+ // @ts-ignore: decorator
555
+ @inline export function rate(hz: u32): u16 {
556
+ return hz < 64 ? 0 : <u16>(2048 - 131072 / hz);
557
+ }
558
+ /** `SOUND1CNT_L`: every `time` 128ths of a second (1–7, 0 = off), the
559
+ * rate changes by rate >> `shift` (1–7), down if `decrease`. */
560
+ // @ts-ignore: decorator
561
+ @inline export function sweep(time: u16, shift: u16, decrease: bool): u16 {
562
+ return ((time & 7) << 4) | (shift & 7) | (decrease ? 8 : 0);
563
+ }
564
+ /** The envelope fields: start at `volume` (0–15), stepping by one every
565
+ * `step` 64ths of a second (0 = constant), up if `increase`. */
566
+ // @ts-ignore: decorator
567
+ @inline export function envelope(volume: u16, step: u16, increase: bool): u16 {
568
+ return ((volume & 15) << 12) | ((step & 7) << 8) | (increase ? 1 << 11 : 0);
569
+ }
570
+ /** The length field of channels 1, 2 and 4 for `n` 256ths of a second
571
+ * (1–64); it only counts with `LENGTH` set. Channel 3 takes 256 − n. */
572
+ // @ts-ignore: decorator
573
+ @inline export function length(n: u16): u16 {
574
+ return (64 - n) & 63;
575
+ }
576
+ /** `SOUND4CNT_H`: noise from a divider (0–7) and shift (0–13), with the
577
+ * 7-bit register if `short` (more tonal). */
578
+ // @ts-ignore: decorator
579
+ @inline export function noise(divider: u16, shift: u16, short: bool): u16 {
580
+ return (divider & 7) | ((shift & 15) << 4) | (short ? 8 : 0);
581
+ }
582
+
583
+ /** Address of wave RAM: two banks of 16 bytes, each 32 4-bit samples, high nibble first. */
584
+ // @ts-ignore: decorator
585
+ @inline export function waveRam(): usize {
586
+ return WAVE_RAM;
587
+ }
588
+ /** Address of the 28 KB of sample RAM (signed 8-bit) that the sample channels play from. */
589
+ // @ts-ignore: decorator
590
+ @inline export function sampleRam(): usize {
591
+ return SAMPLE_RAM;
592
+ }
593
+ /** Which channels are playing: bits 0–3 channels 1–4, bits 4 and 5 sample
594
+ * channels A and B, as of the end of the last frame. */
595
+ // @ts-ignore: decorator
596
+ @inline export function status(): u16 {
597
+ return load<u16>(reg.SOUNDCNT_X) & 0x3f;
598
+ }
599
+ }
600
+
601
+ /** The two sample channels, A (0) and B (1), playing signed 8-bit samples
602
+ * from sample RAM (SPEC.md §14.6). */
603
+ export namespace pcm {
604
+ // @ts-ignore: decorator
605
+ @inline function base(ch: i32): usize {
606
+ return ch == 0 ? reg.PCMA_SAD : reg.PCMB_SAD;
607
+ }
608
+
609
+ /** Plays `len` samples from sample RAM offset `start` at `rate` Hz and `volume` 0–64, once. */
610
+ export function play(ch: i32, start: u16, len: u16, rate: u16, volume: u16): void {
611
+ const b = base(ch);
612
+ store<u16>(b, start);
613
+ store<u16>(b + 2, len);
614
+ store<u16>(b + 6, rate);
615
+ store<u16>(b + 8, sound.START | (volume < 64 ? volume : 64));
616
+ }
617
+ /** Like `play`, then repeats from sample `loopAt` until stopped. */
618
+ export function playLooped(ch: i32, start: u16, len: u16, loopAt: u16, rate: u16, volume: u16): void {
619
+ store<u16>(base(ch) + 4, loopAt);
620
+ play(ch, start, len, rate, volume);
621
+ store<u16>(base(ch) + 8, load<u16>(base(ch) + 8) | (1 << 13));
622
+ }
623
+ export function stop(ch: i32): void {
624
+ store<u16>(base(ch) + 8, 1 << 14);
625
+ }
626
+ /** The playback position, in samples from the start, as of the end of the last frame. */
627
+ export function position(ch: i32): u16 {
628
+ return load<u16>(base(ch) + 10);
629
+ }
630
+ export function playing(ch: i32): bool {
631
+ return (sound.status() & (1 << (4 + ch))) != 0;
632
+ }
633
+ }
634
+
635
+ // ---- Fixed-point trigonometry --------------------------------------------------
636
+
637
+ /** Fixed-point trigonometry shared by all WASM-AD SDKs, so carts in any
638
+ * language compute bit-identical results (SPEC.md §12). */
639
+ export namespace fixed {
640
+ /** sin over a quarter turn in 1/256-turn steps, scaled by 16384 (Q14). */
641
+ export const SIN_Q14: usize = memory.data<i32>([
642
+ 0, 402, 804, 1205, 1606, 2006, 2404, 2801, 3196, 3590, 3981, 4370, 4756, 5139, 5520, 5897, 6270, 6639, 7005,
643
+ 7366, 7723, 8076, 8423, 8765, 9102, 9434, 9760, 10080, 10394, 10702, 11003, 11297, 11585, 11866, 12140, 12406,
644
+ 12665, 12916, 13160, 13395, 13623, 13842, 14053, 14256, 14449, 14635, 14811, 14978, 15137, 15286, 15426, 15557,
645
+ 15679, 15791, 15893, 15986, 16069, 16143, 16207, 16261, 16305, 16340, 16364, 16379, 16384,
646
+ ]);
647
+
648
+ // @ts-ignore: decorator
649
+ @inline function q(i: i32): i32 {
650
+ return load<i32>(SIN_Q14 + (<usize>i << 2));
651
+ }
652
+
653
+ /** sin of `angle` 1/256 turns (only the low 8 bits count), in Q14. */
654
+ export function sin(angle: i32): i32 {
655
+ const a = angle & 255;
656
+ if (a < 64) return q(a);
657
+ if (a < 128) return q(128 - a);
658
+ if (a < 192) return -q(a - 128);
659
+ return -q(256 - a);
660
+ }
661
+
662
+ /** cos of `angle` 1/256 turns, in Q14. */
663
+ export function cos(angle: i32): i32 {
664
+ return sin(angle + 64);
665
+ }
666
+ }
667
+
668
+ // ---- Input, time, save RAM -------------------------------------------------------
669
+
670
+ /** Buttons held this frame. */
671
+ // @ts-ignore: decorator
672
+ @inline export function keys(): u16 {
673
+ return load<u16>(reg.KEYS);
674
+ }
675
+
676
+ /** Edge detection over `KEYS`: call `buttons.update()` at the start of every
677
+ * frame. Its two words of state live in linear memory. */
678
+ export namespace buttons {
679
+ const STATE: usize = memory.data(4); // held: u16, prev: u16
680
+
681
+ export function update(): void {
682
+ store<u16>(STATE + 2, load<u16>(STATE));
683
+ store<u16>(STATE, keys());
684
+ }
685
+ export function held(k: u16): bool {
686
+ return (load<u16>(STATE) & k) != 0;
687
+ }
688
+ /** True only on the frame the button went down. */
689
+ export function pressed(k: u16): bool {
690
+ return (load<u16>(STATE) & ~load<u16>(STATE + 2) & k) != 0;
691
+ }
692
+ }
693
+
694
+ /** Frames since boot. */
695
+ // @ts-ignore: decorator
696
+ @inline export function frame(): u32 {
697
+ return load<u32>(reg.FRAME);
698
+ }
699
+
700
+ /** The boot-time random seed, the cart's only entropy (SPEC.md §12). */
701
+ // @ts-ignore: decorator
702
+ @inline export function seed(): u32 {
703
+ return load<u32>(reg.SEED);
704
+ }
705
+
706
+ /** Address of the 32 KB battery-backed save RAM. */
707
+ // @ts-ignore: decorator
708
+ @inline export function saveRam(): usize {
709
+ return SAVE;
710
+ }
711
+
712
+ /** Whether the host restored save RAM from storage at boot. */
713
+ // @ts-ignore: decorator
714
+ @inline export function saveRestored(): bool {
715
+ return (load<u16>(reg.SAVESTAT) & 1) != 0;
716
+ }
717
+
718
+ /** Asks the host to persist save RAM after this frame. */
719
+ // @ts-ignore: decorator
720
+ @inline export function requestSave(): void {
721
+ store<u16>(reg.SAVECNT, load<u16>(reg.SAVECNT) | 1);
722
+ }
723
+
724
+ // ---- Debug log -------------------------------------------------------------------
725
+
726
+ // @ts-ignore: decorator
727
+ @external("env", "trace")
728
+ declare function hostTrace(ptr: usize, len: i32): void;
729
+
730
+ /** Writes a message to the host's debug log, as UTF-8 (at most 1 KB). */
731
+ export function trace(msg: string): void {
732
+ const buf = memory.data(1024);
733
+ let n: usize = 0;
734
+ const len = msg.length;
735
+ for (let i = 0; i < len; i++) {
736
+ let c = <u32>msg.charCodeAt(i);
737
+ if (c >= 0xd800 && c < 0xdc00 && i + 1 < len) {
738
+ const lo = <u32>msg.charCodeAt(i + 1);
739
+ if (lo >= 0xdc00 && lo < 0xe000) {
740
+ c = 0x10000 + ((c - 0xd800) << 10) + (lo - 0xdc00);
741
+ i++;
742
+ }
743
+ }
744
+ const need: usize = c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
745
+ if (n + need > 1024) break;
746
+ if (need == 1) store<u8>(buf + n, c);
747
+ else if (need == 2) {
748
+ store<u8>(buf + n, 0xc0 | (c >> 6));
749
+ store<u8>(buf + n + 1, 0x80 | (c & 63));
750
+ } else if (need == 3) {
751
+ store<u8>(buf + n, 0xe0 | (c >> 12));
752
+ store<u8>(buf + n + 1, 0x80 | ((c >> 6) & 63));
753
+ store<u8>(buf + n + 2, 0x80 | (c & 63));
754
+ } else {
755
+ store<u8>(buf + n, 0xf0 | (c >> 18));
756
+ store<u8>(buf + n + 1, 0x80 | ((c >> 12) & 63));
757
+ store<u8>(buf + n + 2, 0x80 | ((c >> 6) & 63));
758
+ store<u8>(buf + n + 3, 0x80 | (c & 63));
759
+ }
760
+ n += need;
761
+ }
762
+ hostTrace(buf, <i32>n);
763
+ }
764
+
765
+ /** Replaces AssemblyScript's `env.abort` import (`--use abort=...` in asconfig.json):
766
+ * a failed assertion or bounds check traps. */
767
+ export function abort(message: string | null, file: string | null, line: u32, column: u32): void {
768
+ unreachable();
769
+ }