zx-kit 0.31.2 → 0.32.1

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.
package/README.md CHANGED
@@ -42,9 +42,9 @@ zx-kit captures that aesthetic in TypeScript. You get the Spectrum's palette, RO
42
42
 
43
43
  ---
44
44
 
45
- ## Spectrum-inspired, not hardware-accurate
45
+ ## Spectrum-inspired, not hardware-accurate by default
46
46
 
47
- zx-kit is not a ZX Spectrum emulator. It does not model the hardware attribute clash (where every 8×8 cell can hold only one ink/paper pair), the ULA timing, or the Z80 memory layout.
47
+ zx-kit is not a ZX Spectrum emulator, and the **default** renderer does not model the hardware attribute clash (where every 8×8 cell can hold only one ink/paper pair), the ULA timing, or the Z80 memory layout. The default path composites in full colour, so sprites keep their own colours and never bleed into the background.
48
48
 
49
49
  What it does model is the **aesthetic discipline** of the Spectrum:
50
50
 
@@ -56,9 +56,15 @@ What it does model is the **aesthetic discipline** of the Spectrum:
56
56
  - Beeper-style 1-bit SFX
57
57
  - AY-3-8912-style three-channel chiptune audio
58
58
 
59
- **What this means in practice:** sprites drawn with `drawBitmapAttrs()` keep their own per-cell colors. A white hero walking past a green plant won't turn green — both objects render independently. This is a deliberate choice that makes zx-kit closer to a *Speccy-flavoured fantasy console* than to the original machine.
59
+ **Want the real thing?** Three opt-in rendering paths cover the spectrum from fantasy to faithful:
60
60
 
61
- If you want a stricter hardware-inspired look, use `drawBitmapAttrs()` with paper fills — see the [renderer docs](#rendererts--canvas-renderer) for per-cell attribute styling patterns.
61
+ | Path | Module | Look |
62
+ |------|--------|------|
63
+ | **Fantasy** (default) | `renderer` | Full-colour compositing — sprites keep their colours, no bleed. Best for readability. |
64
+ | **Authentic clash** | `attrscreen` | 1-bit pixels + a 32×24 ink/paper grid: real per-cell colour bleed when a sprite and the background share an 8×8 cell. |
65
+ | **Anti-clash** | `monoscreen` | One ink/paper for the whole playfield — clash-proof monochrome action, with a colourful HUD around it. |
66
+
67
+ A white hero walking past a green plant stays white under the **default** renderer, bleeds the shared cell under **`attrscreen`**, and is a clean silhouette under **`monoscreen`** — your choice, per game or per in-game toggle.
62
68
 
63
69
  ---
64
70
 
@@ -227,7 +233,7 @@ Open `package.json` and replace it with the following. The two key additions are
227
233
  "build": "vite build"
228
234
  },
229
235
  "dependencies": {
230
- "zx-kit": "^0.25.0"
236
+ "zx-kit": "^0.31.1"
231
237
  },
