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,153 @@
|
|
|
1
|
+
import { CartParseError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* The section names picopilot models in a text-format `.p8` cart. These are the
|
|
4
|
+
* markers PICO-8 writes (`__lua__`, `__gfx__`, ...); `__label__` is the cart
|
|
5
|
+
* label image, also hex.
|
|
6
|
+
*/
|
|
7
|
+
export const CART_SECTION_NAMES = [
|
|
8
|
+
'lua',
|
|
9
|
+
'gfx',
|
|
10
|
+
'gff',
|
|
11
|
+
'label',
|
|
12
|
+
'map',
|
|
13
|
+
'sfx',
|
|
14
|
+
'music',
|
|
15
|
+
];
|
|
16
|
+
const KNOWN_SECTIONS = new Set(CART_SECTION_NAMES);
|
|
17
|
+
/**
|
|
18
|
+
* Matches a whole line that is a section marker: `__name__` optionally followed
|
|
19
|
+
* by a `\r` (CRLF) and the line's `\n`, anchored so a `__x__` appearing mid-line
|
|
20
|
+
* (e.g. inside Lua) is never mistaken for a marker. The trailing newline is
|
|
21
|
+
* captured into the match so the marker line, terminator included, is preserved.
|
|
22
|
+
*/
|
|
23
|
+
const SECTION_MARKER = /^__([a-zA-Z0-9]+)__[^\S\r\n]*(\r?\n|$)/gm;
|
|
24
|
+
/**
|
|
25
|
+
* The in-memory model of a text-format `.p8` cart: the load-bearing seam every
|
|
26
|
+
* cart-touching command (init, gfx, tokens, verify) reads and writes through.
|
|
27
|
+
*
|
|
28
|
+
* Design contract:
|
|
29
|
+
*
|
|
30
|
+
* - **Round-trip is identity.** `Cart.parse(text).serialize() === text`, byte
|
|
31
|
+
* for byte, for any well-formed cart, regardless of newline style or trailing
|
|
32
|
+
* newline. Achieved by preserving the header, each marker line, and each
|
|
33
|
+
* section body verbatim, and never normalising whitespace on the way through.
|
|
34
|
+
* - **Sections are independently addressable.** {@link getSection} /
|
|
35
|
+
* {@link setSection} read and replace one section without disturbing the
|
|
36
|
+
* others (or the header).
|
|
37
|
+
* - **gfx/map are addressable as regions, not opaque strings.** The `__gfx__`
|
|
38
|
+
* and `__map__` hex bodies are exposed as row/pixel grids (see `gfx.ts` /
|
|
39
|
+
* `map.ts`) so the gfx codec and the 0x1000 overlap check can target specific
|
|
40
|
+
* sprites/map rows.
|
|
41
|
+
*/
|
|
42
|
+
export class Cart {
|
|
43
|
+
/**
|
|
44
|
+
* The raw text before the first section marker (the `pico-8 cartridge ...`
|
|
45
|
+
* line, the `version N` line, and any trailing blank lines), preserved
|
|
46
|
+
* verbatim so it round-trips exactly.
|
|
47
|
+
*/
|
|
48
|
+
header;
|
|
49
|
+
sections;
|
|
50
|
+
constructor(header, sections) {
|
|
51
|
+
this.header = header;
|
|
52
|
+
this.sections = sections;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Parses a `.p8` text blob into a {@link Cart}.
|
|
56
|
+
*
|
|
57
|
+
* Malformed input throws a {@link CartParseError} with a machine-readable
|
|
58
|
+
* `code` (missing header/version, unknown or duplicate section) rather than
|
|
59
|
+
* crashing, so the command layer can surface a structured failure.
|
|
60
|
+
*/
|
|
61
|
+
static parse(text) {
|
|
62
|
+
if (!/^pico-8 cartridge/.test(text)) {
|
|
63
|
+
throw new CartParseError('missing-header', 'not a .p8 cart: expected a "pico-8 cartridge ..." header on the first line');
|
|
64
|
+
}
|
|
65
|
+
if (!/^pico-8 cartridge[^\n]*\r?\nversion\s+\d+/.test(text)) {
|
|
66
|
+
throw new CartParseError('missing-version', 'malformed .p8 cart: expected a "version N" line after the header');
|
|
67
|
+
}
|
|
68
|
+
const markers = [];
|
|
69
|
+
SECTION_MARKER.lastIndex = 0;
|
|
70
|
+
for (let m = SECTION_MARKER.exec(text); m !== null; m = SECTION_MARKER.exec(text)) {
|
|
71
|
+
markers.push({
|
|
72
|
+
name: m[1],
|
|
73
|
+
marker: m[0],
|
|
74
|
+
start: m.index,
|
|
75
|
+
bodyStart: m.index + m[0].length,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const header = markers.length > 0 ? text.slice(0, markers[0]?.start) : text;
|
|
79
|
+
const sections = [];
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
for (let i = 0; i < markers.length; i++) {
|
|
82
|
+
const cur = markers[i];
|
|
83
|
+
if (cur === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
if (!KNOWN_SECTIONS.has(cur.name)) {
|
|
86
|
+
throw new CartParseError('unknown-section', `unknown .p8 section "__${cur.name}__" (known: ${CART_SECTION_NAMES.map((n) => `__${n}__`).join(', ')})`);
|
|
87
|
+
}
|
|
88
|
+
if (seen.has(cur.name)) {
|
|
89
|
+
throw new CartParseError('duplicate-section', `duplicate .p8 section "__${cur.name}__"`);
|
|
90
|
+
}
|
|
91
|
+
seen.add(cur.name);
|
|
92
|
+
const bodyEnd = markers[i + 1]?.start ?? text.length;
|
|
93
|
+
sections.push({
|
|
94
|
+
name: cur.name,
|
|
95
|
+
marker: cur.marker,
|
|
96
|
+
body: text.slice(cur.bodyStart, bodyEnd),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return new Cart(header, sections);
|
|
100
|
+
}
|
|
101
|
+
/** The names of the sections present in this cart, in file order. */
|
|
102
|
+
sectionNames() {
|
|
103
|
+
return this.sections.map((s) => s.name);
|
|
104
|
+
}
|
|
105
|
+
/** True if the cart contains the named section. */
|
|
106
|
+
hasSection(name) {
|
|
107
|
+
return this.sections.some((s) => s.name === name);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Returns the raw body text of a section (everything after the marker line,
|
|
111
|
+
* verbatim including its own newlines), or `undefined` if the section is not
|
|
112
|
+
* present. The gfx/map region helpers build on this.
|
|
113
|
+
*/
|
|
114
|
+
getSection(name) {
|
|
115
|
+
return this.sections.find((s) => s.name === name)?.body;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Replaces one section's body in place, leaving every other section and the
|
|
119
|
+
* header untouched. If the section is not yet present it is appended (with a
|
|
120
|
+
* `__name__\n` marker in the file's dominant newline style).
|
|
121
|
+
*
|
|
122
|
+
* The body is written verbatim; callers that want a trailing newline must
|
|
123
|
+
* include it. Region-level helpers (gfx/map) go through here.
|
|
124
|
+
*/
|
|
125
|
+
setSection(name, body) {
|
|
126
|
+
const existing = this.sections.find((s) => s.name === name);
|
|
127
|
+
if (existing !== undefined) {
|
|
128
|
+
existing.body = body;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.sections.push({ name, marker: `__${name}__${this.newline()}`, body });
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Serializes the cart back to a `.p8` text blob. For an unmodified cart this
|
|
135
|
+
* is byte-identical to the text it was parsed from.
|
|
136
|
+
*/
|
|
137
|
+
serialize() {
|
|
138
|
+
let out = this.header;
|
|
139
|
+
for (const s of this.sections) {
|
|
140
|
+
out += s.marker + s.body;
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The dominant newline style of the file, inferred from the header, used only
|
|
146
|
+
* when APPENDING a brand-new section marker (existing markers keep their own
|
|
147
|
+
* terminator verbatim). Defaults to `\n`.
|
|
148
|
+
*/
|
|
149
|
+
newline() {
|
|
150
|
+
return /\r\n/.test(this.header) ? '\r\n' : '\n';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=cart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart.js","sourceRoot":"","sources":["../../../src/engine/cart/cart.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured errors for the cart model.
|
|
3
|
+
*
|
|
4
|
+
* Parsing a malformed cart throws a `CartParseError` (never an unstructured
|
|
5
|
+
* crash), carrying a machine-readable `code` and a human-readable `message` so
|
|
6
|
+
* the command layer can turn it into incur's `error({ code, message, ... })`
|
|
7
|
+
* envelope rather than letting a raw exception escape. Region access out of
|
|
8
|
+
* bounds throws a `CartRangeError` for the same reason.
|
|
9
|
+
*/
|
|
10
|
+
/** Machine-readable reason a `.p8` could not be parsed into the cart model. */
|
|
11
|
+
export type CartParseErrorCode =
|
|
12
|
+
/** The `pico-8 cartridge ...` header line is missing or unrecognised. */
|
|
13
|
+
'missing-header'
|
|
14
|
+
/** The `version N` line is missing or malformed. */
|
|
15
|
+
| 'missing-version'
|
|
16
|
+
/** A `__section__` marker is present but names a section we do not model. */
|
|
17
|
+
| 'unknown-section'
|
|
18
|
+
/** The same `__section__` marker appears more than once. */
|
|
19
|
+
| 'duplicate-section';
|
|
20
|
+
/** Thrown when a `.p8` text blob cannot be parsed into a {@link Cart}. */
|
|
21
|
+
export declare class CartParseError extends Error {
|
|
22
|
+
readonly code: CartParseErrorCode;
|
|
23
|
+
constructor(code: CartParseErrorCode, message: string);
|
|
24
|
+
}
|
|
25
|
+
/** Thrown when a gfx/map region access falls outside the addressable grid. */
|
|
26
|
+
export declare class CartRangeError extends Error {
|
|
27
|
+
constructor(message: string);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/engine/cart/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB;AAC7B,yEAAyE;AACvE,gBAAgB;AAClB,oDAAoD;GAClD,iBAAiB;AACnB,6EAA6E;GAC3E,iBAAiB;AACnB,4DAA4D;GAC1D,mBAAmB,CAAC;AAEvB,0EAA0E;AAC1E,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;gBAEtB,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM;CAKrD;AAED,8EAA8E;AAC9E,qBAAa,cAAe,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI3B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured errors for the cart model.
|
|
3
|
+
*
|
|
4
|
+
* Parsing a malformed cart throws a `CartParseError` (never an unstructured
|
|
5
|
+
* crash), carrying a machine-readable `code` and a human-readable `message` so
|
|
6
|
+
* the command layer can turn it into incur's `error({ code, message, ... })`
|
|
7
|
+
* envelope rather than letting a raw exception escape. Region access out of
|
|
8
|
+
* bounds throws a `CartRangeError` for the same reason.
|
|
9
|
+
*/
|
|
10
|
+
/** Thrown when a `.p8` text blob cannot be parsed into a {@link Cart}. */
|
|
11
|
+
export class CartParseError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(code, message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'CartParseError';
|
|
16
|
+
this.code = code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Thrown when a gfx/map region access falls outside the addressable grid. */
|
|
20
|
+
export class CartRangeError extends Error {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'CartRangeError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/engine/cart/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,0EAA0E;AAC1E,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC/B,IAAI,CAAqB;IAElC,YAAY,IAAwB,EAAE,OAAe;QACpD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD;AAED,8EAA8E;AAC9E,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxC,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC9B,CAAC;CACD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Cart } from './cart.js';
|
|
2
|
+
/**
|
|
3
|
+
* Addressable view over a cart's `__gfx__` section: the 128x128 spritesheet as a
|
|
4
|
+
* grid of per-pixel colour nibbles (0..15), NOT an opaque hex string.
|
|
5
|
+
*
|
|
6
|
+
* The spritesheet holds 256 8x8 sprites laid out 16 per row: sprite `n` lives at
|
|
7
|
+
* pixel column `(n % 16) * 8`, row `(n \ 16) * 8`. Sprites 0..127 are the base
|
|
8
|
+
* bank; sprites 128..255 (`0x1000` in memory) alias `__map__` rows 32..63 (the
|
|
9
|
+
* overlap the smart-refuse depends on); see {@link mapRowsForSprite}.
|
|
10
|
+
*
|
|
11
|
+
* Reads/writes go through the parsed grid; call {@link commit} to write the grid
|
|
12
|
+
* back into the cart's `__gfx__` body (preserving its trailing-newline style).
|
|
13
|
+
* If the section body is shorter than the full sheet (a partial/authored cart),
|
|
14
|
+
* missing pixels read as 0 and short rows are padded to full width on commit.
|
|
15
|
+
*/
|
|
16
|
+
export declare const GFX_WIDTH = 128;
|
|
17
|
+
export declare const GFX_HEIGHT = 128;
|
|
18
|
+
export declare const SPRITE_SIZE = 8;
|
|
19
|
+
export declare const SPRITES_PER_ROW = 16;
|
|
20
|
+
export declare class GfxSheet {
|
|
21
|
+
/** `pixels[y][x]` is the colour index 0..15 at pixel (x, y). */
|
|
22
|
+
private readonly pixels;
|
|
23
|
+
/** The newline that terminates each row line in the source body (`\n` default). */
|
|
24
|
+
private readonly newline;
|
|
25
|
+
private constructor();
|
|
26
|
+
/**
|
|
27
|
+
* Builds an addressable sheet from a cart's `__gfx__` body. A missing or empty
|
|
28
|
+
* section yields an all-zero sheet, so the codec can still address any sprite.
|
|
29
|
+
*/
|
|
30
|
+
static fromBody(body: string | undefined): GfxSheet;
|
|
31
|
+
/** Reads the colour index (0..15) at pixel (x, y). */
|
|
32
|
+
getPixel(x: number, y: number): number;
|
|
33
|
+
/** Writes the colour index (0..15) at pixel (x, y). */
|
|
34
|
+
setPixel(x: number, y: number, color: number): void;
|
|
35
|
+
/**
|
|
36
|
+
* Reads one 8x8 sprite (0..255) as an 8-row grid of colour indices, `grid[r][c]`.
|
|
37
|
+
*/
|
|
38
|
+
getSprite(n: number): number[][];
|
|
39
|
+
/**
|
|
40
|
+
* Writes one 8x8 sprite (0..255) from an 8-row grid of colour indices. The
|
|
41
|
+
* grid must be 8x8; each cell 0..15.
|
|
42
|
+
*/
|
|
43
|
+
setSprite(n: number, grid: number[][]): void;
|
|
44
|
+
/**
|
|
45
|
+
* Serializes the grid back to a `__gfx__` body (128 lines of 128 hex nibbles),
|
|
46
|
+
* using the source body's newline style and terminating the last line, so it
|
|
47
|
+
* matches what PICO-8 writes.
|
|
48
|
+
*/
|
|
49
|
+
toBody(): string;
|
|
50
|
+
/** Writes the grid back into the cart's `__gfx__` section. */
|
|
51
|
+
commit(cart: Cart): void;
|
|
52
|
+
private assertPixel;
|
|
53
|
+
}
|
|
54
|
+
/** The top-left pixel origin of sprite `n` (0..255) in the 128x128 sheet. */
|
|
55
|
+
export declare function spriteOrigin(n: number): {
|
|
56
|
+
x0: number;
|
|
57
|
+
y0: number;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Whether sprite `n` lives in the shared upper bank (128..255) that aliases the
|
|
61
|
+
* bottom half of `__map__`. The gfx/map overlap smart-refuse (next task) keys
|
|
62
|
+
* off this.
|
|
63
|
+
*/
|
|
64
|
+
export declare function spriteAliasesMap(n: number): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* The `__map__` rows a shared-bank sprite (128..255) aliases, or `undefined` for
|
|
67
|
+
* a base-bank sprite (0..127) that aliases nothing.
|
|
68
|
+
*
|
|
69
|
+
* Memory layout: sprite `n`'s 8 pixel rows sit at gfx rows `(n \ 16) * 8`; for
|
|
70
|
+
* the shared bank those are gfx rows 64..127, which occupy the SAME memory as
|
|
71
|
+
* `__map__` rows 32..63. Each map row is 128 bytes = 2 gfx pixel rows (128 gfx
|
|
72
|
+
* nibbles map to 128/... ). Concretely: gfx row `g` (64..127) aliases map row
|
|
73
|
+
* `32 + (g - 64) \ 2`, so a single 8-row sprite covers 4 map rows.
|
|
74
|
+
*/
|
|
75
|
+
export declare function mapRowsForSprite(n: number): {
|
|
76
|
+
start: number;
|
|
77
|
+
end: number;
|
|
78
|
+
} | undefined;
|
|
79
|
+
//# sourceMappingURL=gfx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gfx.d.ts","sourceRoot":"","sources":["../../../src/engine/cart/gfx.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAGpC;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,eAAO,MAAM,UAAU,MAAM,CAAC;AAC9B,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,qBAAa,QAAQ;IACpB,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IAEpC,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO;IAKP;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ;IAgBnD,sDAAsD;IACtD,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;IAKtC,uDAAuD;IACvD,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IASnD;;OAEG;IACH,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE;IAYhC;;;OAGG;IACH,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI;IAmB5C;;;;OAIG;IACH,MAAM,IAAI,MAAM;IAWhB,8DAA8D;IAC9D,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAIxB,OAAO,CAAC,WAAW;CAcnB;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAC,CAQhE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAKnD;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC/B,CAAC,EAAE,MAAM,GACP;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,GAAG,SAAS,CAM1C"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { CartRangeError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Addressable view over a cart's `__gfx__` section: the 128x128 spritesheet as a
|
|
4
|
+
* grid of per-pixel colour nibbles (0..15), NOT an opaque hex string.
|
|
5
|
+
*
|
|
6
|
+
* The spritesheet holds 256 8x8 sprites laid out 16 per row: sprite `n` lives at
|
|
7
|
+
* pixel column `(n % 16) * 8`, row `(n \ 16) * 8`. Sprites 0..127 are the base
|
|
8
|
+
* bank; sprites 128..255 (`0x1000` in memory) alias `__map__` rows 32..63 (the
|
|
9
|
+
* overlap the smart-refuse depends on); see {@link mapRowsForSprite}.
|
|
10
|
+
*
|
|
11
|
+
* Reads/writes go through the parsed grid; call {@link commit} to write the grid
|
|
12
|
+
* back into the cart's `__gfx__` body (preserving its trailing-newline style).
|
|
13
|
+
* If the section body is shorter than the full sheet (a partial/authored cart),
|
|
14
|
+
* missing pixels read as 0 and short rows are padded to full width on commit.
|
|
15
|
+
*/
|
|
16
|
+
export const GFX_WIDTH = 128;
|
|
17
|
+
export const GFX_HEIGHT = 128;
|
|
18
|
+
export const SPRITE_SIZE = 8;
|
|
19
|
+
export const SPRITES_PER_ROW = 16;
|
|
20
|
+
export class GfxSheet {
|
|
21
|
+
/** `pixels[y][x]` is the colour index 0..15 at pixel (x, y). */
|
|
22
|
+
pixels;
|
|
23
|
+
/** The newline that terminates each row line in the source body (`\n` default). */
|
|
24
|
+
newline;
|
|
25
|
+
constructor(pixels, newline) {
|
|
26
|
+
this.pixels = pixels;
|
|
27
|
+
this.newline = newline;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds an addressable sheet from a cart's `__gfx__` body. A missing or empty
|
|
31
|
+
* section yields an all-zero sheet, so the codec can still address any sprite.
|
|
32
|
+
*/
|
|
33
|
+
static fromBody(body) {
|
|
34
|
+
const newline = body !== undefined && /\r\n/.test(body) ? '\r\n' : '\n';
|
|
35
|
+
const pixels = [];
|
|
36
|
+
const lines = body === undefined ? [] : body.split(/\r?\n/);
|
|
37
|
+
for (let y = 0; y < GFX_HEIGHT; y++) {
|
|
38
|
+
const line = lines[y] ?? '';
|
|
39
|
+
const row = [];
|
|
40
|
+
for (let x = 0; x < GFX_WIDTH; x++) {
|
|
41
|
+
const ch = line[x];
|
|
42
|
+
row.push(ch === undefined ? 0 : hexNibble(ch));
|
|
43
|
+
}
|
|
44
|
+
pixels.push(row);
|
|
45
|
+
}
|
|
46
|
+
return new GfxSheet(pixels, newline);
|
|
47
|
+
}
|
|
48
|
+
/** Reads the colour index (0..15) at pixel (x, y). */
|
|
49
|
+
getPixel(x, y) {
|
|
50
|
+
this.assertPixel(x, y);
|
|
51
|
+
return this.pixels[y]?.[x] ?? 0;
|
|
52
|
+
}
|
|
53
|
+
/** Writes the colour index (0..15) at pixel (x, y). */
|
|
54
|
+
setPixel(x, y, color) {
|
|
55
|
+
this.assertPixel(x, y);
|
|
56
|
+
if (!Number.isInteger(color) || color < 0 || color > 15) {
|
|
57
|
+
throw new CartRangeError(`colour ${color} out of range 0..15`);
|
|
58
|
+
}
|
|
59
|
+
const row = this.pixels[y];
|
|
60
|
+
if (row !== undefined)
|
|
61
|
+
row[x] = color;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Reads one 8x8 sprite (0..255) as an 8-row grid of colour indices, `grid[r][c]`.
|
|
65
|
+
*/
|
|
66
|
+
getSprite(n) {
|
|
67
|
+
const { x0, y0 } = spriteOrigin(n);
|
|
68
|
+
const grid = [];
|
|
69
|
+
for (let r = 0; r < SPRITE_SIZE; r++) {
|
|
70
|
+
const row = [];
|
|
71
|
+
for (let c = 0; c < SPRITE_SIZE; c++)
|
|
72
|
+
row.push(this.getPixel(x0 + c, y0 + r));
|
|
73
|
+
grid.push(row);
|
|
74
|
+
}
|
|
75
|
+
return grid;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Writes one 8x8 sprite (0..255) from an 8-row grid of colour indices. The
|
|
79
|
+
* grid must be 8x8; each cell 0..15.
|
|
80
|
+
*/
|
|
81
|
+
setSprite(n, grid) {
|
|
82
|
+
if (grid.length !== SPRITE_SIZE) {
|
|
83
|
+
throw new CartRangeError(`sprite grid must have ${SPRITE_SIZE} rows, got ${grid.length}`);
|
|
84
|
+
}
|
|
85
|
+
const { x0, y0 } = spriteOrigin(n);
|
|
86
|
+
for (let r = 0; r < SPRITE_SIZE; r++) {
|
|
87
|
+
const gridRow = grid[r];
|
|
88
|
+
if (gridRow === undefined || gridRow.length !== SPRITE_SIZE) {
|
|
89
|
+
throw new CartRangeError(`sprite grid row ${r} must have ${SPRITE_SIZE} cells`);
|
|
90
|
+
}
|
|
91
|
+
for (let c = 0; c < SPRITE_SIZE; c++)
|
|
92
|
+
this.setPixel(x0 + c, y0 + r, gridRow[c] ?? 0);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Serializes the grid back to a `__gfx__` body (128 lines of 128 hex nibbles),
|
|
97
|
+
* using the source body's newline style and terminating the last line, so it
|
|
98
|
+
* matches what PICO-8 writes.
|
|
99
|
+
*/
|
|
100
|
+
toBody() {
|
|
101
|
+
const lines = [];
|
|
102
|
+
for (let y = 0; y < GFX_HEIGHT; y++) {
|
|
103
|
+
let line = '';
|
|
104
|
+
for (let x = 0; x < GFX_WIDTH; x++)
|
|
105
|
+
line += (this.pixels[y]?.[x] ?? 0).toString(16);
|
|
106
|
+
lines.push(line);
|
|
107
|
+
}
|
|
108
|
+
return lines.join(this.newline) + this.newline;
|
|
109
|
+
}
|
|
110
|
+
/** Writes the grid back into the cart's `__gfx__` section. */
|
|
111
|
+
commit(cart) {
|
|
112
|
+
cart.setSection('gfx', this.toBody());
|
|
113
|
+
}
|
|
114
|
+
assertPixel(x, y) {
|
|
115
|
+
if (!Number.isInteger(x) ||
|
|
116
|
+
!Number.isInteger(y) ||
|
|
117
|
+
x < 0 ||
|
|
118
|
+
y < 0 ||
|
|
119
|
+
x >= GFX_WIDTH ||
|
|
120
|
+
y >= GFX_HEIGHT) {
|
|
121
|
+
throw new CartRangeError(`pixel (${x}, ${y}) out of range 0..${GFX_WIDTH - 1}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** The top-left pixel origin of sprite `n` (0..255) in the 128x128 sheet. */
|
|
126
|
+
export function spriteOrigin(n) {
|
|
127
|
+
if (!Number.isInteger(n) || n < 0 || n > 255) {
|
|
128
|
+
throw new CartRangeError(`sprite ${n} out of range 0..255`);
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
x0: (n % SPRITES_PER_ROW) * SPRITE_SIZE,
|
|
132
|
+
y0: Math.floor(n / SPRITES_PER_ROW) * SPRITE_SIZE,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Whether sprite `n` lives in the shared upper bank (128..255) that aliases the
|
|
137
|
+
* bottom half of `__map__`. The gfx/map overlap smart-refuse (next task) keys
|
|
138
|
+
* off this.
|
|
139
|
+
*/
|
|
140
|
+
export function spriteAliasesMap(n) {
|
|
141
|
+
if (!Number.isInteger(n) || n < 0 || n > 255) {
|
|
142
|
+
throw new CartRangeError(`sprite ${n} out of range 0..255`);
|
|
143
|
+
}
|
|
144
|
+
return n >= 128;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The `__map__` rows a shared-bank sprite (128..255) aliases, or `undefined` for
|
|
148
|
+
* a base-bank sprite (0..127) that aliases nothing.
|
|
149
|
+
*
|
|
150
|
+
* Memory layout: sprite `n`'s 8 pixel rows sit at gfx rows `(n \ 16) * 8`; for
|
|
151
|
+
* the shared bank those are gfx rows 64..127, which occupy the SAME memory as
|
|
152
|
+
* `__map__` rows 32..63. Each map row is 128 bytes = 2 gfx pixel rows (128 gfx
|
|
153
|
+
* nibbles map to 128/... ). Concretely: gfx row `g` (64..127) aliases map row
|
|
154
|
+
* `32 + (g - 64) \ 2`, so a single 8-row sprite covers 4 map rows.
|
|
155
|
+
*/
|
|
156
|
+
export function mapRowsForSprite(n) {
|
|
157
|
+
if (!spriteAliasesMap(n))
|
|
158
|
+
return undefined;
|
|
159
|
+
const { y0 } = spriteOrigin(n);
|
|
160
|
+
// y0 is 64..120 for the shared bank; each pair of gfx rows aliases one map row.
|
|
161
|
+
const start = 32 + Math.floor((y0 - 64) / 2);
|
|
162
|
+
return { start, end: start + SPRITE_SIZE / 2 }; // 8 gfx rows = 4 map rows: [start, end)
|
|
163
|
+
}
|
|
164
|
+
function hexNibble(ch) {
|
|
165
|
+
const v = Number.parseInt(ch, 16);
|
|
166
|
+
if (Number.isNaN(v)) {
|
|
167
|
+
throw new CartRangeError(`invalid hex nibble "${ch}" in __gfx__`);
|
|
168
|
+
}
|
|
169
|
+
return v;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=gfx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gfx.js","sourceRoot":"","sources":["../../../src/engine/cart/gfx.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The picopilot cart model: parse a text-format `.p8` into a typed, section-level
|
|
3
|
+
* model, read/modify individual sections, address the `__gfx__`/`__map__` hex as
|
|
4
|
+
* pixel/tile regions, and serialize back byte-identically.
|
|
5
|
+
*
|
|
6
|
+
* This is the load-bearing seam every cart-touching command (init, gfx, tokens,
|
|
7
|
+
* verify) reads and writes through. See `cart.ts` for the round-trip-identity
|
|
8
|
+
* contract; `gfx.ts` / `map.ts` for the addressable regions (and the 0x1000
|
|
9
|
+
* overlap the smart-refuse depends on).
|
|
10
|
+
*/
|
|
11
|
+
export { Cart, CART_SECTION_NAMES, type CartSectionName } from './cart.js';
|
|
12
|
+
export { CartParseError, type CartParseErrorCode, CartRangeError, } from './errors.js';
|
|
13
|
+
export { GfxSheet, GFX_HEIGHT, GFX_WIDTH, mapRowsForSprite, SPRITE_SIZE, SPRITES_PER_ROW, spriteAliasesMap, spriteOrigin, } from './gfx.js';
|
|
14
|
+
export { MapData, MAP_ROWS, MAP_WIDTH } from './map.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/cart/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAC,IAAI,EAAE,kBAAkB,EAAE,KAAK,eAAe,EAAC,MAAM,WAAW,CAAC;AACzE,OAAO,EACN,cAAc,EACd,KAAK,kBAAkB,EACvB,cAAc,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,QAAQ,EACR,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,YAAY,GACZ,MAAM,UAAU,CAAC;AAClB,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAC,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The picopilot cart model: parse a text-format `.p8` into a typed, section-level
|
|
3
|
+
* model, read/modify individual sections, address the `__gfx__`/`__map__` hex as
|
|
4
|
+
* pixel/tile regions, and serialize back byte-identically.
|
|
5
|
+
*
|
|
6
|
+
* This is the load-bearing seam every cart-touching command (init, gfx, tokens,
|
|
7
|
+
* verify) reads and writes through. See `cart.ts` for the round-trip-identity
|
|
8
|
+
* contract; `gfx.ts` / `map.ts` for the addressable regions (and the 0x1000
|
|
9
|
+
* overlap the smart-refuse depends on).
|
|
10
|
+
*/
|
|
11
|
+
export { Cart, CART_SECTION_NAMES } from './cart.js';
|
|
12
|
+
export { CartParseError, CartRangeError, } from './errors.js';
|
|
13
|
+
export { GfxSheet, GFX_HEIGHT, GFX_WIDTH, mapRowsForSprite, SPRITE_SIZE, SPRITES_PER_ROW, spriteAliasesMap, spriteOrigin, } from './gfx.js';
|
|
14
|
+
export { MapData, MAP_ROWS, MAP_WIDTH } from './map.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/cart/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAC,IAAI,EAAE,kBAAkB,EAAuB,MAAM,WAAW,CAAC;AACzE,OAAO,EACN,cAAc,EAEd,cAAc,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,QAAQ,EACR,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,YAAY,GACZ,MAAM,UAAU,CAAC;AAClB,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAC,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Cart } from './cart.js';
|
|
2
|
+
/**
|
|
3
|
+
* Addressable view over a cart's `__map__` section: the tile map as a grid of
|
|
4
|
+
* 8-bit tile indices, NOT an opaque hex string.
|
|
5
|
+
*
|
|
6
|
+
* The text `__map__` stores the base map: up to 32 rows of 128 tiles, each tile
|
|
7
|
+
* a byte written as two hex chars (`grid[y][x]`). Rows 32..63 (the shared half)
|
|
8
|
+
* are NOT stored here in the text format (they live in `__gfx__`'s upper bank
|
|
9
|
+
), see `gfx.ts` {@link mapRowsForSprite}; this view models the `__map__` body
|
|
10
|
+
* as written. The 0x1000 overlap check reads a shared-bank sprite's aliased map
|
|
11
|
+
* region through {@link nonZeroTilesInRows} to decide whether a write clobbers
|
|
12
|
+
* real tiles.
|
|
13
|
+
*
|
|
14
|
+
* Reads/writes go through the parsed grid; call {@link commit} to write it back
|
|
15
|
+
* into the cart's `__map__` body. A missing/short section reads as zero tiles.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MAP_WIDTH = 128;
|
|
18
|
+
export declare const MAP_ROWS = 32;
|
|
19
|
+
export declare class MapData {
|
|
20
|
+
/** `tiles[y][x]` is the 0..255 tile index at map cell (x, y). */
|
|
21
|
+
private readonly tiles;
|
|
22
|
+
private readonly newline;
|
|
23
|
+
private constructor();
|
|
24
|
+
/** Builds an addressable map from a cart's `__map__` body (all-zero if absent). */
|
|
25
|
+
static fromBody(body: string | undefined): MapData;
|
|
26
|
+
/** Reads the tile index (0..255) at map cell (x, y). */
|
|
27
|
+
getTile(x: number, y: number): number;
|
|
28
|
+
/** Writes the tile index (0..255) at map cell (x, y). */
|
|
29
|
+
setTile(x: number, y: number, value: number): void;
|
|
30
|
+
/** Reads one full map row (128 tile indices) at row `y`. */
|
|
31
|
+
getRow(y: number): number[];
|
|
32
|
+
/**
|
|
33
|
+
* Counts non-zero tiles across a half-open row range `[start, end)`, clamped
|
|
34
|
+
* to the stored `__map__` rows (0..31). Shared-bank rows (32..63) that the
|
|
35
|
+
* text `__map__` does not store contribute zero. This is the primitive the
|
|
36
|
+
* gfx/map overlap smart-refuse uses to decide whether a shared-bank sprite
|
|
37
|
+
* write would clobber real map data.
|
|
38
|
+
*/
|
|
39
|
+
nonZeroTilesInRows(start: number, end: number): number;
|
|
40
|
+
/** Serializes the grid back to a `__map__` body (32 lines of 256 hex chars). */
|
|
41
|
+
toBody(): string;
|
|
42
|
+
/** Writes the grid back into the cart's `__map__` section. */
|
|
43
|
+
commit(cart: Cart): void;
|
|
44
|
+
private assertCell;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../../src/engine/cart/map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAGpC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,eAAO,MAAM,QAAQ,KAAK,CAAC;AAE3B,qBAAa,OAAO;IACnB,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IAEnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO;IAKP,mFAAmF;IACnF,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO;IAiBlD,wDAAwD;IACxD,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;IAKrC,yDAAyD;IACzD,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IASlD,4DAA4D;IAC5D,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE;IAO3B;;;;;;OAMG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;IAStD,gFAAgF;IAChF,MAAM,IAAI,MAAM;IAWhB,8DAA8D;IAC9D,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAIxB,OAAO,CAAC,UAAU;CAclB"}
|