picopilot 0.0.0 → 0.1.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/LICENSE +661 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/audio.d.ts +17 -0
- package/dist/commands/audio.d.ts.map +1 -0
- package/dist/commands/audio.js +315 -0
- package/dist/commands/audio.js.map +1 -0
- package/dist/commands/export.d.ts +36 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +257 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/gfx.d.ts +28 -0
- package/dist/commands/gfx.d.ts.map +1 -0
- package/dist/commands/gfx.js +521 -0
- package/dist/commands/gfx.js.map +1 -0
- package/dist/commands/init.d.ts +34 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +151 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +20 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +90 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/minify.d.ts +20 -0
- package/dist/commands/minify.d.ts.map +1 -0
- package/dist/commands/minify.js +129 -0
- package/dist/commands/minify.js.map +1 -0
- package/dist/commands/music.d.ts +13 -0
- package/dist/commands/music.d.ts.map +1 -0
- package/dist/commands/music.js +241 -0
- package/dist/commands/music.js.map +1 -0
- package/dist/commands/playtest.d.ts +29 -0
- package/dist/commands/playtest.d.ts.map +1 -0
- package/dist/commands/playtest.js +631 -0
- package/dist/commands/playtest.js.map +1 -0
- package/dist/commands/run.d.ts +30 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +224 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/serve.d.ts +50 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +213 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/sfx.d.ts +13 -0
- package/dist/commands/sfx.d.ts.map +1 -0
- package/dist/commands/sfx.js +254 -0
- package/dist/commands/sfx.js.map +1 -0
- package/dist/commands/tokens.d.ts +36 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +129 -0
- package/dist/commands/tokens.js.map +1 -0
- package/dist/commands/verify.d.ts +57 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +213 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/version.d.ts +12 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +24 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/engine/audio/index.d.ts +14 -0
- package/dist/engine/audio/index.d.ts.map +1 -0
- package/dist/engine/audio/index.js +14 -0
- package/dist/engine/audio/index.js.map +1 -0
- package/dist/engine/audio/music.d.ts +116 -0
- package/dist/engine/audio/music.d.ts.map +1 -0
- package/dist/engine/audio/music.js +138 -0
- package/dist/engine/audio/music.js.map +1 -0
- package/dist/engine/audio/section.d.ts +34 -0
- package/dist/engine/audio/section.d.ts.map +1 -0
- package/dist/engine/audio/section.js +67 -0
- package/dist/engine/audio/section.js.map +1 -0
- package/dist/engine/audio/sfx.d.ts +141 -0
- package/dist/engine/audio/sfx.d.ts.map +1 -0
- package/dist/engine/audio/sfx.js +506 -0
- package/dist/engine/audio/sfx.js.map +1 -0
- package/dist/engine/cart/cart.d.ts +75 -0
- package/dist/engine/cart/cart.d.ts.map +1 -0
- package/dist/engine/cart/cart.js +153 -0
- package/dist/engine/cart/cart.js.map +1 -0
- package/dist/engine/cart/errors.d.ts +29 -0
- package/dist/engine/cart/errors.d.ts.map +1 -0
- package/dist/engine/cart/errors.js +26 -0
- package/dist/engine/cart/errors.js.map +1 -0
- package/dist/engine/cart/gfx.d.ts +79 -0
- package/dist/engine/cart/gfx.d.ts.map +1 -0
- package/dist/engine/cart/gfx.js +171 -0
- package/dist/engine/cart/gfx.js.map +1 -0
- package/dist/engine/cart/index.d.ts +15 -0
- package/dist/engine/cart/index.d.ts.map +1 -0
- package/dist/engine/cart/index.js +15 -0
- package/dist/engine/cart/index.js.map +1 -0
- package/dist/engine/cart/map.d.ts +46 -0
- package/dist/engine/cart/map.d.ts.map +1 -0
- package/dist/engine/cart/map.js +118 -0
- package/dist/engine/cart/map.js.map +1 -0
- package/dist/engine/config/config.d.ts +74 -0
- package/dist/engine/config/config.d.ts.map +1 -0
- package/dist/engine/config/config.js +103 -0
- package/dist/engine/config/config.js.map +1 -0
- package/dist/engine/config/index.d.ts +7 -0
- package/dist/engine/config/index.d.ts.map +1 -0
- package/dist/engine/config/index.js +7 -0
- package/dist/engine/config/index.js.map +1 -0
- package/dist/engine/gfx/grid.d.ts +51 -0
- package/dist/engine/gfx/grid.d.ts.map +1 -0
- package/dist/engine/gfx/grid.js +116 -0
- package/dist/engine/gfx/grid.js.map +1 -0
- package/dist/engine/gfx/index.d.ts +17 -0
- package/dist/engine/gfx/index.d.ts.map +1 -0
- package/dist/engine/gfx/index.js +17 -0
- package/dist/engine/gfx/index.js.map +1 -0
- package/dist/engine/gfx/label.d.ts +34 -0
- package/dist/engine/gfx/label.d.ts.map +1 -0
- package/dist/engine/gfx/label.js +190 -0
- package/dist/engine/gfx/label.js.map +1 -0
- package/dist/engine/gfx/overlap.d.ts +42 -0
- package/dist/engine/gfx/overlap.d.ts.map +1 -0
- package/dist/engine/gfx/overlap.js +57 -0
- package/dist/engine/gfx/overlap.js.map +1 -0
- package/dist/engine/gfx/palette.d.ts +29 -0
- package/dist/engine/gfx/palette.d.ts.map +1 -0
- package/dist/engine/gfx/palette.js +46 -0
- package/dist/engine/gfx/palette.js.map +1 -0
- package/dist/engine/gfx/png.d.ts +31 -0
- package/dist/engine/gfx/png.d.ts.map +1 -0
- package/dist/engine/gfx/png.js +165 -0
- package/dist/engine/gfx/png.js.map +1 -0
- package/dist/engine/gfx/render.d.ts +42 -0
- package/dist/engine/gfx/render.d.ts.map +1 -0
- package/dist/engine/gfx/render.js +72 -0
- package/dist/engine/gfx/render.js.map +1 -0
- package/dist/engine/index.d.ts +22 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +22 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/pico8/adapter.d.ts +267 -0
- package/dist/engine/pico8/adapter.d.ts.map +1 -0
- package/dist/engine/pico8/adapter.js +37 -0
- package/dist/engine/pico8/adapter.js.map +1 -0
- package/dist/engine/pico8/drive.d.ts +308 -0
- package/dist/engine/pico8/drive.d.ts.map +1 -0
- package/dist/engine/pico8/drive.js +581 -0
- package/dist/engine/pico8/drive.js.map +1 -0
- package/dist/engine/pico8/harness.d.ts +146 -0
- package/dist/engine/pico8/harness.d.ts.map +1 -0
- package/dist/engine/pico8/harness.js +251 -0
- package/dist/engine/pico8/harness.js.map +1 -0
- package/dist/engine/pico8/index.d.ts +21 -0
- package/dist/engine/pico8/index.d.ts.map +1 -0
- package/dist/engine/pico8/index.js +21 -0
- package/dist/engine/pico8/index.js.map +1 -0
- package/dist/engine/pico8/sentinel.d.ts +63 -0
- package/dist/engine/pico8/sentinel.d.ts.map +1 -0
- package/dist/engine/pico8/sentinel.js +86 -0
- package/dist/engine/pico8/sentinel.js.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts +10 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.js +11 -0
- package/dist/engine/pico8/session-daemon-entry.js.map +1 -0
- package/dist/engine/pico8/session-daemon-main.d.ts +44 -0
- package/dist/engine/pico8/session-daemon-main.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-main.js +71 -0
- package/dist/engine/pico8/session-daemon-main.js.map +1 -0
- package/dist/engine/pico8/session-daemon.d.ts +68 -0
- package/dist/engine/pico8/session-daemon.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon.js +264 -0
- package/dist/engine/pico8/session-daemon.js.map +1 -0
- package/dist/engine/pico8/session.d.ts +140 -0
- package/dist/engine/pico8/session.d.ts.map +1 -0
- package/dist/engine/pico8/session.js +250 -0
- package/dist/engine/pico8/session.js.map +1 -0
- package/dist/engine/pico8/shell.d.ts +103 -0
- package/dist/engine/pico8/shell.d.ts.map +1 -0
- package/dist/engine/pico8/shell.js +324 -0
- package/dist/engine/pico8/shell.js.map +1 -0
- package/dist/engine/pico8/supervisor.d.ts +137 -0
- package/dist/engine/pico8/supervisor.d.ts.map +1 -0
- package/dist/engine/pico8/supervisor.js +140 -0
- package/dist/engine/pico8/supervisor.js.map +1 -0
- package/dist/engine/scaffold/index.d.ts +8 -0
- package/dist/engine/scaffold/index.d.ts.map +1 -0
- package/dist/engine/scaffold/index.js +8 -0
- package/dist/engine/scaffold/index.js.map +1 -0
- package/dist/engine/scaffold/scaffold.d.ts +49 -0
- package/dist/engine/scaffold/scaffold.d.ts.map +1 -0
- package/dist/engine/scaffold/scaffold.js +259 -0
- package/dist/engine/scaffold/scaffold.js.map +1 -0
- package/dist/engine/shrinko/adapter.d.ts +167 -0
- package/dist/engine/shrinko/adapter.d.ts.map +1 -0
- package/dist/engine/shrinko/adapter.js +107 -0
- package/dist/engine/shrinko/adapter.js.map +1 -0
- package/dist/engine/shrinko/index.d.ts +13 -0
- package/dist/engine/shrinko/index.d.ts.map +1 -0
- package/dist/engine/shrinko/index.js +13 -0
- package/dist/engine/shrinko/index.js.map +1 -0
- package/dist/engine/shrinko/shell.d.ts +69 -0
- package/dist/engine/shrinko/shell.d.ts.map +1 -0
- package/dist/engine/shrinko/shell.js +156 -0
- package/dist/engine/shrinko/shell.js.map +1 -0
- package/dist/engine/skills/index.d.ts +8 -0
- package/dist/engine/skills/index.d.ts.map +1 -0
- package/dist/engine/skills/index.js +8 -0
- package/dist/engine/skills/index.js.map +1 -0
- package/dist/engine/skills/skills.d.ts +104 -0
- package/dist/engine/skills/skills.d.ts.map +1 -0
- package/dist/engine/skills/skills.js +149 -0
- package/dist/engine/skills/skills.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +19 -0
- package/dist/version.js.map +1 -0
- package/package.json +48 -2
- package/src/bin.ts +4 -0
- package/src/cli.ts +61 -0
- package/src/commands/audio.test.ts +280 -0
- package/src/commands/audio.ts +410 -0
- package/src/commands/export.test.ts +310 -0
- package/src/commands/export.ts +316 -0
- package/src/commands/gfx-io.test.ts +178 -0
- package/src/commands/gfx.test.ts +470 -0
- package/src/commands/gfx.ts +645 -0
- package/src/commands/init.test.ts +482 -0
- package/src/commands/init.ts +193 -0
- package/src/commands/lint.test.ts +137 -0
- package/src/commands/lint.ts +117 -0
- package/src/commands/minify.test.ts +166 -0
- package/src/commands/minify.ts +156 -0
- package/src/commands/music.test.ts +272 -0
- package/src/commands/music.ts +288 -0
- package/src/commands/playtest.test.ts +317 -0
- package/src/commands/playtest.ts +773 -0
- package/src/commands/run.test.ts +264 -0
- package/src/commands/run.ts +285 -0
- package/src/commands/serve.test.ts +227 -0
- package/src/commands/serve.ts +272 -0
- package/src/commands/sfx.test.ts +349 -0
- package/src/commands/sfx.ts +300 -0
- package/src/commands/tokens.test.ts +212 -0
- package/src/commands/tokens.ts +168 -0
- package/src/commands/verify.test.ts +238 -0
- package/src/commands/verify.ts +256 -0
- package/src/commands/version.test.ts +70 -0
- package/src/commands/version.ts +24 -0
- package/src/engine/audio/index.ts +47 -0
- package/src/engine/audio/music.test.ts +188 -0
- package/src/engine/audio/music.ts +218 -0
- package/src/engine/audio/section.test.ts +124 -0
- package/src/engine/audio/section.ts +79 -0
- package/src/engine/audio/sfx.test.ts +410 -0
- package/src/engine/audio/sfx.ts +694 -0
- package/src/engine/cart/cart.test.ts +304 -0
- package/src/engine/cart/cart.ts +211 -0
- package/src/engine/cart/errors.ts +39 -0
- package/src/engine/cart/fixtures/crlf.p8 +6 -0
- package/src/engine/cart/fixtures/full.p8 +174 -0
- package/src/engine/cart/fixtures/header-only.p8 +2 -0
- package/src/engine/cart/fixtures/malformed-no-header.p8 +2 -0
- package/src/engine/cart/fixtures/minimal.p8 +10 -0
- package/src/engine/cart/fixtures/no-trailing-newline.p8 +6 -0
- package/src/engine/cart/gfx.ts +195 -0
- package/src/engine/cart/index.ts +27 -0
- package/src/engine/cart/map.ts +134 -0
- package/src/engine/config/config.ts +115 -0
- package/src/engine/config/index.ts +12 -0
- package/src/engine/gfx/grid.test.ts +128 -0
- package/src/engine/gfx/grid.ts +136 -0
- package/src/engine/gfx/index.ts +34 -0
- package/src/engine/gfx/label.test.ts +105 -0
- package/src/engine/gfx/label.ts +229 -0
- package/src/engine/gfx/overlap.test.ts +71 -0
- package/src/engine/gfx/overlap.ts +66 -0
- package/src/engine/gfx/palette.test.ts +35 -0
- package/src/engine/gfx/palette.ts +54 -0
- package/src/engine/gfx/png.test.ts +111 -0
- package/src/engine/gfx/png.ts +196 -0
- package/src/engine/gfx/render.test.ts +180 -0
- package/src/engine/gfx/render.ts +106 -0
- package/src/engine/index.ts +21 -0
- package/src/engine/pico8/adapter.ts +303 -0
- package/src/engine/pico8/drive.test.ts +415 -0
- package/src/engine/pico8/drive.ts +729 -0
- package/src/engine/pico8/harness.test.ts +181 -0
- package/src/engine/pico8/harness.ts +354 -0
- package/src/engine/pico8/index.ts +20 -0
- package/src/engine/pico8/sentinel.test.ts +80 -0
- package/src/engine/pico8/sentinel.ts +93 -0
- package/src/engine/pico8/session-daemon-entry.ts +12 -0
- package/src/engine/pico8/session-daemon-main.ts +98 -0
- package/src/engine/pico8/session-daemon.test.ts +212 -0
- package/src/engine/pico8/session-daemon.ts +306 -0
- package/src/engine/pico8/session.test.ts +239 -0
- package/src/engine/pico8/session.ts +321 -0
- package/src/engine/pico8/shell.test.ts +583 -0
- package/src/engine/pico8/shell.ts +412 -0
- package/src/engine/pico8/supervisor.test.ts +105 -0
- package/src/engine/pico8/supervisor.ts +197 -0
- package/src/engine/scaffold/index.ts +15 -0
- package/src/engine/scaffold/scaffold.ts +278 -0
- package/src/engine/shrinko/adapter.ts +241 -0
- package/src/engine/shrinko/fixtures/count-no-compressed.txt +2 -0
- package/src/engine/shrinko/fixtures/count-over-budget.txt +3 -0
- package/src/engine/shrinko/fixtures/count-under-budget.txt +3 -0
- package/src/engine/shrinko/index.ts +33 -0
- package/src/engine/shrinko/shell.ts +224 -0
- package/src/engine/shrinko/shrinko.test.ts +182 -0
- package/src/engine/skills/index.ts +14 -0
- package/src/engine/skills/skills.test.ts +376 -0
- package/src/engine/skills/skills.ts +205 -0
- package/src/index.ts +13 -0
- package/src/skills/game-design-reference/SKILL.md +70 -0
- package/src/skills/game-jam/SKILL.md +45 -0
- package/src/skills/picopilot-art/SKILL.md +62 -0
- package/src/skills/picopilot-audio/SKILL.md +194 -0
- package/src/skills/picopilot-code/SKILL.md +97 -0
- package/src/skills/picopilot-code/reference/README.md +42 -0
- package/src/skills/picopilot-code/reference/mode7-racing.md +95 -0
- package/src/skills/picopilot-code/reference/platformer.md +119 -0
- package/src/skills/picopilot-code/reference/puzzle-grid.md +106 -0
- package/src/skills/picopilot-code/reference/rpg-menus-dialog.md +122 -0
- package/src/skills/picopilot-code/reference/top-down-adventure.md +117 -0
- package/src/skills/picopilot-code/reference/twin-stick-arcade.md +128 -0
- package/src/skills/picopilot-debug/SKILL.md +195 -0
- package/src/skills/picopilot-overview/SKILL.md +52 -0
- package/src/version.ts +22 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tiny, dependency-FREE, deterministic PNG encoder for the `gfx render` JUDGE
|
|
3
|
+
* surface. Encodes an RGB pixel buffer into a valid 8-bit truecolour PNG using
|
|
4
|
+
* hand-rolled CRC32, Adler32, and STORED (uncompressed) zlib deflate blocks.
|
|
5
|
+
*
|
|
6
|
+
* Why hand-rolled + stored blocks (recorded as a `gfx render` encoder choice):
|
|
7
|
+
* the eyes-loop is meant to stay dependency-light (shrinko-free, no npm PNG lib),
|
|
8
|
+
* and `gfx render` output is tiny (~256x256), so compression buys nothing worth a
|
|
9
|
+
* dependency. STORED deflate blocks keep the encoder ~a page of code AND make the
|
|
10
|
+
* output BYTE-DETERMINISTIC (no compressor heuristics), which is exactly what the
|
|
11
|
+
* pixel-byte tests assert against. A standard PNG decoder reads stored blocks
|
|
12
|
+
* identically to compressed ones, so the file is a normal, viewable PNG.
|
|
13
|
+
*
|
|
14
|
+
* This module knows nothing about PICO-8: it takes raw RGB and dimensions. The
|
|
15
|
+
* palette mapping + upscale live in `render.ts`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** A raw RGB image: `width * height * 3` bytes, row-major, no padding. */
|
|
19
|
+
export interface RgbImage {
|
|
20
|
+
readonly width: number;
|
|
21
|
+
readonly height: number;
|
|
22
|
+
/** `rgb[(y * width + x) * 3 + {0,1,2}]` = R, G, B (0..255) for pixel (x, y). */
|
|
23
|
+
readonly rgb: Uint8Array;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PNG_SIGNATURE = Uint8Array.from([
|
|
27
|
+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Encodes an {@link RgbImage} into a complete PNG file (signature + IHDR + IDAT +
|
|
32
|
+
* IEND) as a `Uint8Array`. 8-bit RGB truecolour (colour type 2), no interlace,
|
|
33
|
+
* each scanline prefixed with filter byte 0 (None) so the pixel bytes appear
|
|
34
|
+
* verbatim in the IDAT payload.
|
|
35
|
+
*/
|
|
36
|
+
export function encodePng(image: RgbImage): Uint8Array {
|
|
37
|
+
const {width, height, rgb} = image;
|
|
38
|
+
if (
|
|
39
|
+
!Number.isInteger(width) ||
|
|
40
|
+
!Number.isInteger(height) ||
|
|
41
|
+
width <= 0 ||
|
|
42
|
+
height <= 0
|
|
43
|
+
) {
|
|
44
|
+
throw new RangeError(
|
|
45
|
+
`PNG dimensions must be positive integers, got ${width}x${height}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (rgb.length !== width * height * 3) {
|
|
49
|
+
throw new RangeError(
|
|
50
|
+
`RGB buffer length ${rgb.length} does not match ${width}x${height}x3 = ${width * height * 3}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Raw scanlines: each row is one filter byte (0 = None) then width*3 RGB bytes.
|
|
55
|
+
const stride = width * 3;
|
|
56
|
+
const raw = new Uint8Array(height * (stride + 1));
|
|
57
|
+
for (let y = 0; y < height; y++) {
|
|
58
|
+
const dst = y * (stride + 1);
|
|
59
|
+
raw[dst] = 0; // filter type None
|
|
60
|
+
raw.set(rgb.subarray(y * stride, y * stride + stride), dst + 1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const ihdr = buildIhdr(width, height);
|
|
64
|
+
const idat = zlibStore(raw);
|
|
65
|
+
|
|
66
|
+
const chunks = [
|
|
67
|
+
chunk('IHDR', ihdr),
|
|
68
|
+
chunk('IDAT', idat),
|
|
69
|
+
chunk('IEND', new Uint8Array(0)),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
let total = PNG_SIGNATURE.length;
|
|
73
|
+
for (const c of chunks) total += c.length;
|
|
74
|
+
const out = new Uint8Array(total);
|
|
75
|
+
let off = 0;
|
|
76
|
+
out.set(PNG_SIGNATURE, off);
|
|
77
|
+
off += PNG_SIGNATURE.length;
|
|
78
|
+
for (const c of chunks) {
|
|
79
|
+
out.set(c, off);
|
|
80
|
+
off += c.length;
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The 13-byte IHDR data: width, height, bit-depth 8, colour type 2 (RGB), etc. */
|
|
86
|
+
function buildIhdr(width: number, height: number): Uint8Array {
|
|
87
|
+
const d = new Uint8Array(13);
|
|
88
|
+
writeUint32(d, 0, width);
|
|
89
|
+
writeUint32(d, 4, height);
|
|
90
|
+
d[8] = 8; // bit depth
|
|
91
|
+
d[9] = 2; // colour type: truecolour RGB
|
|
92
|
+
d[10] = 0; // compression: deflate
|
|
93
|
+
d[11] = 0; // filter: adaptive (only filter 0 used per scanline)
|
|
94
|
+
d[12] = 0; // interlace: none
|
|
95
|
+
return d;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Wraps chunk `data` with its 4-byte length, 4-byte type, and 4-byte CRC32. */
|
|
99
|
+
function chunk(type: string, data: Uint8Array): Uint8Array {
|
|
100
|
+
const typeBytes = Uint8Array.from([
|
|
101
|
+
type.charCodeAt(0),
|
|
102
|
+
type.charCodeAt(1),
|
|
103
|
+
type.charCodeAt(2),
|
|
104
|
+
type.charCodeAt(3),
|
|
105
|
+
]);
|
|
106
|
+
const out = new Uint8Array(4 + 4 + data.length + 4);
|
|
107
|
+
writeUint32(out, 0, data.length);
|
|
108
|
+
out.set(typeBytes, 4);
|
|
109
|
+
out.set(data, 8);
|
|
110
|
+
// CRC covers the type + data (not the length).
|
|
111
|
+
const crcInput = new Uint8Array(4 + data.length);
|
|
112
|
+
crcInput.set(typeBytes, 0);
|
|
113
|
+
crcInput.set(data, 4);
|
|
114
|
+
writeUint32(out, 8 + data.length, crc32(crcInput));
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Wraps `data` in a zlib stream (RFC 1950) using DEFLATE STORED blocks (RFC 1951,
|
|
120
|
+
* BTYPE=00): a 2-byte zlib header, one or more stored blocks (each ≤ 65535
|
|
121
|
+
* bytes), and a 4-byte big-endian Adler32 of the uncompressed data.
|
|
122
|
+
*/
|
|
123
|
+
function zlibStore(data: Uint8Array): Uint8Array {
|
|
124
|
+
const MAX_BLOCK = 0xffff;
|
|
125
|
+
const blockCount = Math.max(1, Math.ceil(data.length / MAX_BLOCK));
|
|
126
|
+
// 2 header bytes + per-block (1 BFINAL/BTYPE + 2 LEN + 2 NLEN) + data + 4 adler.
|
|
127
|
+
const out = new Uint8Array(2 + blockCount * 5 + data.length + 4);
|
|
128
|
+
let off = 0;
|
|
129
|
+
|
|
130
|
+
// zlib header: CMF=0x78 (deflate, 32K window), FLG=0x01 so (CMF*256+FLG)%31==0.
|
|
131
|
+
out[off++] = 0x78;
|
|
132
|
+
out[off++] = 0x01;
|
|
133
|
+
|
|
134
|
+
let pos = 0;
|
|
135
|
+
for (let b = 0; b < blockCount; b++) {
|
|
136
|
+
const len = Math.min(MAX_BLOCK, data.length - pos);
|
|
137
|
+
const isFinal = b === blockCount - 1;
|
|
138
|
+
out[off++] = isFinal ? 1 : 0; // BFINAL bit, BTYPE=00 (stored)
|
|
139
|
+
// LEN then NLEN, both little-endian.
|
|
140
|
+
out[off++] = len & 0xff;
|
|
141
|
+
out[off++] = (len >> 8) & 0xff;
|
|
142
|
+
const nlen = ~len & 0xffff;
|
|
143
|
+
out[off++] = nlen & 0xff;
|
|
144
|
+
out[off++] = (nlen >> 8) & 0xff;
|
|
145
|
+
out.set(data.subarray(pos, pos + len), off);
|
|
146
|
+
off += len;
|
|
147
|
+
pos += len;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
writeUint32(out, off, adler32(data));
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function writeUint32(buf: Uint8Array, offset: number, value: number): void {
|
|
155
|
+
buf[offset] = (value >>> 24) & 0xff;
|
|
156
|
+
buf[offset + 1] = (value >>> 16) & 0xff;
|
|
157
|
+
buf[offset + 2] = (value >>> 8) & 0xff;
|
|
158
|
+
buf[offset + 3] = value & 0xff;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- CRC32 (PNG polynomial 0xedb88320) ---
|
|
162
|
+
|
|
163
|
+
const CRC_TABLE = buildCrcTable();
|
|
164
|
+
|
|
165
|
+
function buildCrcTable(): Uint32Array {
|
|
166
|
+
const table = new Uint32Array(256);
|
|
167
|
+
for (let n = 0; n < 256; n++) {
|
|
168
|
+
let c = n;
|
|
169
|
+
for (let k = 0; k < 8; k++) {
|
|
170
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
171
|
+
}
|
|
172
|
+
table[n] = c >>> 0;
|
|
173
|
+
}
|
|
174
|
+
return table;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function crc32(data: Uint8Array): number {
|
|
178
|
+
let crc = 0xffffffff;
|
|
179
|
+
for (let i = 0; i < data.length; i++) {
|
|
180
|
+
crc = CRC_TABLE[(crc ^ (data[i] ?? 0)) & 0xff]! ^ (crc >>> 8);
|
|
181
|
+
}
|
|
182
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Adler32 (RFC 1950) ---
|
|
186
|
+
|
|
187
|
+
function adler32(data: Uint8Array): number {
|
|
188
|
+
const MOD = 65521;
|
|
189
|
+
let a = 1;
|
|
190
|
+
let b = 0;
|
|
191
|
+
for (let i = 0; i < data.length; i++) {
|
|
192
|
+
a = (a + (data[i] ?? 0)) % MOD;
|
|
193
|
+
b = (b + a) % MOD;
|
|
194
|
+
}
|
|
195
|
+
return ((b << 16) | a) >>> 0;
|
|
196
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import {inflateSync} from 'node:zlib';
|
|
2
|
+
import {describe, expect, it} from 'vitest';
|
|
3
|
+
|
|
4
|
+
import {GfxSheet} from '../cart/index.js';
|
|
5
|
+
import {PICO8_PALETTE} from './palette.js';
|
|
6
|
+
import type {RgbImage} from './png.js';
|
|
7
|
+
import {
|
|
8
|
+
renderSheetPng,
|
|
9
|
+
renderSpritePng,
|
|
10
|
+
SHEET_RENDER_SCALE,
|
|
11
|
+
SPRITE_RENDER_SCALE,
|
|
12
|
+
upscale,
|
|
13
|
+
} from './render.js';
|
|
14
|
+
|
|
15
|
+
/** Decodes a PNG (via node's independent inflate) into a raw RGB image. */
|
|
16
|
+
function decodePng(bytes: Uint8Array): RgbImage {
|
|
17
|
+
let off = 8;
|
|
18
|
+
let width = 0;
|
|
19
|
+
let height = 0;
|
|
20
|
+
const idatParts: Buffer[] = [];
|
|
21
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
22
|
+
while (off < bytes.length) {
|
|
23
|
+
const len = view.getUint32(off);
|
|
24
|
+
const type = String.fromCharCode(
|
|
25
|
+
bytes[off + 4]!,
|
|
26
|
+
bytes[off + 5]!,
|
|
27
|
+
bytes[off + 6]!,
|
|
28
|
+
bytes[off + 7]!,
|
|
29
|
+
);
|
|
30
|
+
const data = bytes.subarray(off + 8, off + 8 + len);
|
|
31
|
+
if (type === 'IHDR') {
|
|
32
|
+
const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
33
|
+
width = dv.getUint32(0);
|
|
34
|
+
height = dv.getUint32(4);
|
|
35
|
+
} else if (type === 'IDAT') {
|
|
36
|
+
idatParts.push(Buffer.from(data));
|
|
37
|
+
}
|
|
38
|
+
off += 12 + len;
|
|
39
|
+
if (type === 'IEND') break;
|
|
40
|
+
}
|
|
41
|
+
const raw = new Uint8Array(inflateSync(Buffer.concat(idatParts)));
|
|
42
|
+
const stride = width * 3;
|
|
43
|
+
const rgb = new Uint8Array(width * height * 3);
|
|
44
|
+
for (let y = 0; y < height; y++) {
|
|
45
|
+
rgb.set(
|
|
46
|
+
raw.subarray(y * (stride + 1) + 1, y * (stride + 1) + 1 + stride),
|
|
47
|
+
y * stride,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return {width, height, rgb};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function pixelAt(
|
|
54
|
+
img: RgbImage,
|
|
55
|
+
x: number,
|
|
56
|
+
y: number,
|
|
57
|
+
): [number, number, number] {
|
|
58
|
+
const off = (y * img.width + x) * 3;
|
|
59
|
+
return [img.rgb[off]!, img.rgb[off + 1]!, img.rgb[off + 2]!];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function rgbOf(index: number): [number, number, number] {
|
|
63
|
+
const c = PICO8_PALETTE[index]!;
|
|
64
|
+
return [c.r, c.g, c.b];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** An 8x8 nibble grid from a generator. */
|
|
68
|
+
function makeGrid(fn: (r: number, c: number) => number): number[][] {
|
|
69
|
+
return Array.from({length: 8}, (_, r) =>
|
|
70
|
+
Array.from({length: 8}, (_, c) => fn(r, c)),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe('upscale (nearest-neighbour, palette-accurate)', () => {
|
|
75
|
+
it('paints each source pixel as a scale x scale block of its palette RGB', () => {
|
|
76
|
+
// 2x1 source: index 8 (red) then index 12 (blue), scale 4.
|
|
77
|
+
const img = upscale(2, 1, (x) => (x === 0 ? 8 : 12), 4);
|
|
78
|
+
expect(img.width).toBe(8);
|
|
79
|
+
expect(img.height).toBe(4);
|
|
80
|
+
// The whole left 4x4 block is red; the whole right 4x4 block is blue.
|
|
81
|
+
for (let y = 0; y < 4; y++) {
|
|
82
|
+
for (let x = 0; x < 4; x++) expect(pixelAt(img, x, y)).toEqual(rgbOf(8));
|
|
83
|
+
for (let x = 4; x < 8; x++) expect(pixelAt(img, x, y)).toEqual(rgbOf(12));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('renderSpritePng: deterministic palette-accurate pixel bytes', () => {
|
|
89
|
+
it('upscales an 8x8 sprite to 256x256 with the exact palette RGB per index', () => {
|
|
90
|
+
// Paint sprite 0 so pixel (c, r) has colour index (r*8 + c) % 16 — every
|
|
91
|
+
// index appears, at a known position.
|
|
92
|
+
const painted = makeGrid((r, c) => (r * 8 + c) % 16);
|
|
93
|
+
const sheet = GfxSheet.fromBody(undefined);
|
|
94
|
+
sheet.setSprite(0, painted);
|
|
95
|
+
|
|
96
|
+
const png = renderSpritePng(sheet, 0);
|
|
97
|
+
const img = decodePng(png);
|
|
98
|
+
expect(img.width).toBe(8 * SPRITE_RENDER_SCALE);
|
|
99
|
+
expect(img.height).toBe(8 * SPRITE_RENDER_SCALE);
|
|
100
|
+
expect(img.width).toBe(256);
|
|
101
|
+
|
|
102
|
+
// Assert the PIXEL BYTES: the centre of each source-pixel block is exactly
|
|
103
|
+
// that index's palette RGB.
|
|
104
|
+
const s = SPRITE_RENDER_SCALE;
|
|
105
|
+
for (let r = 0; r < 8; r++) {
|
|
106
|
+
for (let c = 0; c < 8; c++) {
|
|
107
|
+
const index = (r * 8 + c) % 16;
|
|
108
|
+
const cx = c * s + (s >> 1);
|
|
109
|
+
const cy = r * s + (s >> 1);
|
|
110
|
+
expect(pixelAt(img, cx, cy)).toEqual(rgbOf(index));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('renders a non-zero sprite (e.g. 5) from its own origin, not sprite 0', () => {
|
|
116
|
+
const sheet = GfxSheet.fromBody(undefined);
|
|
117
|
+
sheet.setSprite(
|
|
118
|
+
0,
|
|
119
|
+
makeGrid(() => 8),
|
|
120
|
+
); // sprite 0 is all red — must NOT leak in
|
|
121
|
+
sheet.setSprite(
|
|
122
|
+
5,
|
|
123
|
+
makeGrid(() => 11),
|
|
124
|
+
); // sprite 5 is all green
|
|
125
|
+
|
|
126
|
+
const img = decodePng(renderSpritePng(sheet, 5));
|
|
127
|
+
// Every pixel is green (index 11), proving sprite 5's origin was used.
|
|
128
|
+
for (let y = 0; y < img.height; y += 37) {
|
|
129
|
+
for (let x = 0; x < img.width; x += 37) {
|
|
130
|
+
expect(pixelAt(img, x, y)).toEqual(rgbOf(11));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('is byte-deterministic for the same sprite', () => {
|
|
136
|
+
const sheet = GfxSheet.fromBody(undefined);
|
|
137
|
+
sheet.setSprite(
|
|
138
|
+
0,
|
|
139
|
+
makeGrid((r, c) => (r + c) % 16),
|
|
140
|
+
);
|
|
141
|
+
const a = renderSpritePng(sheet, 0);
|
|
142
|
+
const b = renderSpritePng(sheet, 0);
|
|
143
|
+
expect(Array.from(a)).toEqual(Array.from(b));
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('renderSheetPng: the whole 128x128 spritesheet', () => {
|
|
148
|
+
it('upscales the 128x128 sheet to 256x256 with palette-accurate pixels', () => {
|
|
149
|
+
const sheet = GfxSheet.fromBody(undefined);
|
|
150
|
+
// Distinct colours in distinct sprites, so positions are checkable.
|
|
151
|
+
sheet.setSprite(
|
|
152
|
+
0,
|
|
153
|
+
makeGrid(() => 8),
|
|
154
|
+
); // top-left 8x8 (upscaled) = red
|
|
155
|
+
sheet.setSprite(
|
|
156
|
+
15,
|
|
157
|
+
makeGrid(() => 12),
|
|
158
|
+
); // top-right sprite = blue
|
|
159
|
+
sheet.setSprite(
|
|
160
|
+
255,
|
|
161
|
+
makeGrid(() => 10),
|
|
162
|
+
); // bottom-right sprite = yellow
|
|
163
|
+
|
|
164
|
+
const img = decodePng(renderSheetPng(sheet));
|
|
165
|
+
expect(img.width).toBe(128 * SHEET_RENDER_SCALE);
|
|
166
|
+
expect(img.height).toBe(128 * SHEET_RENDER_SCALE);
|
|
167
|
+
expect(img.width).toBe(256);
|
|
168
|
+
|
|
169
|
+
const s = SHEET_RENDER_SCALE;
|
|
170
|
+
// sprite 0 occupies source pixels [0,8)x[0,8) → dest [0,16)x[0,16).
|
|
171
|
+
expect(pixelAt(img, 0, 0)).toEqual(rgbOf(8));
|
|
172
|
+
expect(pixelAt(img, 8 * s - 1, 8 * s - 1)).toEqual(rgbOf(8));
|
|
173
|
+
// sprite 15 is source cols [120,128) row [0,8) → dest x [240,256).
|
|
174
|
+
expect(pixelAt(img, 120 * s, 0)).toEqual(rgbOf(12));
|
|
175
|
+
// sprite 255 is the bottom-right 8x8 → dest bottom-right corner.
|
|
176
|
+
expect(pixelAt(img, img.width - 1, img.height - 1)).toEqual(rgbOf(10));
|
|
177
|
+
// An untouched pixel (sprite 1 region) is index 0 = black.
|
|
178
|
+
expect(pixelAt(img, 8 * s, 0)).toEqual(rgbOf(0));
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GFX_HEIGHT,
|
|
3
|
+
GFX_WIDTH,
|
|
4
|
+
type GfxSheet,
|
|
5
|
+
SPRITE_SIZE,
|
|
6
|
+
spriteOrigin,
|
|
7
|
+
} from '../cart/index.js';
|
|
8
|
+
import {paletteColor} from './palette.js';
|
|
9
|
+
import {encodePng, type RgbImage} from './png.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The `gfx render` JUDGE surface: turn a sprite (or the whole spritesheet) into a
|
|
13
|
+
* nearest-neighbour-upscaled, palette-accurate PNG a multimodal agent can LOOK at.
|
|
14
|
+
*
|
|
15
|
+
* The char grid (`gfx show`/`gfx set`) is the exact EDIT surface, but an LLM reads
|
|
16
|
+
* serialized hex poorly for gestalt/colour (the SVG parallel: models generate
|
|
17
|
+
* structured visual source well but read it back poorly). So this renders the
|
|
18
|
+
* ACTUAL pixels, upscaled enough to be viewable, with each colour index mapped to
|
|
19
|
+
* its exact PICO-8 palette RGB (single source of truth: {@link paletteColor}).
|
|
20
|
+
*
|
|
21
|
+
* Upscale is NEAREST-NEIGHBOUR (each source pixel becomes a `scale`x`scale` block
|
|
22
|
+
* of identical RGB) so the pixel-art stays crisp and the output bytes are
|
|
23
|
+
* deterministic. The scale is chosen so the longer edge lands near ~256px:
|
|
24
|
+
* - a single 8x8 sprite → scale 32 → 256x256 (see {@link SPRITE_RENDER_SCALE}).
|
|
25
|
+
* - the 128x128 sheet → scale 2 → 256x256 (see {@link SHEET_RENDER_SCALE}).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Nearest-neighbour scale for a single 8x8 sprite: 8 * 32 = 256px. */
|
|
29
|
+
export const SPRITE_RENDER_SCALE = 32;
|
|
30
|
+
|
|
31
|
+
/** Nearest-neighbour scale for the 128x128 spritesheet: 128 * 2 = 256px. */
|
|
32
|
+
export const SHEET_RENDER_SCALE = 2;
|
|
33
|
+
|
|
34
|
+
/** The target viewable edge length both scales aim for (~256px). */
|
|
35
|
+
export const RENDER_TARGET_PX = 256;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Renders one sprite (0..255) to a PNG buffer: its 8x8 pixels, each mapped to its
|
|
39
|
+
* palette RGB, nearest-neighbour upscaled by {@link SPRITE_RENDER_SCALE}.
|
|
40
|
+
*/
|
|
41
|
+
export function renderSpritePng(sheet: GfxSheet, n: number): Uint8Array {
|
|
42
|
+
const {x0, y0} = spriteOrigin(n);
|
|
43
|
+
return encodePng(
|
|
44
|
+
upscale(
|
|
45
|
+
SPRITE_SIZE,
|
|
46
|
+
SPRITE_SIZE,
|
|
47
|
+
(x, y) => sheet.getPixel(x0 + x, y0 + y),
|
|
48
|
+
SPRITE_RENDER_SCALE,
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Renders the whole 128x128 spritesheet to a PNG buffer: every pixel mapped to
|
|
55
|
+
* its palette RGB, nearest-neighbour upscaled by {@link SHEET_RENDER_SCALE}.
|
|
56
|
+
*/
|
|
57
|
+
export function renderSheetPng(sheet: GfxSheet): Uint8Array {
|
|
58
|
+
return encodePng(
|
|
59
|
+
upscale(
|
|
60
|
+
GFX_WIDTH,
|
|
61
|
+
GFX_HEIGHT,
|
|
62
|
+
(x, y) => sheet.getPixel(x, y),
|
|
63
|
+
SHEET_RENDER_SCALE,
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Builds an upscaled {@link RgbImage} from a source of colour indices. Each
|
|
70
|
+
* source pixel (`getIndex(x, y)`) is mapped to its palette RGB and painted as a
|
|
71
|
+
* `scale`x`scale` block (nearest-neighbour), producing a `(srcW*scale)` x
|
|
72
|
+
* `(srcH*scale)` RGB buffer.
|
|
73
|
+
*/
|
|
74
|
+
export function upscale(
|
|
75
|
+
srcWidth: number,
|
|
76
|
+
srcHeight: number,
|
|
77
|
+
getIndex: (x: number, y: number) => number,
|
|
78
|
+
scale: number,
|
|
79
|
+
): RgbImage {
|
|
80
|
+
if (!Number.isInteger(scale) || scale < 1) {
|
|
81
|
+
throw new RangeError(
|
|
82
|
+
`upscale factor must be a positive integer, got ${scale}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const width = srcWidth * scale;
|
|
86
|
+
const height = srcHeight * scale;
|
|
87
|
+
const rgb = new Uint8Array(width * height * 3);
|
|
88
|
+
|
|
89
|
+
for (let sy = 0; sy < srcHeight; sy++) {
|
|
90
|
+
for (let sx = 0; sx < srcWidth; sx++) {
|
|
91
|
+
const {r, g, b} = paletteColor(getIndex(sx, sy));
|
|
92
|
+
// Paint the scale x scale destination block for this source pixel.
|
|
93
|
+
for (let dy = 0; dy < scale; dy++) {
|
|
94
|
+
const y = sy * scale + dy;
|
|
95
|
+
for (let dx = 0; dx < scale; dx++) {
|
|
96
|
+
const x = sx * scale + dx;
|
|
97
|
+
const off = (y * width + x) * 3;
|
|
98
|
+
rgb[off] = r;
|
|
99
|
+
rgb[off + 1] = g;
|
|
100
|
+
rgb[off + 2] = b;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {width, height, rgb};
|
|
106
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The picopilot engine layer: pure codecs and external-tool adapters, kept
|
|
3
|
+
* separate from the incur command layer (`src/commands/`) so commands stay thin
|
|
4
|
+
* and the engine stays independently testable.
|
|
5
|
+
*
|
|
6
|
+
* Modules (one per seam; more added by later tasks):
|
|
7
|
+
*
|
|
8
|
+
* - `engine/cart`: the `.p8` cart model: parse sections, addressable
|
|
9
|
+
* gfx/map regions, byte-identical serialize (this seam).
|
|
10
|
+
* - `engine/gfx`: TS codec: `__gfx__` hex to/from char grid, and hex to PNG.
|
|
11
|
+
* - `engine/audio`: TS codec: picopilot-MML to `__sfx__`, patterns to `__music__`.
|
|
12
|
+
* - `engine/shrinko`: adapter shelling out to the external `shrinko8` tool
|
|
13
|
+
* (token count, lint, minify, p8/lua, spritesheet PNG).
|
|
14
|
+
* - `engine/pico8`: headless run + capture + export (needs the PICO-8 binary).
|
|
15
|
+
*
|
|
16
|
+
* Later tasks re-export from here without restructuring.
|
|
17
|
+
*/
|
|
18
|
+
export * from './audio/index.js';
|
|
19
|
+
export * from './cart/index.js';
|
|
20
|
+
export * from './pico8/index.js';
|
|
21
|
+
export * from './shrinko/index.js';
|