232
238
  "devDependencies": {
233
239
  "vite": "^6.0.0"
package/dist/index.d.ts CHANGED
@@ -21,4 +21,5 @@ export * from './cache.js';
21
21
  export * from './attrscreen.js';
22
22
  export * from './monoscreen.js';
23
23
  export * from './music.js';
24
+ export * from './presentation.js';
24
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -21,4 +21,5 @@ export * from './cache.js';
21
21
  export * from './attrscreen.js';
22
22
  export * from './monoscreen.js';
23
23
  export * from './music.js';
24
+ export * from './presentation.js';
24
25
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @module presentation
3
+ *
4
+ * Small **8-bit title / loading-screen helpers** — the iconic ZX "PRESS ANY KEY"
5
+ * blink, tape-loading border stripes, and a title menu. These are tiny, composable
6
+ * functions over the renderer + palette (in the spirit of {@link "renderer" | drawScanlines}
7
+ * and {@link "renderer" | drawText}), **not** a framework: a game wires its own title
8
+ * or loading screen from them.
9
+ *
10
+ * Everything draws in **game pixels** (rely on the `setupCanvas` scale), so these
11
+ * compose freely with `drawText`, sprites, and tiles.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // A loading screen, composed from the pieces (no built-in "title screen"):
16
+ * drawTapeStripes(ctx, now, { side: 'border' })
17
+ * drawTextCentered(ctx, 'LOADING CHAOSBUNNY', 80, 32, C.B_WHITE)
18
+ * drawBlinkingText(ctx, 'PRESS ANY KEY', 96, 120, now, C.B_YELLOW)
19
+ * ```
20
+ */
21
+ import { type SpectrumColor } from './palette.js';
22
+ /**
23
+ * `true` for the lit half of each blink cycle — a pure, time-driven toggle (no
24
+ * state). Gate any flashing element with it.
25
+ *
26
+ * @example
27
+ * if (blinkVisible(now)) drawText(ctx, 'INSERT COIN', x, y, C.B_WHITE)
28
+ */
29
+ export declare function blinkVisible(now: number, intervalMs?: number): boolean;
30
+ /**
31
+ * Draws `text` only on the visible half of the blink cycle — the classic flashing
32
+ * "PRESS ANY KEY" / "PRESS FIRE" prompt. Same args as {@link drawText} plus `now`.
33
+ */
34
+ export declare function drawBlinkingText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, now: number, ink?: SpectrumColor, paper?: SpectrumColor, intervalMs?: number): void;
35
+ /** Options for {@link drawTapeStripes}. */
36
+ export interface TapeStripesOptions {
37
+ /** Colours cycled down the screen. Default: the classic loader red / cyan. */
38
+ colors?: readonly SpectrumColor[];
39
+ /** Stripe thickness in game px (default `2`). */
40
+ stripeHeight?: number;
41
+ /** Scroll speed in px/second (default `24`) — animates the loading shimmer. */
42
+ speed?: number;
43
+ /** `'full'` stripes the whole screen; `'border'` stripes only a frame. Default `'full'`. */
44
+ side?: 'full' | 'border';
45
+ /** Border band thickness in px when `side: 'border'` (default `CELL`). */
46
+ band?: number;
47
+ /** Screen size in game px (default 256×192 — the ZX canvas). */
48
+ width?: number;
49
+ height?: number;
50
+ }
51
+ /**
52
+ * Draws animated ZX **tape-loading stripes** — the shimmering coloured bands of a
53
+ * cassette load. `'full'` paints the whole screen; `'border'` paints only a frame
54
+ * (leaving the middle for your title art). Call before your title/logo each frame.
55
+ */
56
+ export declare function drawTapeStripes(ctx: CanvasRenderingContext2D, now: number, options?: TapeStripesOptions): void;
57
+ /** Options for {@link drawMenuOptions}. */
58
+ export interface MenuOptionsConfig {
59
+ /** Ink for unselected options (default `C.WHITE`). */
60
+ ink?: SpectrumColor;
61
+ /** Ink for the selected option (default `C.B_YELLOW`). */
62
+ selectedInk?: SpectrumColor;
63
+ /** Optional paper behind each line. */
64
+ paper?: SpectrumColor;
65
+ /** Extra px between rows on top of the 8px line height (default `2`). */
66
+ gap?: number;
67
+ /** Marker before the selected option; others are padded to align (default `'> '`). */
68
+ prefix?: string;
69
+ }
70
+ /**
71
+ * Draws a vertical **title menu** at `(x, y)`. The selected option is highlighted
72
+ * with `selectedInk` and the `prefix` marker; the rest are padded to stay aligned.
73
+ */
74
+ export declare function drawMenuOptions(ctx: CanvasRenderingContext2D, options: readonly string[], selectedIndex: number, x: number, y: number, config?: MenuOptionsConfig): void;
75
+ //# sourceMappingURL=presentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation.d.ts","sourceRoot":"","sources":["../src/presentation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAW,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAG1D;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,SAAM,GAAG,OAAO,CAEnE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,aAAyB,EAC9B,KAAK,CAAC,EAAE,aAAa,EACrB,UAAU,SAAM,GACf,IAAI,CAEN;AAED,2CAA2C;AAC3C,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;IACjC,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4FAA4F;IAC5F,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;IACxB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,wBAAwB,EAC7B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,IAAI,CAwBN;AAED,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,GAAG,CAAC,EAAE,aAAa,CAAA;IACnB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,aAAa,CAAA;IAC3B,uCAAuC;IACvC,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,wBAAwB,EAC7B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,aAAa,EAAE,MAAM,EACrB,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EACpB,MAAM,GAAE,iBAAsB,GAC7B,IAAI,CAQN"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @module presentation
3
+ *
4
+ * Small **8-bit title / loading-screen helpers** — the iconic ZX "PRESS ANY KEY"
5
+ * blink, tape-loading border stripes, and a title menu. These are tiny, composable
6
+ * functions over the renderer + palette (in the spirit of {@link "renderer" | drawScanlines}
7
+ * and {@link "renderer" | drawText}), **not** a framework: a game wires its own title
8
+ * or loading screen from them.
9
+ *
10
+ * Everything draws in **game pixels** (rely on the `setupCanvas` scale), so these
11
+ * compose freely with `drawText`, sprites, and tiles.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // A loading screen, composed from the pieces (no built-in "title screen"):
16
+ * drawTapeStripes(ctx, now, { side: 'border' })
17
+ * drawTextCentered(ctx, 'LOADING CHAOSBUNNY', 80, 32, C.B_WHITE)
18
+ * drawBlinkingText(ctx, 'PRESS ANY KEY', 96, 120, now, C.B_YELLOW)
19
+ * ```
20
+ */
21
+ import { C, CELL } from './palette.js';
22
+ import { drawText } from './renderer.js';
23
+ /**
24
+ * `true` for the lit half of each blink cycle — a pure, time-driven toggle (no
25
+ * state). Gate any flashing element with it.
26
+ *
27
+ * @example
28
+ * if (blinkVisible(now)) drawText(ctx, 'INSERT COIN', x, y, C.B_WHITE)
29
+ */
30
+ export function blinkVisible(now, intervalMs = 500) {
31
+ return Math.floor(now / intervalMs) % 2 === 0;
32
+ }
33
+ /**
34
+ * Draws `text` only on the visible half of the blink cycle — the classic flashing
35
+ * "PRESS ANY KEY" / "PRESS FIRE" prompt. Same args as {@link drawText} plus `now`.
36
+ */
37
+ export function drawBlinkingText(ctx, text, x, y, now, ink = C.B_WHITE, paper, intervalMs = 500) {
38
+ if (blinkVisible(now, intervalMs))
39
+ drawText(ctx, text, x, y, ink, paper);
40
+ }
41
+ /**
42
+ * Draws animated ZX **tape-loading stripes** — the shimmering coloured bands of a
43
+ * cassette load. `'full'` paints the whole screen; `'border'` paints only a frame
44
+ * (leaving the middle for your title art). Call before your title/logo each frame.
45
+ */
46
+ export function drawTapeStripes(ctx, now, options = {}) {
47
+ const { colors = [C.B_RED, C.B_CYAN], stripeHeight = 2, speed = 24, side = 'full', band = CELL, width = 256, height = 192, } = options;
48
+ const n = colors.length;
49
+ if (n === 0 || stripeHeight <= 0)
50
+ return;
51
+ const offset = Math.floor((now / 1000) * speed);
52
+ for (let y = 0; y < height; y += stripeHeight) {
53
+ const i = Math.floor((y - offset) / stripeHeight);
54
+ ctx.fillStyle = colors[((i % n) + n) % n];
55
+ const h = Math.min(stripeHeight, height - y);
56
+ if (side === 'full' || y < band || y >= height - band) {
57
+ ctx.fillRect(0, y, width, h); // full row (whole screen, or top/bottom band)
58
+ }
59
+ else {
60
+ ctx.fillRect(0, y, band, h); // left band
61
+ ctx.fillRect(width - band, y, band, h); // right band
62
+ }
63
+ }
64
+ }
65
+ /**
66
+ * Draws a vertical **title menu** at `(x, y)`. The selected option is highlighted
67
+ * with `selectedInk` and the `prefix` marker; the rest are padded to stay aligned.
68
+ */
69
+ export function drawMenuOptions(ctx, options, selectedIndex, x, y, config = {}) {
70
+ const { ink = C.WHITE, selectedInk = C.B_YELLOW, paper, gap = 2, prefix = '> ' } = config;
71
+ const pad = ' '.repeat(prefix.length);
72
+ const lineH = CELL + gap;
73
+ for (let i = 0; i < options.length; i++) {
74
+ const selected = i === selectedIndex;
75
+ drawText(ctx, (selected ? prefix : pad) + options[i], x, y + i * lineH, selected ? selectedInk : ink, paper);
76
+ }
77
+ }
78
+ //# sourceMappingURL=presentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation.js","sourceRoot":"","sources":["../src/presentation.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zx-kit",
3
- "version": "0.31.2",
3
+ "version": "0.32.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/zrebec/zx-kit.git"
@@ -1,25 +0,0 @@
1
- /**
2
- * Pixel-perfect collision — visual ground truth test.
3
- *
4
- * Two bitmaps: 8×8 px each, but only a 2×2 px region is lit
5
- * (rows 0–1, cols 0–1). The effective sprite shape is 2×2 — a size that
6
- * is intentionally NOT a multiple of the ZX tile size (8 px).
7
- *
8
- * col: 01234567
9
- * row 0: ██...... (0xC0)
10
- * row 1: ██...... (0xC0)
11
- * rows 2–7: empty
12
- *
13
- * Ground truth: a brute-force Set-of-pixel-coords intersection.
14
- * Both sprites are projected to absolute (x,y) coordinates, all lit pixels
15
- * are collected into a flat Set, and any shared coordinate counts as overlap.
16
- * No formulas, no assumptions — just "which pixels are painted and do any
17
- * coordinates match?"
18
- *
19
- * Every assertion compares masksOverlap against this ground truth, sweeping
20
- * sprite B toward sprite A one pixel at a time in all four directions and
21
- * diagonally. A separate block shows that AABB (8×8 bounding-box) fires
22
- * 6 pixels earlier than pixel-perfect — the gap the ground truth exposes.
23
- */
24
- export {};
25
- //# sourceMappingURL=collision.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"collision.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/collision.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG"}
@@ -1,198 +0,0 @@
1
- /**
2
- * Pixel-perfect collision — visual ground truth test.
3
- *
4
- * Two bitmaps: 8×8 px each, but only a 2×2 px region is lit
5
- * (rows 0–1, cols 0–1). The effective sprite shape is 2×2 — a size that
6
- * is intentionally NOT a multiple of the ZX tile size (8 px).
7
- *
8
- * col: 01234567
9
- * row 0: ██...... (0xC0)
10
- * row 1: ██...... (0xC0)
11
- * rows 2–7: empty
12
- *
13
- * Ground truth: a brute-force Set-of-pixel-coords intersection.
14
- * Both sprites are projected to absolute (x,y) coordinates, all lit pixels
15
- * are collected into a flat Set, and any shared coordinate counts as overlap.
16
- * No formulas, no assumptions — just "which pixels are painted and do any
17
- * coordinates match?"
18
- *
19
- * Every assertion compares masksOverlap against this ground truth, sweeping
20
- * sprite B toward sprite A one pixel at a time in all four directions and
21
- * diagonally. A separate block shows that AABB (8×8 bounding-box) fires
22
- * 6 pixels earlier than pixel-perfect — the gap the ground truth exposes.
23
- */
24
- import { describe, it, expect } from 'vitest';
25
- import { bitmapPixelMask, masksOverlap, rectsOverlap } from '../collision.js';
26
- import { createBitmap } from '../renderer.js';
27
- // ── Fixture ───────────────────────────────────────────────────────────────────
28
- // width=8 (minimum for Bitmap: 1 byte per row). height=8.
29
- // Lit region: rows 0–1, cols 0–1. All other bytes 0x00.
30
- const BMP = createBitmap(new Uint8Array([
31
- 0xC0, // row 0: bits 7,6 set → cols 0,1
32
- 0xC0, // row 1: bits 7,6 set → cols 0,1
33
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rows 2–7: empty
34
- ]), 8, 8);
35
- const MASK = bitmapPixelMask(BMP);
36
- // ── Brute-force ground truth ──────────────────────────────────────────────────
37
- // Project every lit pixel of a mask to world (x,y). Put them in a Set.
38
- // Check if any pixel of B appears in the Set of A. Completely explicit —
39
- // no clever algorithm, just coordinate comparison.
40
- function litPixels(mask, ox, oy) {
41
- const s = new Set();
42
- for (let row = 0; row < mask.height; row++) {
43
- for (const col of mask.rows[row]) {
44
- s.add(`${ox + col},${oy + row}`);
45
- }
46
- }
47
- return s;
48
- }
49
- function bruteForce(maskA, ax, ay, maskB, bx, by) {
50
- const setA = litPixels(maskA, ax, ay);
51
- for (let row = 0; row < maskB.height; row++) {
52
- for (const col of maskB.rows[row]) {
53
- if (setA.has(`${bx + col},${by + row}`))
54
- return true;
55
- }
56
- }
57
- return false;
58
- }
59
- // ─────────────────────────────────────────────────────────────────────────────
60
- describe('masksOverlap — 2×2 lit region, visual ground truth', () => {
61
- // ── Sanity-check the mask ─────────────────────────────────────────────────
62
- describe('mask shape', () => {
63
- it('has exactly 4 lit pixels (the 2×2 region)', () => {
64
- expect(MASK.totalPixels).toBe(4);
65
- });
66
- it('rows 0–1 contain columns [0, 1]; rows 2–7 are empty', () => {
67
- expect(MASK.rows[0]).toEqual([0, 1]);
68
- expect(MASK.rows[1]).toEqual([0, 1]);
69
- for (let r = 2; r < MASK.height; r++) {
70
- expect(MASK.rows[r]).toEqual([]);
71
- }
72
- });
73
- });
74
- // ── Horizontal sweep ──────────────────────────────────────────────────────
75
- // A is fixed at (20,20). B sweeps from right (x=30) to left (x=17), same y.
76
- // A's lit pixels: x∈[20,21], y∈[20,21].
77
- // First contact: bx=21 — B column 0 (x=21) meets A column 1 (x=21).
78
- // AABB first contact: bx=27 — 8×8 boxes share one column. Gap = 6 px.
79
- describe('horizontal sweep: B approaches A from the right', () => {
80
- const AX = 20, AY = 20;
81
- it('masksOverlap matches brute-force at every pixel step', () => {
82
- for (let bx = AX + 10; bx >= AX - 3; bx--) {
83
- const expected = bruteForce(MASK, AX, AY, MASK, bx, AY);
84
- const actual = masksOverlap(MASK, AX, AY, MASK, bx, AY) > 0;
85
- if (actual !== expected) {
86
- throw new Error(`bx=${bx}: masksOverlap=${actual} but brute-force says ${expected}`);
87
- }
88
- }
89
- });
90
- it('no collision at bx = AX+2 — 1-pixel gap between lit edges', () => {
91
- // A's rightmost lit column is AX+1=21. B's leftmost at bx=AX+2=22. Gap 1 px.
92
- expect(masksOverlap(MASK, AX, AY, MASK, AX + 2, AY)).toBe(0);
93
- expect(bruteForce(MASK, AX, AY, MASK, AX + 2, AY)).toBe(false);
94
- });
95
- it('collision fires at bx = AX+1 — lit edges share column AX+1', () => {
96
- expect(masksOverlap(MASK, AX, AY, MASK, AX + 1, AY)).toBeGreaterThan(0);
97
- expect(bruteForce(MASK, AX, AY, MASK, AX + 1, AY)).toBe(true);
98
- });
99
- it('AABB (8×8 box) fires 6 pixels before pixel-perfect', () => {
100
- const boxA = { x: AX, y: AY, w: 8, h: 8 };
101
- // AABB says: first overlap at bx=AX+7 (right edge of A box = left edge of B box)
102
- expect(rectsOverlap(boxA, { x: AX + 8, y: AY, w: 8, h: 8 })).toBe(false);
103
- expect(rectsOverlap(boxA, { x: AX + 7, y: AY, w: 8, h: 8 })).toBe(true);
104
- // Pixel-perfect: no lit pixels near each other at bx=AX+7 — false positive
105
- expect(masksOverlap(MASK, AX, AY, MASK, AX + 7, AY)).toBe(0);
106
- expect(bruteForce(MASK, AX, AY, MASK, AX + 7, AY)).toBe(false);
107
- });
108
- });
109
- // ── Vertical sweep ────────────────────────────────────────────────────────
110
- // A fixed at (20,20). B sweeps from below (y=30) upward (y=17), same x.
111
- // First contact: by=21 — B row 0 (y=21) meets A row 1 (y=21).
112
- // AABB first contact: by=27. Gap = 6 px.
113
- describe('vertical sweep: B approaches A from below', () => {
114
- const AX = 20, AY = 20;
115
- it('masksOverlap matches brute-force at every pixel step', () => {
116
- for (let by = AY + 10; by >= AY - 3; by--) {
117
- const expected = bruteForce(MASK, AX, AY, MASK, AX, by);
118
- const actual = masksOverlap(MASK, AX, AY, MASK, AX, by) > 0;
119
- if (actual !== expected) {
120
- throw new Error(`by=${by}: masksOverlap=${actual} but brute-force says ${expected}`);
121
- }
122
- }
123
- });
124
- it('no collision at by = AY+2 — 1-pixel gap between lit rows', () => {
125
- expect(masksOverlap(MASK, AX, AY, MASK, AX, AY + 2)).toBe(0);
126
- expect(bruteForce(MASK, AX, AY, MASK, AX, AY + 2)).toBe(false);
127
- });
128
- it('collision fires at by = AY+1 — lit rows share row AY+1', () => {
129
- expect(masksOverlap(MASK, AX, AY, MASK, AX, AY + 1)).toBeGreaterThan(0);
130
- expect(bruteForce(MASK, AX, AY, MASK, AX, AY + 1)).toBe(true);
131
- });
132
- it('AABB fires 6 pixels before pixel-perfect', () => {
133
- const boxA = { x: AX, y: AY, w: 8, h: 8 };
134
- expect(rectsOverlap(boxA, { x: AX, y: AY + 8, w: 8, h: 8 })).toBe(false);
135
- expect(rectsOverlap(boxA, { x: AX, y: AY + 7, w: 8, h: 8 })).toBe(true);
136
- expect(masksOverlap(MASK, AX, AY, MASK, AX, AY + 7)).toBe(0);
137
- expect(bruteForce(MASK, AX, AY, MASK, AX, AY + 7)).toBe(false);
138
- });
139
- });
140
- // ── Diagonal sweep ────────────────────────────────────────────────────────
141
- // A fixed at (20,20). B sweeps along the main diagonal: bx=by=AX+d, d=10→-3.
142
- // First contact: d=1 — corner pixel (AX+1, AY+1) is shared by both sprites.
143
- // At d=2: B's nearest pixel is (AX+2, AY+2) — corner of A is (AX+1, AY+1). Gap.
144
- describe('diagonal sweep: B approaches A from bottom-right', () => {
145
- const AX = 20, AY = 20;
146
- it('masksOverlap matches brute-force at every diagonal step', () => {
147
- for (let d = 10; d >= -3; d--) {
148
- const bx = AX + d, by = AY + d;
149
- const expected = bruteForce(MASK, AX, AY, MASK, bx, by);
150
- const actual = masksOverlap(MASK, AX, AY, MASK, bx, by) > 0;
151
- if (actual !== expected) {
152
- throw new Error(`d=${d} (bx=${bx}, by=${by}): masksOverlap=${actual} but brute-force says ${expected}`);
153
- }
154
- }
155
- });
156
- it('no collision at d=2 — corner pixels are 1 diagonal step apart', () => {
157
- expect(masksOverlap(MASK, AX, AY, MASK, AX + 2, AY + 2)).toBe(0);
158
- expect(bruteForce(MASK, AX, AY, MASK, AX + 2, AY + 2)).toBe(false);
159
- });
160
- it('collision fires at d=1 — single corner pixel (AX+1, AY+1) coincides', () => {
161
- expect(masksOverlap(MASK, AX, AY, MASK, AX + 1, AY + 1)).toBeGreaterThan(0);
162
- expect(bruteForce(MASK, AX, AY, MASK, AX + 1, AY + 1)).toBe(true);
163
- });
164
- });
165
- // ── Overlap count ─────────────────────────────────────────────────────────
166
- // masksOverlap returns a count, not just a boolean. Verify it grows as the
167
- // sprites move deeper into each other.
168
- describe('overlap pixel count increases with penetration depth', () => {
169
- const AX = 20, AY = 20;
170
- it('1 overlapping pixel at first horizontal contact (bx = AX+1)', () => {
171
- // Only column AX+1 is shared (both rows): 2 pixels
172
- expect(masksOverlap(MASK, AX, AY, MASK, AX + 1, AY)).toBe(2);
173
- });
174
- it('4 overlapping pixels when fully aligned (bx = AX)', () => {
175
- // Complete overlap of the 2×2 regions
176
- expect(masksOverlap(MASK, AX, AY, MASK, AX, AY)).toBe(4);
177
- });
178
- it('single corner pixel at diagonal first contact (d=1)', () => {
179
- // Only (AX+1, AY+1) is shared
180
- expect(masksOverlap(MASK, AX, AY, MASK, AX + 1, AY + 1)).toBe(1);
181
- });
182
- });
183
- // ── Symmetry ──────────────────────────────────────────────────────────────
184
- // masksOverlap(A, B) must equal masksOverlap(B, A) — no directional bias.
185
- describe('symmetry: collision count is order-independent', () => {
186
- it('A→B count equals B→A count for all horizontal positions', () => {
187
- const AX = 20, AY = 20;
188
- for (let bx = AX + 5; bx >= AX - 3; bx--) {
189
- const ab = masksOverlap(MASK, AX, AY, MASK, bx, AY);
190
- const ba = masksOverlap(MASK, bx, AY, MASK, AX, AY);
191
- if (ab !== ba) {
192
- throw new Error(`Asymmetry at bx=${bx}: A→B=${ab}, B→A=${ba}`);
193
- }
194
- }
195
- });
196
- });
197
- });
198
- //# sourceMappingURL=collision.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"collision.test.js","sourceRoot":"","sources":["../../src/__tests__/collision.test.ts"],"names":[],"mappings":"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"}