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 +11 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/presentation.d.ts +75 -0
- package/dist/presentation.d.ts.map +1 -0
- package/dist/presentation.js +78 -0
- package/dist/presentation.js.map +1 -0
- package/package.json +1 -1
- package/dist/__tests__/collision.test.d.ts +0 -25
- package/dist/__tests__/collision.test.d.ts.map +0 -1
- package/dist/__tests__/collision.test.js +0 -198
- package/dist/__tests__/collision.test.js.map +0 -1
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
|
|
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
|
-
**
|
|
59
|
+
**Want the real thing?** Three opt-in rendering paths cover the spectrum from fantasy to faithful:
|
|
60
60
|
|
|
61
|
-
|
|
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.
|
|
236
|
+
"zx-kit": "^0.31.1"
|
|
231
237
|
},
|
|
232
238
|
"devDependencies": {
|
|
233
239
|
"vite": "^6.0.0"
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -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
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,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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"}
|