picopilot 0.0.0 → 0.1.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/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 +202 -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 +217 -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 +460 -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 +238 -0
- package/src/commands/run.ts +259 -0
- package/src/commands/serve.test.ts +218 -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 +236 -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,213 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { z } from 'incur';
|
|
4
|
+
import { Cart, CartParseError } from '../engine/cart/index.js';
|
|
5
|
+
import { ShrinkoParseError, ShellShrinkoAdapter, } from '../engine/shrinko/index.js';
|
|
6
|
+
import { TOKEN_BUDGET } from './tokens.js';
|
|
7
|
+
/**
|
|
8
|
+
* The one-line self-scoping the `picopilot verify` envelope always carries, so
|
|
9
|
+
* the connotation of "verify" never misleads an agent into treating a green
|
|
10
|
+
* static gate as terminal (ADR-0003). It states BOTH facts: verify is static,
|
|
11
|
+
* and passing does NOT mean the cart runs.
|
|
12
|
+
*/
|
|
13
|
+
export const VERIFY_SCOPE = 'static gate: tokens + integrity; passing does NOT mean the cart runs';
|
|
14
|
+
/**
|
|
15
|
+
* The exit code for the DISTINCT `gate-incapable` outcome (shrinko absent).
|
|
16
|
+
* Chosen as 2 to be categorically separate from `fail` (exit 1, a cart that
|
|
17
|
+
* flunked a check) and from `pass` (exit 0): an absent capability is neither a
|
|
18
|
+
* green gate nor a failed check but a gate that could not run its most
|
|
19
|
+
* important check, so it gets its own nonzero code an agent can branch on.
|
|
20
|
+
*/
|
|
21
|
+
export const GATE_INCAPABLE_EXIT = 2;
|
|
22
|
+
const defaultAdapterFactory = (env) => new ShellShrinkoAdapter({ env });
|
|
23
|
+
/**
|
|
24
|
+
* Runs the STATIC cart-integrity check against `text`: the cart must parse into
|
|
25
|
+
* the cart model and round-trip cleanly (`Cart.parse(text).serialize() ===
|
|
26
|
+
* text`). This is a purely static, well-formedness check — it never runs the
|
|
27
|
+
* cart. A {@link CartParseError} (malformed header/version/section) or a
|
|
28
|
+
* round-trip mismatch is a fail with a human-readable detail.
|
|
29
|
+
*/
|
|
30
|
+
function checkIntegrity(text) {
|
|
31
|
+
let cart;
|
|
32
|
+
try {
|
|
33
|
+
cart = Cart.parse(text);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
if (e instanceof CartParseError) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
detail: `cart does not parse (${e.code}): ${e.message}`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
if (cart.serialize() !== text) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
detail: 'cart does not round-trip cleanly through the cart model (malformed sections)',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { ok: true };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Registers `picopilot verify` (US #15, #18), the STATIC cart-acceptance gate an
|
|
54
|
+
* agent drives each iteration toward.
|
|
55
|
+
*
|
|
56
|
+
* It runs two static checks and returns ONE structured envelope:
|
|
57
|
+
*
|
|
58
|
+
* - **integrity** — the cart parses and round-trips cleanly through the cart
|
|
59
|
+
* model (well-formed sections). No run.
|
|
60
|
+
* - **tokens** — the cart is within the 8,192-token budget (via the shrinko
|
|
61
|
+
* adapter `--count`).
|
|
62
|
+
*
|
|
63
|
+
* (Lint folds in when the lint task lands; in v1-core verify = tokens +
|
|
64
|
+
* integrity.)
|
|
65
|
+
*
|
|
66
|
+
* Three categorical outcomes (ADR-0003):
|
|
67
|
+
*
|
|
68
|
+
* - **pass** (exit 0) — both checks green. The envelope SELF-SCOPES (states it
|
|
69
|
+
* is static and that passing does not mean the cart runs) and CTAs to
|
|
70
|
+
* `picopilot run` ("static checks pass, now confirm it boots"), so the agent
|
|
71
|
+
* is led from well-formed to actually-runs.
|
|
72
|
+
* - **fail** (exit 1) — a check flunked (over budget, or a malformed/non-parsing
|
|
73
|
+
* cart). A structured `verify-failed` error listing which checks failed.
|
|
74
|
+
* - **gate-incapable** (exit {@link GATE_INCAPABLE_EXIT}) — shrinko is ABSENT,
|
|
75
|
+
* so the token check (the #1 failure mode, token bloat) cannot run. verify
|
|
76
|
+
* returns a DISTINCT `gate-incapable` result, NEVER green, categorically
|
|
77
|
+
* separate from pass and fail. This is the gate-as-theatre regression guard:
|
|
78
|
+
* verify must never report green by skipping its most important check.
|
|
79
|
+
*
|
|
80
|
+
* verify is STATIC and never runs the cart, so it has NO PICO-8 dependency (US
|
|
81
|
+
* #19). It reaches shrinko entirely through the {@link ShrinkoAdapter} seam, so
|
|
82
|
+
* a native-TS shrinko would satisfy it unchanged.
|
|
83
|
+
*
|
|
84
|
+
* @param adapterFactory injects the adapter (defaults to the shell adapter);
|
|
85
|
+
* tests pass a stub to drive present / absent / over-budget without the real
|
|
86
|
+
* binary.
|
|
87
|
+
*/
|
|
88
|
+
export function registerVerify(cli, adapterFactory = defaultAdapterFactory) {
|
|
89
|
+
cli.command('verify', {
|
|
90
|
+
description: 'Static acceptance gate: run tokens + cart-integrity and return one pass/fail envelope. Does NOT run the cart. Requires shrinko.',
|
|
91
|
+
args: z.object({
|
|
92
|
+
cart: z
|
|
93
|
+
.string()
|
|
94
|
+
.default('main.p8')
|
|
95
|
+
.describe('The .p8 cart to verify. Defaults to main.p8 in the current folder.'),
|
|
96
|
+
}),
|
|
97
|
+
// Declared so incur resolves it from the (test-overridable) env source and
|
|
98
|
+
// hands it to the shrinko child; verify itself never runs the cart, so it
|
|
99
|
+
// needs no PICO-8 env.
|
|
100
|
+
env: z.object({
|
|
101
|
+
PATH: z
|
|
102
|
+
.string()
|
|
103
|
+
.optional()
|
|
104
|
+
.describe('Used to locate shrinko8 in the child process.'),
|
|
105
|
+
}),
|
|
106
|
+
output: z.object({
|
|
107
|
+
status: z
|
|
108
|
+
.literal('pass')
|
|
109
|
+
.describe('The overall gate result. Only a green gate reaches ok.'),
|
|
110
|
+
scope: z
|
|
111
|
+
.string()
|
|
112
|
+
.describe('The self-scoping note: verify is static and passing does NOT mean the cart runs.'),
|
|
113
|
+
checks: z
|
|
114
|
+
.object({
|
|
115
|
+
integrity: z
|
|
116
|
+
.boolean()
|
|
117
|
+
.describe('True when the cart parses and round-trips cleanly.'),
|
|
118
|
+
tokens: z
|
|
119
|
+
.boolean()
|
|
120
|
+
.describe('True when the cart is within the 8,192-token budget.'),
|
|
121
|
+
})
|
|
122
|
+
.describe('Per-check pass/fail, so the agent sees WHAT was checked.'),
|
|
123
|
+
tokens: z.number().describe('The token count shrinko reported.'),
|
|
124
|
+
budget: z.number().describe('The token budget checked against (8192).'),
|
|
125
|
+
}),
|
|
126
|
+
examples: [
|
|
127
|
+
{ description: 'Verify main.p8' },
|
|
128
|
+
{ description: 'Verify a specific cart', args: { cart: 'game.p8' } },
|
|
129
|
+
],
|
|
130
|
+
async run({ args, env, error, ok }) {
|
|
131
|
+
const cartPath = isAbsolute(args.cart)
|
|
132
|
+
? args.cart
|
|
133
|
+
: resolve(process.cwd(), args.cart);
|
|
134
|
+
// A missing cart is a picopilot-side error, distinct from a fail: there is
|
|
135
|
+
// nothing to gate. Surface it clearly rather than reporting a hollow fail.
|
|
136
|
+
if (!existsSync(cartPath)) {
|
|
137
|
+
return error({
|
|
138
|
+
code: 'cart-not-found',
|
|
139
|
+
message: `no cart at ${cartPath}`,
|
|
140
|
+
exitCode: 1,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const text = readFileSync(cartPath, 'utf8');
|
|
144
|
+
// Integrity is a static, shrinko-free check: the cart parses + round-trips.
|
|
145
|
+
const integrity = checkIntegrity(text);
|
|
146
|
+
// Tokens goes through the shrinko seam. Absent shrinko is the gate-incapable
|
|
147
|
+
// trigger (below); a parse failure of shrinko's own output is shrinko-failed.
|
|
148
|
+
const adapter = adapterFactory(env);
|
|
149
|
+
let count;
|
|
150
|
+
try {
|
|
151
|
+
count = await adapter.count(cartPath);
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
if (e instanceof ShrinkoParseError) {
|
|
155
|
+
return error({
|
|
156
|
+
code: 'shrinko-failed',
|
|
157
|
+
message: `shrinko ran but its --count output could not be parsed: ${e.message}`,
|
|
158
|
+
exitCode: 1,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
throw e;
|
|
162
|
+
}
|
|
163
|
+
// GATE-INCAPABLE (ADR-0003): shrinko absent → verify CANNOT check the #1
|
|
164
|
+
// failure mode (token bloat). It returns a DISTINCT outcome, NEVER green,
|
|
165
|
+
// never a silent skip. This is the load-bearing regression guard: verify
|
|
166
|
+
// must not report pass by omitting its most important check.
|
|
167
|
+
if (!count.ok) {
|
|
168
|
+
return error({
|
|
169
|
+
code: 'gate-incapable',
|
|
170
|
+
message: `${VERIFY_SCOPE}. gate-incapable: cannot check tokens without shrinko, so verify will not report green. ${count.remedy} (needs: ${count.needs.join(', ')})`,
|
|
171
|
+
exitCode: GATE_INCAPABLE_EXIT,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const tokens = count.value.tokens;
|
|
175
|
+
const tokensOk = tokens <= TOKEN_BUDGET;
|
|
176
|
+
// FAIL (exit 1): a static check flunked. List every failing check so the
|
|
177
|
+
// agent knows what to fix, not just that something did.
|
|
178
|
+
if (!integrity.ok || !tokensOk) {
|
|
179
|
+
const failed = [];
|
|
180
|
+
if (!integrity.ok)
|
|
181
|
+
failed.push(`integrity: ${integrity.detail}`);
|
|
182
|
+
if (!tokensOk) {
|
|
183
|
+
failed.push(`tokens: ${tokens} exceeds the ${TOKEN_BUDGET}-token budget`);
|
|
184
|
+
}
|
|
185
|
+
return error({
|
|
186
|
+
code: 'verify-failed',
|
|
187
|
+
message: `${VERIFY_SCOPE}. verify FAILED: ${failed.join('; ')}`,
|
|
188
|
+
exitCode: 1,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// PASS (exit 0): both static checks green. Self-scope, then CTA to `run` so
|
|
192
|
+
// the agent is led from well-formed to actually-boots (ADR-0003 CTA).
|
|
193
|
+
return ok({
|
|
194
|
+
status: 'pass',
|
|
195
|
+
scope: VERIFY_SCOPE,
|
|
196
|
+
checks: { integrity: true, tokens: true },
|
|
197
|
+
tokens,
|
|
198
|
+
budget: TOKEN_BUDGET,
|
|
199
|
+
}, {
|
|
200
|
+
cta: {
|
|
201
|
+
description: 'Static checks pass. Now confirm the cart actually boots:',
|
|
202
|
+
commands: [
|
|
203
|
+
{
|
|
204
|
+
command: 'run',
|
|
205
|
+
description: 'Launch the cart headless to confirm it boots (verify does not run it).',
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Cli } from 'incur';
|
|
2
|
+
/**
|
|
3
|
+
* Registers the trivial `version` command onto the given CLI (or command group).
|
|
4
|
+
*
|
|
5
|
+
* This is the skeleton's proof-of-life command: it exercises the full incur
|
|
6
|
+
* pipeline (output schema, the structured envelope, TOON-by-default and `--json`
|
|
7
|
+
* formatting) with no engine dependencies, so the `verify` gate has something
|
|
8
|
+
* real to build and test. Later command groups mount alongside it via their own
|
|
9
|
+
* `register*` helpers, called from `createCli`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerVersion(cli: Cli.Cli): void;
|
|
12
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/commands/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAGlC;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,IAAI,CAWlD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'incur';
|
|
2
|
+
import { VERSION } from '../version.js';
|
|
3
|
+
/**
|
|
4
|
+
* Registers the trivial `version` command onto the given CLI (or command group).
|
|
5
|
+
*
|
|
6
|
+
* This is the skeleton's proof-of-life command: it exercises the full incur
|
|
7
|
+
* pipeline (output schema, the structured envelope, TOON-by-default and `--json`
|
|
8
|
+
* formatting) with no engine dependencies, so the `verify` gate has something
|
|
9
|
+
* real to build and test. Later command groups mount alongside it via their own
|
|
10
|
+
* `register*` helpers, called from `createCli`.
|
|
11
|
+
*/
|
|
12
|
+
export function registerVersion(cli) {
|
|
13
|
+
cli.command('version', {
|
|
14
|
+
description: 'Print the picopilot version.',
|
|
15
|
+
output: z.object({
|
|
16
|
+
name: z.string().describe('The tool name.'),
|
|
17
|
+
version: z.string().describe('The installed picopilot version.'),
|
|
18
|
+
}),
|
|
19
|
+
run() {
|
|
20
|
+
return { name: 'picopilot', version: VERSION };
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/commands/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,CAAC,EAAC,MAAM,OAAO,CAAC;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAEtC;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY;IAC3C,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE;QACtB,WAAW,EAAE,8BAA8B;QAC3C,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;YAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAC3C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;SAChE,CAAC;QACF,GAAG;YACF,OAAO,EAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAC,CAAC;QAC9C,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `engine/audio` — the shrinko-free, PICO-8-free audio codec. Two halves:
|
|
3
|
+
* the picopilot-MML to `__sfx__` transpiler (finding Part A/B, ADR-0005 +
|
|
4
|
+
* ADR-0008) and the structural pattern-list to `__music__` codec (finding A.5 /
|
|
5
|
+
* B.6, ADR-0005).
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the `engine/gfx` shape: a pure module + this index re-export, no
|
|
8
|
+
* adapter. The `sfx from-mml` command is thin wiring over {@link mmlToSfxRow};
|
|
9
|
+
* the `music from-patterns` command over {@link patternsToMusic}.
|
|
10
|
+
*/
|
|
11
|
+
export { mergeSfxRow, setMusicSection, SFX_SLOT_COUNT } from './section.js';
|
|
12
|
+
export { AudioMmlError, type AudioMmlErrorCode, DEFAULT_LENGTH, DEFAULT_OCTAVE, DEFAULT_SPEED, FILTER_BUZZ, FILTER_DAMPEN_LEVEL, FILTER_DETUNE_1, FILTER_DETUNE_2, FILTER_MAX_LEVEL, FILTER_NOIZ, FILTER_REVERB, mmlToSfxRow, PITCH_MAX, PITCH_MIN, type RowRoundingWarning, SFX_MAX_ROWS, SFX_ROW_LENGTH, type SfxTranspileResult, } from './sfx.js';
|
|
13
|
+
export { CHANNEL_OFF_BIT, type ChannelRef, FLAG_LOOP_BACK, FLAG_LOOP_START, FLAG_STOP, MUSIC_PATTERN_MAX, MusicError, type MusicErrorCode, type MusicTranspileResult, type MusicWarning, type Pattern, patternsToMusic, SFX_INDEX_MAX, } from './music.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/audio/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAC,WAAW,EAAE,eAAe,EAAE,cAAc,EAAC,MAAM,cAAc,CAAC;AAC1E,OAAO,EACN,aAAa,EACb,KAAK,iBAAiB,EACtB,cAAc,EACd,cAAc,EACd,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,WAAW,EACX,SAAS,EACT,SAAS,EACT,KAAK,kBAAkB,EACvB,YAAY,EACZ,cAAc,EACd,KAAK,kBAAkB,GACvB,MAAM,UAAU,CAAC;AAClB,OAAO,EACN,eAAe,EACf,KAAK,UAAU,EACf,cAAc,EACd,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,eAAe,EACf,aAAa,GACb,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `engine/audio` — the shrinko-free, PICO-8-free audio codec. Two halves:
|
|
3
|
+
* the picopilot-MML to `__sfx__` transpiler (finding Part A/B, ADR-0005 +
|
|
4
|
+
* ADR-0008) and the structural pattern-list to `__music__` codec (finding A.5 /
|
|
5
|
+
* B.6, ADR-0005).
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the `engine/gfx` shape: a pure module + this index re-export, no
|
|
8
|
+
* adapter. The `sfx from-mml` command is thin wiring over {@link mmlToSfxRow};
|
|
9
|
+
* the `music from-patterns` command over {@link patternsToMusic}.
|
|
10
|
+
*/
|
|
11
|
+
export { mergeSfxRow, setMusicSection, SFX_SLOT_COUNT } from './section.js';
|
|
12
|
+
export { AudioMmlError, DEFAULT_LENGTH, DEFAULT_OCTAVE, DEFAULT_SPEED, FILTER_BUZZ, FILTER_DAMPEN_LEVEL, FILTER_DETUNE_1, FILTER_DETUNE_2, FILTER_MAX_LEVEL, FILTER_NOIZ, FILTER_REVERB, mmlToSfxRow, PITCH_MAX, PITCH_MIN, SFX_MAX_ROWS, SFX_ROW_LENGTH, } from './sfx.js';
|
|
13
|
+
export { CHANNEL_OFF_BIT, FLAG_LOOP_BACK, FLAG_LOOP_START, FLAG_STOP, MUSIC_PATTERN_MAX, MusicError, patternsToMusic, SFX_INDEX_MAX, } from './music.js';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/audio/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAC,WAAW,EAAE,eAAe,EAAE,cAAc,EAAC,MAAM,cAAc,CAAC;AAC1E,OAAO,EACN,aAAa,EAEb,cAAc,EACd,cAAc,EACd,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,WAAW,EACX,SAAS,EACT,SAAS,EAET,YAAY,EACZ,cAAc,GAEd,MAAM,UAAU,CAAC;AAClB,OAAO,EACN,eAAe,EAEf,cAAc,EACd,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,UAAU,EAKV,eAAe,EACf,aAAa,GACb,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `engine/audio`: the shrinko-free structural pattern-list to `__music__` codec.
|
|
3
|
+
*
|
|
4
|
+
* `__music__` carries NO melodic content (that lives entirely in the referenced
|
|
5
|
+
* SFX built by `sfx from-mml`). A song is authored STRUCTURALLY: an ordered list
|
|
6
|
+
* of patterns, each naming up to 4 SFX-channel references plus the 3 pattern-flow
|
|
7
|
+
* flags. This module OWNS that pattern-list -> `__music__` text emission; the
|
|
8
|
+
* `music from-patterns` command is thin wiring over {@link patternsToMusic}.
|
|
9
|
+
*
|
|
10
|
+
* The layout is the CLOSED spec from the ADR-0005 spike, verified byte-for-byte
|
|
11
|
+
* against PICO-8 v0.2.7:
|
|
12
|
+
* `work/notes/findings/pico8-audio-sfx-music-layout-and-mml-subset.md` Part A.5
|
|
13
|
+
* (the `__music__` text row) + Part B.6 (the structural authoring model). Each
|
|
14
|
+
* pattern serializes to `<flagbyte> <4 channel bytes>` = `FF CCCCCCCC` (a 2-hex
|
|
15
|
+
* flag byte + space + 8 hex = 4 channel bytes). Patterns are listed top to
|
|
16
|
+
* bottom = song order.
|
|
17
|
+
*
|
|
18
|
+
* The load-bearing subtlety (finding B.6 / B.7 #9): "channel off" is NOT "sfx 0".
|
|
19
|
+
* An off channel sets bit6 (`0x40`) on its channel byte and is silent this
|
|
20
|
+
* pattern; sfx 0 is a real, playing SFX. The model keeps them DISTINCT: a channel
|
|
21
|
+
* entry is an SFX index 0..63 OR `null` (off).
|
|
22
|
+
*
|
|
23
|
+
* Pattern LENGTH is not stored here: PICO-8 ends a pattern when the LEFT-MOST
|
|
24
|
+
* non-looping channel's SFX finishes (finding B.6), so timing is INHERITED from
|
|
25
|
+
* the referenced SFX (its speed + LEN, set via `sfx from-mml`). The music model
|
|
26
|
+
* carries no per-pattern length by design.
|
|
27
|
+
*/
|
|
28
|
+
/** The flag bit set on the leading flag byte for a loop-start pattern (finding A.5). */
|
|
29
|
+
export declare const FLAG_LOOP_START = 1;
|
|
30
|
+
/** The flag bit for a loop-back pattern (jump back to loop-start at its end). */
|
|
31
|
+
export declare const FLAG_LOOP_BACK = 2;
|
|
32
|
+
/** The flag bit for a stop pattern (music stops after it). */
|
|
33
|
+
export declare const FLAG_STOP = 4;
|
|
34
|
+
/** The bit6 marker that makes a channel byte "off" (silent this pattern; finding A.5). */
|
|
35
|
+
export declare const CHANNEL_OFF_BIT = 64;
|
|
36
|
+
/** The highest valid SFX index a channel may reference (0..63; finding A.5). */
|
|
37
|
+
export declare const SFX_INDEX_MAX = 63;
|
|
38
|
+
/** The most patterns `__music__` holds (song positions 0..63; finding: 64 slots). */
|
|
39
|
+
export declare const MUSIC_PATTERN_MAX = 64;
|
|
40
|
+
/**
|
|
41
|
+
* One channel reference in a pattern: an SFX index 0..63, or `null` = "off"
|
|
42
|
+
* (bit6 set; the channel is silent this pattern). `null` and `0` are DISTINCT
|
|
43
|
+
* (finding B.6): 0 is a real, playing SFX; `null` is silence.
|
|
44
|
+
*/
|
|
45
|
+
export type ChannelRef = number | null;
|
|
46
|
+
/**
|
|
47
|
+
* One song pattern (finding B.6). `channels` is EXACTLY 4 entries (ch0..ch3);
|
|
48
|
+
* each is an SFX index 0..63 or `null` (off). The 3 flow flags are optional and
|
|
49
|
+
* default to false. Pattern order in the list = song order.
|
|
50
|
+
*/
|
|
51
|
+
export interface Pattern {
|
|
52
|
+
/** The 4 channel references (ch0..ch3): each an SFX index 0..63 or `null` (off). */
|
|
53
|
+
channels: [ChannelRef, ChannelRef, ChannelRef, ChannelRef];
|
|
54
|
+
/** Flag 0x01: a `music` loop-back searches back to the nearest of these (or pattern 0). */
|
|
55
|
+
loopStart?: boolean;
|
|
56
|
+
/** Flag 0x02: at the end of this pattern, jump back to the loop-start pattern. */
|
|
57
|
+
loopBack?: boolean;
|
|
58
|
+
/** Flag 0x04: music stops after this pattern. */
|
|
59
|
+
stop?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** Machine-readable reason a pattern list could not be transpiled. */
|
|
62
|
+
export type MusicErrorCode =
|
|
63
|
+
/** A channel referenced an SFX index outside 0..63 (finding A.5). */
|
|
64
|
+
'audio-music-sfx-out-of-range'
|
|
65
|
+
/** A pattern did not carry exactly 4 channel entries (finding A.5). */
|
|
66
|
+
| 'audio-music-bad-channel-count'
|
|
67
|
+
/** More than 64 patterns were supplied (the `__music__` slot count). */
|
|
68
|
+
| 'audio-music-too-many-patterns'
|
|
69
|
+
/** The pattern list was empty (nothing to emit). */
|
|
70
|
+
| 'audio-music-empty';
|
|
71
|
+
/**
|
|
72
|
+
* A structured pattern-list transpile failure. Mirrors {@link AudioMmlError}'s
|
|
73
|
+
* shape (a machine-readable `code` + an actionable `message`), surfaced by the
|
|
74
|
+
* `music from-patterns` command as incur's `error({code,...})` envelope with a
|
|
75
|
+
* nonzero exit. Never thrown for a case the spec allows (e.g. a loop-back with
|
|
76
|
+
* no loop-start, which WARNS, not errors).
|
|
77
|
+
*/
|
|
78
|
+
export declare class MusicError extends Error {
|
|
79
|
+
readonly code: MusicErrorCode;
|
|
80
|
+
constructor(code: MusicErrorCode, message: string);
|
|
81
|
+
}
|
|
82
|
+
/** A non-fatal advisory surfaced to the caller (finding B.6: loop-back w/o loop-start). */
|
|
83
|
+
export interface MusicWarning {
|
|
84
|
+
/** A machine handle for the advisory. */
|
|
85
|
+
code: 'loop-back-without-loop-start';
|
|
86
|
+
/** A human-readable, actionable explanation. */
|
|
87
|
+
message: string;
|
|
88
|
+
}
|
|
89
|
+
/** The successful result of transpiling a pattern list to `__music__`. */
|
|
90
|
+
export interface MusicTranspileResult {
|
|
91
|
+
/** The `__music__` section body: one `FF CCCCCCCC` row per pattern, `\n`-terminated. */
|
|
92
|
+
section: string;
|
|
93
|
+
/** The number of patterns emitted (= song length). */
|
|
94
|
+
patterns: number;
|
|
95
|
+
/** Any non-fatal advisories (e.g. a loop-back with no loop-start anywhere). */
|
|
96
|
+
warnings: MusicWarning[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Transpiles an ordered pattern list into a `__music__` section body (finding
|
|
100
|
+
* Part A.5 + B.6). Emits the friendly text rows directly (flow flags hoisted
|
|
101
|
+
* into the leading flag byte; bit6 set on off channels).
|
|
102
|
+
*
|
|
103
|
+
* Pattern order = song order. The result body is one `FF CCCCCCCC` row per
|
|
104
|
+
* pattern, `\n`-joined with a terminating newline (what PICO-8 writes).
|
|
105
|
+
*
|
|
106
|
+
* A `loopBack` flag anywhere with NO `loopStart` flag anywhere is ALLOWED (PICO-8
|
|
107
|
+
* falls back to pattern 0; finding B.6) but surfaces a {@link MusicWarning}, not
|
|
108
|
+
* an error. Pattern LENGTH is inherited from the referenced SFX and is not stored
|
|
109
|
+
* here (finding B.6).
|
|
110
|
+
*
|
|
111
|
+
* @throws {MusicError} `audio-music-empty` (no patterns),
|
|
112
|
+
* `audio-music-too-many-patterns` (>64), `audio-music-bad-channel-count` or
|
|
113
|
+
* `audio-music-sfx-out-of-range` (a malformed / out-of-range channel).
|
|
114
|
+
*/
|
|
115
|
+
export declare function patternsToMusic(patterns: Pattern[]): MusicTranspileResult;
|
|
116
|
+
//# sourceMappingURL=music.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"music.d.ts","sourceRoot":"","sources":["../../../src/engine/audio/music.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,wFAAwF;AACxF,eAAO,MAAM,eAAe,IAAO,CAAC;AAEpC,iFAAiF;AACjF,eAAO,MAAM,cAAc,IAAO,CAAC;AAEnC,8DAA8D;AAC9D,eAAO,MAAM,SAAS,IAAO,CAAC;AAE9B,0FAA0F;AAC1F,eAAO,MAAM,eAAe,KAAO,CAAC;AAEpC,gFAAgF;AAChF,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;AAEvC;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACvB,oFAAoF;IACpF,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IAC3D,2FAA2F;IAC3F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,sEAAsE;AACtE,MAAM,MAAM,cAAc;AACzB,qEAAqE;AACnE,8BAA8B;AAChC,uEAAuE;GACrE,+BAA+B;AACjC,wEAAwE;GACtE,+BAA+B;AACjC,oDAAoD;GAClD,mBAAmB,CAAC;AAEvB;;;;;;GAMG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;gBAElB,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM;CAKjD;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC5B,yCAAyC;IACzC,IAAI,EAAE,8BAA8B,CAAC;IACrC,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACpC,wFAAwF;IACxF,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,QAAQ,EAAE,YAAY,EAAE,CAAC;CACzB;AAuDD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,oBAAoB,CAgCzE"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `engine/audio`: the shrinko-free structural pattern-list to `__music__` codec.
|
|
3
|
+
*
|
|
4
|
+
* `__music__` carries NO melodic content (that lives entirely in the referenced
|
|
5
|
+
* SFX built by `sfx from-mml`). A song is authored STRUCTURALLY: an ordered list
|
|
6
|
+
* of patterns, each naming up to 4 SFX-channel references plus the 3 pattern-flow
|
|
7
|
+
* flags. This module OWNS that pattern-list -> `__music__` text emission; the
|
|
8
|
+
* `music from-patterns` command is thin wiring over {@link patternsToMusic}.
|
|
9
|
+
*
|
|
10
|
+
* The layout is the CLOSED spec from the ADR-0005 spike, verified byte-for-byte
|
|
11
|
+
* against PICO-8 v0.2.7:
|
|
12
|
+
* `work/notes/findings/pico8-audio-sfx-music-layout-and-mml-subset.md` Part A.5
|
|
13
|
+
* (the `__music__` text row) + Part B.6 (the structural authoring model). Each
|
|
14
|
+
* pattern serializes to `<flagbyte> <4 channel bytes>` = `FF CCCCCCCC` (a 2-hex
|
|
15
|
+
* flag byte + space + 8 hex = 4 channel bytes). Patterns are listed top to
|
|
16
|
+
* bottom = song order.
|
|
17
|
+
*
|
|
18
|
+
* The load-bearing subtlety (finding B.6 / B.7 #9): "channel off" is NOT "sfx 0".
|
|
19
|
+
* An off channel sets bit6 (`0x40`) on its channel byte and is silent this
|
|
20
|
+
* pattern; sfx 0 is a real, playing SFX. The model keeps them DISTINCT: a channel
|
|
21
|
+
* entry is an SFX index 0..63 OR `null` (off).
|
|
22
|
+
*
|
|
23
|
+
* Pattern LENGTH is not stored here: PICO-8 ends a pattern when the LEFT-MOST
|
|
24
|
+
* non-looping channel's SFX finishes (finding B.6), so timing is INHERITED from
|
|
25
|
+
* the referenced SFX (its speed + LEN, set via `sfx from-mml`). The music model
|
|
26
|
+
* carries no per-pattern length by design.
|
|
27
|
+
*/
|
|
28
|
+
/** The flag bit set on the leading flag byte for a loop-start pattern (finding A.5). */
|
|
29
|
+
export const FLAG_LOOP_START = 0x01;
|
|
30
|
+
/** The flag bit for a loop-back pattern (jump back to loop-start at its end). */
|
|
31
|
+
export const FLAG_LOOP_BACK = 0x02;
|
|
32
|
+
/** The flag bit for a stop pattern (music stops after it). */
|
|
33
|
+
export const FLAG_STOP = 0x04;
|
|
34
|
+
/** The bit6 marker that makes a channel byte "off" (silent this pattern; finding A.5). */
|
|
35
|
+
export const CHANNEL_OFF_BIT = 0x40;
|
|
36
|
+
/** The highest valid SFX index a channel may reference (0..63; finding A.5). */
|
|
37
|
+
export const SFX_INDEX_MAX = 63;
|
|
38
|
+
/** The most patterns `__music__` holds (song positions 0..63; finding: 64 slots). */
|
|
39
|
+
export const MUSIC_PATTERN_MAX = 64;
|
|
40
|
+
/**
|
|
41
|
+
* A structured pattern-list transpile failure. Mirrors {@link AudioMmlError}'s
|
|
42
|
+
* shape (a machine-readable `code` + an actionable `message`), surfaced by the
|
|
43
|
+
* `music from-patterns` command as incur's `error({code,...})` envelope with a
|
|
44
|
+
* nonzero exit. Never thrown for a case the spec allows (e.g. a loop-back with
|
|
45
|
+
* no loop-start, which WARNS, not errors).
|
|
46
|
+
*/
|
|
47
|
+
export class MusicError extends Error {
|
|
48
|
+
code;
|
|
49
|
+
constructor(code, message) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = 'MusicError';
|
|
52
|
+
this.code = code;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** A byte as two lowercase hex chars. */
|
|
56
|
+
function byte(n) {
|
|
57
|
+
return n.toString(16).padStart(2, '0');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Encodes one pattern into its `FF CCCCCCCC` text row (finding A.5): the 3 flow
|
|
61
|
+
* flags are hoisted into the leading flag byte; each channel byte is the SFX
|
|
62
|
+
* index (0..63), OR the index with bit6 (`0x40`) set for an "off" channel. A
|
|
63
|
+
* `null` channel is off (bit6, index bits 0 -> byte `0x40`); the finding notes
|
|
64
|
+
* an off byte keeps its low 6 bits (`0x41` = off + sfx 1), so a `null` becomes a
|
|
65
|
+
* bare `0x40` (there is no underlying sfx index to preserve).
|
|
66
|
+
*
|
|
67
|
+
* @throws {MusicError} `audio-music-bad-channel-count` (not exactly 4 channels),
|
|
68
|
+
* `audio-music-sfx-out-of-range` (a channel index outside 0..63).
|
|
69
|
+
*/
|
|
70
|
+
function encodePattern(pattern, index) {
|
|
71
|
+
const { channels } = pattern;
|
|
72
|
+
if (!Array.isArray(channels) || channels.length !== 4) {
|
|
73
|
+
throw new MusicError('audio-music-bad-channel-count', `pattern ${index} must have exactly 4 channels (ch0..ch3), got ${Array.isArray(channels) ? channels.length : typeof channels}. Each channel is an SFX index 0..${SFX_INDEX_MAX} or null (off).`);
|
|
74
|
+
}
|
|
75
|
+
let flags = 0;
|
|
76
|
+
if (pattern.loopStart)
|
|
77
|
+
flags |= FLAG_LOOP_START;
|
|
78
|
+
if (pattern.loopBack)
|
|
79
|
+
flags |= FLAG_LOOP_BACK;
|
|
80
|
+
if (pattern.stop)
|
|
81
|
+
flags |= FLAG_STOP;
|
|
82
|
+
let channelBytes = '';
|
|
83
|
+
for (let c = 0; c < 4; c++) {
|
|
84
|
+
const ref = channels[c];
|
|
85
|
+
if (ref === null) {
|
|
86
|
+
// "Off" is bit6 with no sfx index: a bare 0x40 (finding A.5). Distinct
|
|
87
|
+
// from sfx 0 (byte 0x00), which is a real, playing SFX.
|
|
88
|
+
channelBytes += byte(CHANNEL_OFF_BIT);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!Number.isInteger(ref) || ref < 0 || ref > SFX_INDEX_MAX) {
|
|
92
|
+
throw new MusicError('audio-music-sfx-out-of-range', `pattern ${index} channel ${c} references SFX ${ref}, outside 0..${SFX_INDEX_MAX}. A channel is an SFX index 0..${SFX_INDEX_MAX} or null (off); use null to silence a channel, not an out-of-range index.`);
|
|
93
|
+
}
|
|
94
|
+
channelBytes += byte(ref);
|
|
95
|
+
}
|
|
96
|
+
return `${byte(flags)} ${channelBytes}`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Transpiles an ordered pattern list into a `__music__` section body (finding
|
|
100
|
+
* Part A.5 + B.6). Emits the friendly text rows directly (flow flags hoisted
|
|
101
|
+
* into the leading flag byte; bit6 set on off channels).
|
|
102
|
+
*
|
|
103
|
+
* Pattern order = song order. The result body is one `FF CCCCCCCC` row per
|
|
104
|
+
* pattern, `\n`-joined with a terminating newline (what PICO-8 writes).
|
|
105
|
+
*
|
|
106
|
+
* A `loopBack` flag anywhere with NO `loopStart` flag anywhere is ALLOWED (PICO-8
|
|
107
|
+
* falls back to pattern 0; finding B.6) but surfaces a {@link MusicWarning}, not
|
|
108
|
+
* an error. Pattern LENGTH is inherited from the referenced SFX and is not stored
|
|
109
|
+
* here (finding B.6).
|
|
110
|
+
*
|
|
111
|
+
* @throws {MusicError} `audio-music-empty` (no patterns),
|
|
112
|
+
* `audio-music-too-many-patterns` (>64), `audio-music-bad-channel-count` or
|
|
113
|
+
* `audio-music-sfx-out-of-range` (a malformed / out-of-range channel).
|
|
114
|
+
*/
|
|
115
|
+
export function patternsToMusic(patterns) {
|
|
116
|
+
if (!Array.isArray(patterns) || patterns.length === 0) {
|
|
117
|
+
throw new MusicError('audio-music-empty', 'no patterns to assemble: supply at least one pattern (each: 4 channel refs + optional loopStart/loopBack/stop flags).');
|
|
118
|
+
}
|
|
119
|
+
if (patterns.length > MUSIC_PATTERN_MAX) {
|
|
120
|
+
throw new MusicError('audio-music-too-many-patterns', `${patterns.length} patterns exceeds the ${MUSIC_PATTERN_MAX}-pattern __music__ limit (song positions 0..${MUSIC_PATTERN_MAX - 1}).`);
|
|
121
|
+
}
|
|
122
|
+
const rows = patterns.map((p, i) => encodePattern(p, i));
|
|
123
|
+
const warnings = [];
|
|
124
|
+
const anyLoopStart = patterns.some((p) => p.loopStart === true);
|
|
125
|
+
const anyLoopBack = patterns.some((p) => p.loopBack === true);
|
|
126
|
+
if (anyLoopBack && !anyLoopStart) {
|
|
127
|
+
warnings.push({
|
|
128
|
+
code: 'loop-back-without-loop-start',
|
|
129
|
+
message: 'a pattern has loopBack but no pattern has loopStart: PICO-8 loops back to pattern 0. Add loopStart to the pattern you want the loop to return to, or accept the fall-back to pattern 0.',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
section: rows.join('\n') + '\n',
|
|
134
|
+
patterns: rows.length,
|
|
135
|
+
warnings,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=music.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"music.js","sourceRoot":"","sources":["../../../src/engine/audio/music.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Cart } from '../cart/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The number of SFX slots a cart holds (finding: 64 SFX, indices 0..63).
|
|
4
|
+
*/
|
|
5
|
+
export declare const SFX_SLOT_COUNT = 64;
|
|
6
|
+
/**
|
|
7
|
+
* Merges one transpiled `__sfx__` row into slot `slot` (0..63) of a cart's
|
|
8
|
+
* `__sfx__` section, leaving every other slot BYTE-IDENTICAL, and commits it
|
|
9
|
+
* back through the cart model (so all other sections stay untouched).
|
|
10
|
+
*
|
|
11
|
+
* PICO-8 trims trailing all-zero SFX rows on save, so a cart's `__sfx__` body
|
|
12
|
+
* may have FEWER than 64 lines (or be absent). This pads with empty rows up to
|
|
13
|
+
* the target slot as needed, replaces the target, and drops trailing empty rows
|
|
14
|
+
* again so the result matches what PICO-8 would write (a stable round-trip).
|
|
15
|
+
*
|
|
16
|
+
* The `__sfx__` body's newline style + trailing-newline are preserved from the
|
|
17
|
+
* existing section (defaulting to `\n` with a terminating newline, matching what
|
|
18
|
+
* PICO-8 writes).
|
|
19
|
+
*/
|
|
20
|
+
export declare function mergeSfxRow(cart: Cart, slot: number, row: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Replaces a cart's `__music__` section with a transpiled body, leaving every
|
|
23
|
+
* OTHER section (including `__sfx__`) BYTE-IDENTICAL, and commits it back through
|
|
24
|
+
* the cart model.
|
|
25
|
+
*
|
|
26
|
+
* Unlike `__sfx__` (where {@link mergeSfxRow} writes ONE slot), a `music
|
|
27
|
+
* from-patterns` call authors the WHOLE song at once (an ordered pattern list is
|
|
28
|
+
* the whole `__music__`), so this REPLACES the section wholesale rather than
|
|
29
|
+
* merging a single row. The body is the codec's output (one `FF CCCCCCCC` row per
|
|
30
|
+
* pattern); this only normalises its newline style to match the cart's existing
|
|
31
|
+
* `__music__`/dominant newline so the round-trip stays stable.
|
|
32
|
+
*/
|
|
33
|
+
export declare function setMusicSection(cart: Cart, body: string): void;
|
|
34
|
+
//# sourceMappingURL=section.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../../src/engine/audio/section.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAC;AAG3C;;GAEG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAKjC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAgCvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAO9D"}
|