zx-kit 0.28.0 → 0.30.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.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > **A Speccy-flavoured fantasy toolkit for tiny TypeScript browser games.**
4
4
  > Inspired by the ZX Spectrum — not an emulator, not a hardware clone.
5
5
 
6
- Spectrum-palette canvas rendering. ROM bitmap font. AY-3-8912 three-channel audio. Beeper SFX. Tile maps. Free-roaming sprites. Collision detection. Saves. Camera. Scene manager. Particle pool. Dithered lighting. Zero dependencies. TypeScript-first.
6
+ Spectrum-palette canvas rendering. ROM bitmap font. AY-3-8912 three-channel audio. Beeper SFX. Tile maps. Free-roaming sprites. Collision detection. Saves. Camera. Scene manager. Particle pool. Dithered lighting. Offscreen layer cache. Authentic attribute clash. Zero dependencies. TypeScript-first.
7
7
 
8
8
  [![npm](https://img.shields.io/npm/v/zx-kit)](https://www.npmjs.com/package/zx-kit)
9
9
  [![license](https://img.shields.io/npm/l/zx-kit)](LICENSE)
@@ -27,6 +27,8 @@ zx-kit captures that aesthetic in TypeScript. You get the Spectrum's palette, RO
27
27
  - **Authentic 15-color palette** — normal and bright variants, palette-enforced at compile time via the `SpectrumColor` type
28
28
  - **Canvas renderer** — pixel-perfect scaled rendering, sprite flipping, text drawing, CRT scanline overlay, animated border flashing
29
29
  - **Tile map engine** — scrollable maps, O(1) id-index, smart seasonal background swapping, solid-tile collision queries
30
+ - **Offscreen layer cache** — render a static or rarely-changing layer (tile map, CRT overlay) once to an offscreen canvas and blit it each frame; `dirty`-flag invalidation turns thousands of per-pixel `fillRect`s into a single `drawImage`
31
+ - **Authentic attribute clash (opt-in)** — a 32×24 cell ink/paper screen that reproduces the real Spectrum colour bleed when a sprite and the background share an 8×8 cell; resolved to one `putImageData`/frame. Off by default, on when you want it
30
32
  - **Free-roaming sprites** — position, velocity, gravity, `flipX` caching, transparent or opaque background
31
33
  - **Three-tier collision** — AABB overlap tests, generic rect-vs-tile wall resolution (any sprite size), and pixel-precise mask overlap with O(pixels) sorted-merge intersection — no allocations per frame
32
34
  - **Keyboard and gamepad input** — configurable key-repeat, transparent gamepad polling, single-consume action flags, instant state reset on phase transitions
@@ -589,6 +591,8 @@ requestAnimationFrame(loop)
589
591
  | [`font.ts`](#fontts--rom-bitmap-font) | 96-character ROM font, raw bitmap access |
590
592
  | [`i18n.ts`](#i18nts--runtime-locale-selection) | Type-safe runtime locale selection for translated string packs |
591
593
  | [`lighting.ts`](#lightingts--dithered-cave-darkness) | Dithered cave darkness: pre-baked level tiles + dirty-cell buffer, one blit/frame (no per-frame putImageData) |
594
+ | [`cache.ts`](#cachets--offscreen-layer-cache) | Offscreen layer cache: render a static layer once, blit each frame, `dirty`-flag invalidation |
595
+ | [`attrscreen.ts`](#attrscreents--attribute-clash-opt-in) | Opt-in authentic ZX colour clash: 1-bit pixels + 32×24 per-cell ink/paper, one `putImageData`/frame |
592
596
  | [`music.ts`](#musicts--note-name-ay-music) | Write AY music by note name (`A5`, `C#4`) and loop it for background tracks |
593
597
 
594
598
  ---
@@ -2740,6 +2744,115 @@ track.stop()
2740
2744
 
2741
2745
  ---
2742
2746
 
2747
+ ## `cache.ts` — Offscreen Layer Cache
2748
+
2749
+ `drawBitmap`, `drawSprite` and `drawTileMapAt` paint **one `fillRect` per lit
2750
+ pixel** — perfect for a moving sprite, but lethal for a full-screen layer redrawn
2751
+ every frame (a scrolling tile map can be thousands of `fillRect`s per frame). The
2752
+ cure is to render that layer **once** to an offscreen canvas, then blit it with a
2753
+ single `drawImage`.
2754
+
2755
+ A `LayerCache` is an offscreen canvas plus a `dirty` flag. `refreshLayer` re-runs
2756
+ your draw callback **only while the cache is dirty**, then clears the flag; call
2757
+ `invalidateLayer` whenever the layer's contents change (a tile edited, a level
2758
+ reset) to force exactly one re-render. Headless-safe: with no `document` the
2759
+ canvas is `null`, the draw is skipped, and nothing throws.
2760
+
2761
+ ### `createLayerCache(width, height): LayerCache`
2762
+
2763
+ Creates an offscreen cache of the given pixel size, starting `dirty`. Image
2764
+ smoothing is disabled for crisp ZX output. Create once; reuse across frames.
2765
+ Throws if `width`/`height` is not a positive number.
2766
+
2767
+ ### `invalidateLayer(layer): void`
2768
+
2769
+ Marks the cache stale so the next `refreshLayer` re-renders it.
2770
+
2771
+ ### `refreshLayer(layer, render): HTMLCanvasElement | null`
2772
+
2773
+ Runs `render(offscreenCtx)` only if the cache is dirty (the context is cleared
2774
+ first), then clears the flag and returns the offscreen canvas to blit (or `null`
2775
+ when headless). Blitting is yours: a `drawImage` source window for a scrolling
2776
+ camera, or `drawImage(canvas, 0, 0)` for a static overlay.
2777
+
2778
+ ```ts
2779
+ // Cache a whole tile map; blit a moving camera window each frame.
2780
+ const world = tileMapWorldSize(map)
2781
+ const tiles = createLayerCache(world.width, world.height) // once
2782
+
2783
+ // game loop:
2784
+ refreshLayer(tiles, (lctx) => drawTileMapAt(lctx, map, 0, 0, world.width, world.height))
2785
+ if (tiles.canvas) ctx.drawImage(tiles.canvas, camX, camY, 256, 192, 0, 0, 256, 192)
2786
+
2787
+ // when a tile changes (a platform crumbles, the level resets):
2788
+ invalidateLayer(tiles)
2789
+ ```
2790
+
2791
+ > Pairs naturally with `tilescroll` / `tilemap`: cache the static geometry and
2792
+ > invalidate only on the rare tile change. The same primitive caches any static
2793
+ > overlay — e.g. `refreshLayer(overlay, (c) => drawScanlines(c))`.
2794
+
2795
+ ---
2796
+
2797
+ ## `attrscreen.ts` — Attribute Clash (opt-in)
2798
+
2799
+ The real Spectrum stored the screen as **two planes**: a 1-bit pixel bitmap and a
2800
+ 32×24 grid where each 8×8 cell holds exactly *one* ink + *one* paper. Drawing into a
2801
+ cell rewrote that single attribute, so the sprite and the background sharing the
2802
+ cell snapped to the same two colours — the famous **colour clash**.
2803
+
2804
+ zx-kit composites in full colour by default (no clash — see *Spectrum-inspired, not
2805
+ hardware-accurate*). `attrscreen.ts` is the opt-in way to get the authentic bleed,
2806
+ the same shape as `drawScanlines` / `renderDarkness`: route a frame through an
2807
+ `AttrScreen`, then `flushAttrScreen` once.
2808
+
2809
+ `stampMono` writes a monochrome bitmap's *shape* into the pixel plane and
2810
+ re-attributes every cell a lit pixel lands in — and it **never clears** other
2811
+ pixels, so whatever was already in a touched cell renders in the new colour. That
2812
+ is the clash. The flush resolves both planes into one reused `ImageData` and uploads
2813
+ it with a single `putImageData` + `drawImage` — never per-pixel `fillRect`.
2814
+
2815
+ > Not hardware-exact by choice: ink and paper may be any two of the 15 colours (the
2816
+ > real machine forced both to share the BRIGHT bit), and FLASH is not modelled (yet).
2817
+ > Assumes a little-endian platform (every browser). Headless-safe.
2818
+
2819
+ ### `createAttrScreen(cols = 32, rows = 24): AttrScreen`
2820
+
2821
+ Allocates a screen-space attribute plane (default 32×24 cells = 256×192). Create
2822
+ once; reuse across frames. Throws on a non-positive size.
2823
+
2824
+ ### `clearAttrScreen(scr, paper, ink?)`
2825
+
2826
+ Resets for a new frame: clears all pixels to paper and fills every cell's
2827
+ attributes (`ink` defaults to `paper`). Call before stamping.
2828
+
2829
+ ### `stampMono(scr, bitmap, x, y, ink, paper, policy?)`
2830
+
2831
+ Stamps a monochrome `Bitmap` at screen pixel `(x, y)` (rounded; may be sub-cell,
2832
+ negative, or off-screen — clipped). `policy` controls how touched cells recolour:
2833
+ `'both'` (default — ink **and** paper, the most authentic bleed), `'ink-only'`
2834
+ (keep the existing paper), `'paper-only'`.
2835
+
2836
+ ### `flushAttrScreen(ctx, scr)`
2837
+
2838
+ Resolves the two planes into RGBA and uploads with one `putImageData` + `drawImage`
2839
+ at `(0, 0)` under the current transform (so `setupCanvas`'s `×scale` fills the
2840
+ canvas). Headless: fills `scr.rgba` and skips the blit.
2841
+
2842
+ ```ts
2843
+ const scr = createAttrScreen() // once
2844
+ // each frame, in screen space:
2845
+ clearAttrScreen(scr, C.BLACK)
2846
+ stampMono(scr, caveBitmap, 0, 0, C.B_BLUE, C.BLACK) // background
2847
+ stampMono(scr, rabbitBitmap, rx, ry, C.B_WHITE, C.BLACK) // sprite → its cells clash to white
2848
+ flushAttrScreen(ctx, scr)
2849
+ ```
2850
+
2851
+ > A cell clashes only when a lit pixel lands in it (silhouette clash), so a sprite
2852
+ > recolours exactly the cells it visibly occupies.
2853
+
2854
+ ---
2855
+
2743
2856
  ## Architecture
2744
2857
 
2745
2858
  ### Module structure
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @module attrscreen
3
+ *
4
+ * **Authentic ZX attribute clash — opt-in.** The real Spectrum stored the screen
5
+ * as two separate planes: a 1-bit **pixel** bitmap (256×192) and a 32×24
6
+ * **attribute** grid where each 8×8 cell holds exactly *one* ink + *one* paper.
7
+ * Drawing a sprite into a cell rewrote that cell's attribute, so everything in
8
+ * the cell — sprite *and* background — snapped to the same two colours. That bleed
9
+ * is the famous *colour clash*.
10
+ *
11
+ * zx-kit normally composites in full colour (no clash — by design, see the README).
12
+ * This module is the opt-in way to get the real thing, the same shape as the other
13
+ * effects ({@link "renderer" | drawScanlines}, {@link "lighting" | renderDarkness}):
14
+ * a game that wants clash routes its drawing through an {@link AttrScreen} and
15
+ * calls {@link flushAttrScreen} once per frame.
16
+ *
17
+ * **The dual plane:** {@link stampMono} writes a monochrome bitmap's *shape* into
18
+ * the pixel plane and re-attributes every cell it touches (per {@link AttrPolicy}).
19
+ * Crucially it does **not** clear other pixels — so a leaf already drawn in a cell
20
+ * keeps its pixels but now renders in the *new* cell colour. That is the clash.
21
+ *
22
+ * **Fast flush:** the two planes are resolved into one RGBA buffer and uploaded
23
+ * with a single `putImageData` + `drawImage` — never per-pixel `fillRect`. Assumes
24
+ * a little-endian platform (every browser; common Node).
25
+ *
26
+ * Headless-safe: with no `document` the offscreen canvas is `null`, the resolve
27
+ * still runs (so the logic is testable) and the blit is skipped — nothing throws.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const scr = createAttrScreen() // 32×24 cells = 256×192, once
32
+ * // each frame, in screen space:
33
+ * clearAttrScreen(scr, C.BLACK) // blank paper
34
+ * stampMono(scr, caveBitmap, 0, 0, C.B_BLUE, C.BLACK) // background
35
+ * stampMono(scr, rabbitBitmap, rx, ry, C.B_WHITE, C.BLACK) // sprite → its cells clash to white
36
+ * flushAttrScreen(ctx, scr) // one putImageData + drawImage
37
+ * ```
38
+ */
39
+ import { type SpectrumColor } from './palette.js';
40
+ import type { Bitmap } from './renderer.js';
41
+ /**
42
+ * How a {@link stampMono} re-colours each cell it touches:
43
+ * - `'both'` — set ink **and** paper (last writer owns the whole cell — the most
44
+ * authentic clash: the background paper changes too).
45
+ * - `'ink-only'` — set ink, keep the existing paper (gentler bleed).
46
+ * - `'paper-only'` — set paper, keep the existing ink.
47
+ */
48
+ export type AttrPolicy = 'both' | 'ink-only' | 'paper-only';
49
+ /** A screen-space dual plane: 1-bit pixels + per-cell ink/paper attributes. */
50
+ export interface AttrScreen {
51
+ readonly cols: number;
52
+ readonly rows: number;
53
+ readonly width: number;
54
+ readonly height: number;
55
+ /** One byte per pixel — `1` = ink, `0` = paper. Row-major, `width*height`. */
56
+ readonly pixels: Uint8Array;
57
+ /** Packed RGBA (little-endian) ink colour per cell, `cols*rows`. */
58
+ readonly cellInk: Uint32Array;
59
+ /** Packed RGBA paper colour per cell, `cols*rows`. */
60
+ readonly cellPaper: Uint32Array;
61
+ /** Resolved RGBA frame buffer (`width*height*4`), filled by {@link flushAttrScreen}. */
62
+ readonly rgba: Uint8ClampedArray;
63
+ /** Reusable `ImageData` backing {@link rgba} (`null` when headless). */
64
+ readonly image: ImageData | null;
65
+ /** Offscreen canvas the resolved buffer is uploaded to (`null` when headless). */
66
+ readonly canvas: HTMLCanvasElement | null;
67
+ }
68
+ /**
69
+ * Creates a screen-space attribute plane of `cols`×`rows` 8×8 cells (default
70
+ * 32×24 = 256×192). Allocate once; reuse across frames. Throws on a non-positive
71
+ * size.
72
+ */
73
+ export declare function createAttrScreen(cols?: number, rows?: number): AttrScreen;
74
+ /**
75
+ * Resets the screen for a new frame: clears all pixels to paper and fills every
76
+ * cell's attributes. `ink` defaults to `paper`, so untouched cells are a flat
77
+ * paper colour. Call at the start of each frame, before stamping.
78
+ */
79
+ export declare function clearAttrScreen(scr: AttrScreen, paper: SpectrumColor, ink?: SpectrumColor): void;
80
+ /**
81
+ * Stamps a monochrome {@link Bitmap} at screen pixel `(x, y)` (rounded; may be
82
+ * sub-cell, negative, or off-screen — clipped). Sets the bitmap's lit pixels in
83
+ * the pixel plane and re-attributes every cell that receives a lit pixel, per
84
+ * `policy` (default `'both'`). Lit-pixel-only: existing pixels are never cleared,
85
+ * so other sprites/background in a touched cell bleed to the new colour.
86
+ */
87
+ export declare function stampMono(scr: AttrScreen, bitmap: Bitmap, x: number, y: number, ink: SpectrumColor, paper: SpectrumColor, policy?: AttrPolicy): void;
88
+ /**
89
+ * Resolves the two planes into RGBA (each pixel takes its cell's ink or paper)
90
+ * and uploads the result with one `putImageData` + `drawImage`. The image is
91
+ * drawn at `(0, 0)` under the current transform, so the usual `setupCanvas`
92
+ * `×scale` makes it fill the canvas. Headless: fills {@link AttrScreen.rgba} and
93
+ * skips the blit.
94
+ */
95
+ export declare function flushAttrScreen(ctx: CanvasRenderingContext2D, scr: AttrScreen): void;
96
+ //# sourceMappingURL=attrscreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attrscreen.d.ts","sourceRoot":"","sources":["../src/attrscreen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,CAAA;AAE3D,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAA;IAC/B,wFAAwF;IACxF,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAChC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAChC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAA;CAC1C;AAUD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,SAAK,EAAE,IAAI,SAAK,GAAG,UAAU,CAiCjE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,GAAE,aAAqB,GAAG,IAAI,CAIvG;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,MAAM,EACd,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,GAAG,EAAE,aAAa,EAClB,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,UAAmB,GAC1B,IAAI,CA+BN;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,wBAAwB,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CAoBpF"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @module attrscreen
3
+ *
4
+ * **Authentic ZX attribute clash — opt-in.** The real Spectrum stored the screen
5
+ * as two separate planes: a 1-bit **pixel** bitmap (256×192) and a 32×24
6
+ * **attribute** grid where each 8×8 cell holds exactly *one* ink + *one* paper.
7
+ * Drawing a sprite into a cell rewrote that cell's attribute, so everything in
8
+ * the cell — sprite *and* background — snapped to the same two colours. That bleed
9
+ * is the famous *colour clash*.
10
+ *
11
+ * zx-kit normally composites in full colour (no clash — by design, see the README).
12
+ * This module is the opt-in way to get the real thing, the same shape as the other
13
+ * effects ({@link "renderer" | drawScanlines}, {@link "lighting" | renderDarkness}):
14
+ * a game that wants clash routes its drawing through an {@link AttrScreen} and
15
+ * calls {@link flushAttrScreen} once per frame.
16
+ *
17
+ * **The dual plane:** {@link stampMono} writes a monochrome bitmap's *shape* into
18
+ * the pixel plane and re-attributes every cell it touches (per {@link AttrPolicy}).
19
+ * Crucially it does **not** clear other pixels — so a leaf already drawn in a cell
20
+ * keeps its pixels but now renders in the *new* cell colour. That is the clash.
21
+ *
22
+ * **Fast flush:** the two planes are resolved into one RGBA buffer and uploaded
23
+ * with a single `putImageData` + `drawImage` — never per-pixel `fillRect`. Assumes
24
+ * a little-endian platform (every browser; common Node).
25
+ *
26
+ * Headless-safe: with no `document` the offscreen canvas is `null`, the resolve
27
+ * still runs (so the logic is testable) and the blit is skipped — nothing throws.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const scr = createAttrScreen() // 32×24 cells = 256×192, once
32
+ * // each frame, in screen space:
33
+ * clearAttrScreen(scr, C.BLACK) // blank paper
34
+ * stampMono(scr, caveBitmap, 0, 0, C.B_BLUE, C.BLACK) // background
35
+ * stampMono(scr, rabbitBitmap, rx, ry, C.B_WHITE, C.BLACK) // sprite → its cells clash to white
36
+ * flushAttrScreen(ctx, scr) // one putImageData + drawImage
37
+ * ```
38
+ */
39
+ import { CELL } from './palette.js';
40
+ /** Packs a `#RRGGBB` Spectrum colour into a little-endian RGBA word (opaque). */
41
+ function hexToU32(hex) {
42
+ const r = parseInt(hex.slice(1, 3), 16);
43
+ const g = parseInt(hex.slice(3, 5), 16);
44
+ const b = parseInt(hex.slice(5, 7), 16);
45
+ return ((255 << 24) | (b << 16) | (g << 8) | r) >>> 0;
46
+ }
47
+ /**
48
+ * Creates a screen-space attribute plane of `cols`×`rows` 8×8 cells (default
49
+ * 32×24 = 256×192). Allocate once; reuse across frames. Throws on a non-positive
50
+ * size.
51
+ */
52
+ export function createAttrScreen(cols = 32, rows = 24) {
53
+ if (!Number.isInteger(cols) || cols <= 0 || !Number.isInteger(rows) || rows <= 0) {
54
+ throw new Error(`createAttrScreen: cols and rows must be positive integers, got ${cols}×${rows}`);
55
+ }
56
+ const width = cols * CELL;
57
+ const height = rows * CELL;
58
+ let canvas = null;
59
+ let image = null;
60
+ if (typeof document !== 'undefined') {
61
+ canvas = document.createElement('canvas');
62
+ canvas.width = width;
63
+ canvas.height = height;
64
+ const ctx = canvas.getContext('2d');
65
+ if (ctx) {
66
+ ctx.imageSmoothingEnabled = false;
67
+ image = ctx.createImageData(width, height);
68
+ }
69
+ }
70
+ // In the browser the resolved buffer IS the ImageData's data (no per-frame copy,
71
+ // no constructor); headless it's a standalone buffer so the resolve stays testable.
72
+ const rgba = image ? image.data : new Uint8ClampedArray(width * height * 4);
73
+ return {
74
+ cols,
75
+ rows,
76
+ width,
77
+ height,
78
+ pixels: new Uint8Array(width * height),
79
+ cellInk: new Uint32Array(cols * rows),
80
+ cellPaper: new Uint32Array(cols * rows),
81
+ rgba,
82
+ image,
83
+ canvas,
84
+ };
85
+ }
86
+ /**
87
+ * Resets the screen for a new frame: clears all pixels to paper and fills every
88
+ * cell's attributes. `ink` defaults to `paper`, so untouched cells are a flat
89
+ * paper colour. Call at the start of each frame, before stamping.
90
+ */
91
+ export function clearAttrScreen(scr, paper, ink = paper) {
92
+ scr.pixels.fill(0);
93
+ scr.cellPaper.fill(hexToU32(paper));
94
+ scr.cellInk.fill(hexToU32(ink));
95
+ }
96
+ /**
97
+ * Stamps a monochrome {@link Bitmap} at screen pixel `(x, y)` (rounded; may be
98
+ * sub-cell, negative, or off-screen — clipped). Sets the bitmap's lit pixels in
99
+ * the pixel plane and re-attributes every cell that receives a lit pixel, per
100
+ * `policy` (default `'both'`). Lit-pixel-only: existing pixels are never cleared,
101
+ * so other sprites/background in a touched cell bleed to the new colour.
102
+ */
103
+ export function stampMono(scr, bitmap, x, y, ink, paper, policy = 'both') {
104
+ const inkU32 = hexToU32(ink);
105
+ const paperU32 = hexToU32(paper);
106
+ const setInk = policy !== 'paper-only';
107
+ const setPaper = policy !== 'ink-only';
108
+ const { data, width: bw, height: bh } = bitmap;
109
+ const bytesPerRow = bw >> 3;
110
+ const ox = Math.round(x);
111
+ const oy = Math.round(y);
112
+ for (let row = 0; row < bh; row++) {
113
+ const py = oy + row;
114
+ if (py < 0 || py >= scr.height)
115
+ continue;
116
+ const rowByteBase = row * bytesPerRow;
117
+ const rowPixBase = py * scr.width;
118
+ const cellRowBase = (py >> 3) * scr.cols;
119
+ for (let byteIdx = 0; byteIdx < bytesPerRow; byteIdx++) {
120
+ const byte = data[rowByteBase + byteIdx];
121
+ if (!byte)
122
+ continue;
123
+ const colBase = byteIdx << 3;
124
+ for (let bit = 0; bit < 8; bit++) {
125
+ if (!(byte & (0x80 >> bit)))
126
+ continue;
127
+ const px = ox + colBase + bit;
128
+ if (px < 0 || px >= scr.width)
129
+ continue;
130
+ scr.pixels[rowPixBase + px] = 1;
131
+ const cell = cellRowBase + (px >> 3);
132
+ if (setInk)
133
+ scr.cellInk[cell] = inkU32;
134
+ if (setPaper)
135
+ scr.cellPaper[cell] = paperU32;
136
+ }
137
+ }
138
+ }
139
+ }
140
+ /**
141
+ * Resolves the two planes into RGBA (each pixel takes its cell's ink or paper)
142
+ * and uploads the result with one `putImageData` + `drawImage`. The image is
143
+ * drawn at `(0, 0)` under the current transform, so the usual `setupCanvas`
144
+ * `×scale` makes it fill the canvas. Headless: fills {@link AttrScreen.rgba} and
145
+ * skips the blit.
146
+ */
147
+ export function flushAttrScreen(ctx, scr) {
148
+ const { pixels, cellInk, cellPaper, width, height, cols } = scr;
149
+ const out = new Uint32Array(scr.rgba.buffer);
150
+ let p = 0;
151
+ for (let yy = 0; yy < height; yy++) {
152
+ const cellRow = (yy >> 3) * cols;
153
+ for (let xx = 0; xx < width; xx++) {
154
+ const cell = cellRow + (xx >> 3);
155
+ out[p] = pixels[p] ? cellInk[cell] : cellPaper[cell];
156
+ p++;
157
+ }
158
+ }
159
+ if (scr.canvas && scr.image) {
160
+ const offctx = scr.canvas.getContext('2d');
161
+ if (offctx) {
162
+ offctx.putImageData(scr.image, 0, 0);
163
+ ctx.drawImage(scr.canvas, 0, 0);
164
+ }
165
+ }
166
+ }
167
+ //# sourceMappingURL=attrscreen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attrscreen.js","sourceRoot":"","sources":["../src/attrscreen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @module cache
3
+ *
4
+ * **Offscreen layer cache** — render a static (or rarely-changing) layer to an
5
+ * offscreen canvas **once**, then blit it every frame with a single `drawImage`
6
+ * instead of re-rasterising it. This is the antidote to the per-pixel `fillRect`
7
+ * cost of {@link "renderer" | drawBitmap} / {@link "tilescroll" | drawTileMapAt}
8
+ * when they are used for a full-screen layer on every frame.
9
+ *
10
+ * The cache is **dirty-driven**: {@link refreshLayer} re-runs your draw callback
11
+ * only while the cache is `dirty`, then clears the flag. Call
12
+ * {@link invalidateLayer} whenever the layer's contents change (a tile edited, a
13
+ * palette swap, a level reset) to force exactly one re-render next frame.
14
+ *
15
+ * Headless-safe: with no `document` (Node / SSR / tests) the canvas is `null`,
16
+ * `refreshLayer` skips the draw and returns `null`, and nothing throws — mirrors
17
+ * the {@link "lighting" | lighting} module's degrade-gracefully behaviour.
18
+ *
19
+ * @example Cache a whole tile map, blit a moving camera window each frame
20
+ * ```ts
21
+ * const world = tileMapWorldSize(map)
22
+ * const tiles = createLayerCache(world.width, world.height) // once
23
+ * // game loop:
24
+ * refreshLayer(tiles, (lctx) => drawTileMapAt(lctx, map, 0, 0, world.width, world.height))
25
+ * if (tiles.canvas) ctx.drawImage(tiles.canvas, camX, camY, viewW, viewH, 0, 0, viewW, viewH)
26
+ * // when a tile changes (e.g. a platform crumbles or the level resets):
27
+ * invalidateLayer(tiles)
28
+ * ```
29
+ *
30
+ * @example Cache a static overlay — drawn once, blitted forever
31
+ * ```ts
32
+ * const overlay = createLayerCache(canvas.width, canvas.height)
33
+ * refreshLayer(overlay, (lctx) => drawScanlines(lctx))
34
+ * if (overlay.canvas) ctx.drawImage(overlay.canvas, 0, 0)
35
+ * ```
36
+ */
37
+ /** An offscreen canvas plus a dirty flag. `canvas` is `null` when headless. */
38
+ export interface LayerCache {
39
+ /** Width of the cached layer in pixels. */
40
+ readonly width: number;
41
+ /** Height of the cached layer in pixels. */
42
+ readonly height: number;
43
+ /** The offscreen canvas to blit, or `null` with no `document` (headless). */
44
+ readonly canvas: HTMLCanvasElement | null;
45
+ /** When `true`, the next {@link refreshLayer} re-runs the draw callback. */
46
+ dirty: boolean;
47
+ }
48
+ /**
49
+ * Creates an offscreen layer cache of the given pixel size. Starts `dirty`, so
50
+ * the first {@link refreshLayer} renders it. Image smoothing is disabled on the
51
+ * offscreen context for crisp ZX output. Create once and reuse across frames.
52
+ *
53
+ * @throws if `width` or `height` is not a positive finite number.
54
+ */
55
+ export declare function createLayerCache(width: number, height: number): LayerCache;
56
+ /** Marks the cache stale so the next {@link refreshLayer} re-renders it. */
57
+ export declare function invalidateLayer(layer: LayerCache): void;
58
+ /**
59
+ * Re-renders the cache via `render` **only if it is dirty**, then clears the
60
+ * dirty flag. `render` receives the offscreen 2D context (already cleared) and
61
+ * should draw the whole layer in layer-local coordinates (top-left = `0,0`).
62
+ * Returns the offscreen canvas to blit (or `null` when headless).
63
+ *
64
+ * Blitting is the caller's job: use a `drawImage` source window for a scrolling
65
+ * camera, or `drawImage(canvas, 0, 0)` for a static overlay.
66
+ */
67
+ export declare function refreshLayer(layer: LayerCache, render: (ctx: CanvasRenderingContext2D) => void): HTMLCanvasElement | null;
68
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACzC,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAa1E;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAEvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,CAAC,GAAG,EAAE,wBAAwB,KAAK,IAAI,GAC9C,iBAAiB,GAAG,IAAI,CAW1B"}
package/dist/cache.js ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @module cache
3
+ *
4
+ * **Offscreen layer cache** — render a static (or rarely-changing) layer to an
5
+ * offscreen canvas **once**, then blit it every frame with a single `drawImage`
6
+ * instead of re-rasterising it. This is the antidote to the per-pixel `fillRect`
7
+ * cost of {@link "renderer" | drawBitmap} / {@link "tilescroll" | drawTileMapAt}
8
+ * when they are used for a full-screen layer on every frame.
9
+ *
10
+ * The cache is **dirty-driven**: {@link refreshLayer} re-runs your draw callback
11
+ * only while the cache is `dirty`, then clears the flag. Call
12
+ * {@link invalidateLayer} whenever the layer's contents change (a tile edited, a
13
+ * palette swap, a level reset) to force exactly one re-render next frame.
14
+ *
15
+ * Headless-safe: with no `document` (Node / SSR / tests) the canvas is `null`,
16
+ * `refreshLayer` skips the draw and returns `null`, and nothing throws — mirrors
17
+ * the {@link "lighting" | lighting} module's degrade-gracefully behaviour.
18
+ *
19
+ * @example Cache a whole tile map, blit a moving camera window each frame
20
+ * ```ts
21
+ * const world = tileMapWorldSize(map)
22
+ * const tiles = createLayerCache(world.width, world.height) // once
23
+ * // game loop:
24
+ * refreshLayer(tiles, (lctx) => drawTileMapAt(lctx, map, 0, 0, world.width, world.height))
25
+ * if (tiles.canvas) ctx.drawImage(tiles.canvas, camX, camY, viewW, viewH, 0, 0, viewW, viewH)
26
+ * // when a tile changes (e.g. a platform crumbles or the level resets):
27
+ * invalidateLayer(tiles)
28
+ * ```
29
+ *
30
+ * @example Cache a static overlay — drawn once, blitted forever
31
+ * ```ts
32
+ * const overlay = createLayerCache(canvas.width, canvas.height)
33
+ * refreshLayer(overlay, (lctx) => drawScanlines(lctx))
34
+ * if (overlay.canvas) ctx.drawImage(overlay.canvas, 0, 0)
35
+ * ```
36
+ */
37
+ /**
38
+ * Creates an offscreen layer cache of the given pixel size. Starts `dirty`, so
39
+ * the first {@link refreshLayer} renders it. Image smoothing is disabled on the
40
+ * offscreen context for crisp ZX output. Create once and reuse across frames.
41
+ *
42
+ * @throws if `width` or `height` is not a positive finite number.
43
+ */
44
+ export function createLayerCache(width, height) {
45
+ if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
46
+ throw new Error(`createLayerCache: width and height must be positive, got ${width}×${height}`);
47
+ }
48
+ let canvas = null;
49
+ if (typeof document !== 'undefined') {
50
+ canvas = document.createElement('canvas');
51
+ canvas.width = width;
52
+ canvas.height = height;
53
+ const ctx = canvas.getContext('2d');
54
+ if (ctx)
55
+ ctx.imageSmoothingEnabled = false;
56
+ }
57
+ return { width, height, canvas, dirty: true };
58
+ }
59
+ /** Marks the cache stale so the next {@link refreshLayer} re-renders it. */
60
+ export function invalidateLayer(layer) {
61
+ layer.dirty = true;
62
+ }
63
+ /**
64
+ * Re-renders the cache via `render` **only if it is dirty**, then clears the
65
+ * dirty flag. `render` receives the offscreen 2D context (already cleared) and
66
+ * should draw the whole layer in layer-local coordinates (top-left = `0,0`).
67
+ * Returns the offscreen canvas to blit (or `null` when headless).
68
+ *
69
+ * Blitting is the caller's job: use a `drawImage` source window for a scrolling
70
+ * camera, or `drawImage(canvas, 0, 0)` for a static overlay.
71
+ */
72
+ export function refreshLayer(layer, render) {
73
+ if (layer.dirty) {
74
+ layer.dirty = false;
75
+ const ctx = layer.canvas ? layer.canvas.getContext('2d') : null;
76
+ if (ctx) {
77
+ ctx.clearRect(0, 0, layer.width, layer.height);
78
+ ctx.imageSmoothingEnabled = false;
79
+ render(ctx);
80
+ }
81
+ }
82
+ return layer.canvas;
83
+ }
84
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAcH;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa,EAAE,MAAc;IAC5D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QACrF,MAAM,IAAI,KAAK,CAAC,4DAA4D,KAAK,IAAI,MAAM,EAAE,CAAC,CAAA;IAChG,CAAC;IACD,IAAI,MAAM,GAA6B,IAAI,CAAA;IAC3C,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;QACzC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAA;QACpB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAA;QACtB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,GAAG;YAAE,GAAG,CAAC,qBAAqB,GAAG,KAAK,CAAA;IAC5C,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AAC/C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,KAAK,CAAC,KAAK,GAAG,IAAI,CAAA;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAiB,EACjB,MAA+C;IAE/C,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,KAAK,CAAC,KAAK,GAAG,KAAK,CAAA;QACnB,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC/D,IAAI,GAAG,EAAE,CAAC;YACR,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;YAC9C,GAAG,CAAC,qBAAqB,GAAG,KAAK,CAAA;YACjC,MAAM,CAAC,GAAG,CAAC,CAAA;QACb,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAA;AACrB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -17,5 +17,7 @@ export * from './scene.js';
17
17
  export * from './save.js';
18
18
  export * from './i18n.js';
19
19
  export * from './lighting.js';
20
+ export * from './cache.js';
21
+ export * from './attrscreen.js';
20
22
  export * from './music.js';
21
23
  //# 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"}
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,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -17,5 +17,7 @@ export * from './scene.js';
17
17
  export * from './save.js';
18
18
  export * from './i18n.js';
19
19
  export * from './lighting.js';
20
+ export * from './cache.js';
21
+ export * from './attrscreen.js';
20
22
  export * from './music.js';
21
23
  //# 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"}
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,YAAY,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zx-kit",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/zrebec/zx-kit.git"