picopilot 0.1.3 → 0.1.5
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/dist/commands/cart-includes.d.ts +11 -0
- package/dist/commands/cart-includes.d.ts.map +1 -0
- package/dist/commands/cart-includes.js +26 -0
- package/dist/commands/cart-includes.js.map +1 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +62 -77
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/label-cart.d.ts +72 -0
- package/dist/commands/label-cart.d.ts.map +1 -0
- package/dist/commands/label-cart.js +228 -0
- package/dist/commands/label-cart.js.map +1 -0
- package/dist/commands/playtest.d.ts.map +1 -1
- package/dist/commands/playtest.js +2 -23
- package/dist/commands/playtest.js.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +61 -10
- package/dist/commands/serve.js.map +1 -1
- package/dist/engine/gfx/label.d.ts +15 -9
- package/dist/engine/gfx/label.d.ts.map +1 -1
- package/dist/engine/gfx/label.js +130 -38
- package/dist/engine/gfx/label.js.map +1 -1
- package/dist/engine/pico8/session-daemon-entry.d.ts +1 -1
- package/dist/engine/pico8/session-daemon-entry.js +1 -1
- package/dist/engine/pico8/session-daemon-main.d.ts +1 -1
- package/dist/engine/pico8/session-daemon-main.js +1 -1
- package/dist/engine/pico8/session-daemon.d.ts +1 -1
- package/dist/engine/pico8/session-daemon.d.ts.map +1 -1
- package/dist/engine/pico8/session-daemon.js +19 -19
- package/dist/engine/pico8/session-daemon.js.map +1 -1
- package/dist/engine/pico8/session.d.ts +1 -1
- package/dist/engine/pico8/session.js +1 -1
- package/dist/engine/pico8/supervisor.d.ts +1 -1
- package/dist/engine/pico8/supervisor.js +1 -1
- package/dist/engine/scaffold/scaffold.d.ts.map +1 -1
- package/dist/engine/scaffold/scaffold.js +15 -0
- package/dist/engine/scaffold/scaffold.js.map +1 -1
- package/dist/engine/shrinko/adapter.d.ts +1 -1
- package/package.json +2 -1
- package/src/commands/audio.test.ts +12 -0
- package/src/commands/cart-includes.ts +27 -0
- package/src/commands/export.test.ts +327 -3
- package/src/commands/export.ts +72 -86
- package/src/commands/init.test.ts +10 -0
- package/src/commands/label-cart.ts +304 -0
- package/src/commands/lint.test.ts +6 -0
- package/src/commands/minify.test.ts +6 -0
- package/src/commands/playtest.test.ts +3 -0
- package/src/commands/playtest.ts +2 -23
- package/src/commands/run.test.ts +6 -0
- package/src/commands/serve.test.ts +138 -2
- package/src/commands/serve.ts +72 -10
- package/src/commands/tokens.test.ts +19 -0
- package/src/commands/verify.test.ts +12 -0
- package/src/engine/gfx/fixtures/pico8-screenshot-384.png +0 -0
- package/src/engine/gfx/label.test.ts +467 -1
- package/src/engine/gfx/label.ts +147 -41
- package/src/engine/pico8/session-daemon-entry.ts +1 -1
- package/src/engine/pico8/session-daemon-main.ts +1 -1
- package/src/engine/pico8/session-daemon.test.ts +32 -1
- package/src/engine/pico8/session-daemon.ts +25 -17
- package/src/engine/pico8/session.test.ts +1 -1
- package/src/engine/pico8/session.ts +1 -1
- package/src/engine/pico8/shell.test.ts +1 -1
- package/src/engine/pico8/supervisor.ts +1 -1
- package/src/engine/scaffold/scaffold.ts +15 -0
- package/src/engine/shrinko/adapter.ts +1 -1
- package/src/engine/skills/skills.test.ts +16 -0
- package/src/skills/game-design-reference/SKILL.md +2 -0
- package/src/skills/picopilot-debug/SKILL.md +18 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `#include` resolver for the drive-transform, bound to the SOURCE cart's dir:
|
|
3
|
+
* picopilot's standard scaffold is `main.p8` = `#include main.lua`, and the
|
|
4
|
+
* driven cart runs from a throwaway temp dir where `main.lua` is absent, so the
|
|
5
|
+
* transform inlines includes to be self-contained. Reads the included file
|
|
6
|
+
* relative to the cart (absolute paths honoured); returns `undefined` if it
|
|
7
|
+
* cannot be read (the directive is then left verbatim). Shared by `playtest` and
|
|
8
|
+
* the `--label-frame` capture.
|
|
9
|
+
*/
|
|
10
|
+
export declare function makeReadInclude(cartPath: string): (p: string) => string | undefined;
|
|
11
|
+
//# sourceMappingURL=cart-includes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-includes.d.ts","sourceRoot":"","sources":["../../src/commands/cart-includes.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,MAAM,GACd,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAYnC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* A `#include` resolver for the drive-transform, bound to the SOURCE cart's dir:
|
|
5
|
+
* picopilot's standard scaffold is `main.p8` = `#include main.lua`, and the
|
|
6
|
+
* driven cart runs from a throwaway temp dir where `main.lua` is absent, so the
|
|
7
|
+
* transform inlines includes to be self-contained. Reads the included file
|
|
8
|
+
* relative to the cart (absolute paths honoured); returns `undefined` if it
|
|
9
|
+
* cannot be read (the directive is then left verbatim). Shared by `playtest` and
|
|
10
|
+
* the `--label-frame` capture.
|
|
11
|
+
*/
|
|
12
|
+
export function makeReadInclude(cartPath) {
|
|
13
|
+
const base = dirname(cartPath);
|
|
14
|
+
return (includePath) => {
|
|
15
|
+
const full = isAbsolute(includePath)
|
|
16
|
+
? includePath
|
|
17
|
+
: join(base, includePath);
|
|
18
|
+
try {
|
|
19
|
+
return readFileSync(full, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=cart-includes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cart-includes.js","sourceRoot":"","sources":["../../src/commands/cart-includes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AACrC,OAAO,EAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAC,MAAM,WAAW,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC9B,QAAgB;IAEhB,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,WAAW,EAAE,EAAE;QACtB,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC;YACnC,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAC3B,IAAI,CAAC;YACJ,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,SAAS,CAAC;QAClB,CAAC;IACF,CAAC,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAClC,OAAO,EAEN,KAAK,YAAY,EAGjB,MAAM,0BAA0B,CAAC;AAGlC;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,YAAY,CAAC;AAQ3E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAC7B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,mBAA2C,GACzD,IAAI,CA+ON"}
|
package/dist/commands/export.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, mkdtempSync, readdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
|
-
import {
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import { z } from 'incur';
|
|
5
|
-
import { Cart, CartParseError } from '../engine/cart/index.js';
|
|
6
|
-
import { labelHexFromPng, LabelError } from '../engine/gfx/index.js';
|
|
7
5
|
import { EXPORT_HTML_NAME, ShellPico8Adapter, } from '../engine/pico8/index.js';
|
|
6
|
+
import { prepareLabeledCart } from './label-cart.js';
|
|
8
7
|
const defaultAdapterFactory = (env) => new ShellPico8Adapter({ env });
|
|
9
8
|
/** The default backstop: kill an export that never completes/exits after this many ms. */
|
|
10
9
|
const DEFAULT_BACKSTOP_MS = 30_000;
|
|
@@ -50,7 +49,13 @@ export function registerExport(cli, adapterFactory = defaultAdapterFactory) {
|
|
|
50
49
|
label: z
|
|
51
50
|
.string()
|
|
52
51
|
.optional()
|
|
53
|
-
.describe('
|
|
52
|
+
.describe('PNG to bake in as the cart __label__ (the export splash), for a cart that has none: a `run`/`playtest` screenshot as-is (any 128*k square, e.g. 384x384, is scaled down for you) or any 128x128 PNG. Ignored if the cart already has a label.'),
|
|
53
|
+
labelFrame: z
|
|
54
|
+
.number()
|
|
55
|
+
.int()
|
|
56
|
+
.min(0)
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('Capture the label from the game itself: run the cart headlessly with NO input and use frame N (0-based: 0 = the first drawn frame, usually the title screen) as the __label__. Needs PICO-8; the cart need not cooperate. Exclusive with --label; ignored if the cart already has a label.'),
|
|
54
59
|
payloadOnly: z
|
|
55
60
|
.boolean()
|
|
56
61
|
.default(false)
|
|
@@ -60,7 +65,7 @@ export function registerExport(cli, adapterFactory = defaultAdapterFactory) {
|
|
|
60
65
|
.int()
|
|
61
66
|
.positive()
|
|
62
67
|
.default(DEFAULT_BACKSTOP_MS)
|
|
63
|
-
.describe('Safety backstop: kill PICO-8 after this many ms if the export never completes.'),
|
|
68
|
+
.describe('Safety backstop: kill PICO-8 after this many ms if the export (or the --label-frame capture run) never completes.'),
|
|
64
69
|
}),
|
|
65
70
|
// incur resolves these from the (test-overridable) env source and hands
|
|
66
71
|
// them to the child, so PICO8_PATH/PATH locate the binary in isolation.
|
|
@@ -83,6 +88,9 @@ export function registerExport(cli, adapterFactory = defaultAdapterFactory) {
|
|
|
83
88
|
files: z
|
|
84
89
|
.array(z.string())
|
|
85
90
|
.describe('Absolute paths of every file the bundle contains.'),
|
|
91
|
+
labelSource: z
|
|
92
|
+
.enum(['cart', 'image', 'frame', 'none'])
|
|
93
|
+
.describe("Where the bundle's label came from: the cart's own __label__, --label, --label-frame, or none."),
|
|
86
94
|
labelWarning: z
|
|
87
95
|
.boolean()
|
|
88
96
|
.describe('True when the cart has no __label__: it exports with a blank/ugly splash.'),
|
|
@@ -94,9 +102,14 @@ export function registerExport(cli, adapterFactory = defaultAdapterFactory) {
|
|
|
94
102
|
args: { cart: 'game.p8', dest: './website/static/games/my-game' },
|
|
95
103
|
},
|
|
96
104
|
{
|
|
97
|
-
description: 'Bake a
|
|
105
|
+
description: 'Bake a `run` screenshot in as the label splash for a labelless cart',
|
|
98
106
|
args: { cart: 'game.p8', dest: './website/static/games/my-game' },
|
|
99
|
-
options: { label: './
|
|
107
|
+
options: { label: './shots/<name>.png' },
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
description: 'Capture the title screen (frame 0) as the label splash, no image needed',
|
|
111
|
+
args: { cart: 'game.p8', dest: './website/static/games/my-game' },
|
|
112
|
+
options: { labelFrame: 0 },
|
|
100
113
|
},
|
|
101
114
|
{
|
|
102
115
|
description: 'Export only the runtime payload for a site player component',
|
|
@@ -116,54 +129,23 @@ export function registerExport(cli, adapterFactory = defaultAdapterFactory) {
|
|
|
116
129
|
exitCode: 1,
|
|
117
130
|
});
|
|
118
131
|
}
|
|
119
|
-
// --label <png
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
try {
|
|
139
|
-
const cart = Cart.parse(readFileSync(cartPath, 'utf8'));
|
|
140
|
-
const existingLabel = cart.getSection('label');
|
|
141
|
-
if (existingLabel === undefined || existingLabel.trim() === '') {
|
|
142
|
-
const hex = labelHexFromPng(readFileSync(labelPath));
|
|
143
|
-
cart.setSection('label', hex);
|
|
144
|
-
labeledCopy = join(dirname(cartPath), `.picopilot-export-${process.pid}-${Date.now()}.p8`);
|
|
145
|
-
writeFileSync(labeledCopy, cart.serialize());
|
|
146
|
-
exportCartPath = labeledCopy;
|
|
147
|
-
}
|
|
148
|
-
// else: cart already has a label; keep it, ignore --label.
|
|
149
|
-
}
|
|
150
|
-
catch (e) {
|
|
151
|
-
if (e instanceof LabelError) {
|
|
152
|
-
return error({
|
|
153
|
-
code: `label-${e.code}`,
|
|
154
|
-
message: e.message,
|
|
155
|
-
exitCode: 1,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
if (e instanceof CartParseError) {
|
|
159
|
-
return error({
|
|
160
|
-
code: `cart-${e.code}`,
|
|
161
|
-
message: `could not parse the cart to inject a label: ${e.message}`,
|
|
162
|
-
exitCode: 1,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
throw e;
|
|
166
|
-
}
|
|
132
|
+
// --label <png> / --label-frame <n>: bake an image, or a captured frame of
|
|
133
|
+
// the running cart, in as the __label__ of a throwaway copy (the user's
|
|
134
|
+
// cart is never mutated; an existing label wins). Shared with `serve`,
|
|
135
|
+
// see label-cart.ts.
|
|
136
|
+
const adapter = adapterFactory(env);
|
|
137
|
+
const labeled = await prepareLabeledCart(cartPath, {
|
|
138
|
+
label: options.label,
|
|
139
|
+
labelFrame: options.labelFrame,
|
|
140
|
+
backstopMs: options.backstopMs,
|
|
141
|
+
}, (o) => adapter.drive(o));
|
|
142
|
+
if (!labeled.ok) {
|
|
143
|
+
return error({
|
|
144
|
+
code: labeled.code,
|
|
145
|
+
message: labeled.message,
|
|
146
|
+
exitCode: 1,
|
|
147
|
+
...(labeled.cta ? { cta: labeled.cta } : {}),
|
|
148
|
+
});
|
|
167
149
|
}
|
|
168
150
|
// Output dir: a user-given dest (point it at the showcase), else an
|
|
169
151
|
// isolated temp dir. Create it so PICO-8 has somewhere to write.
|
|
@@ -172,24 +154,22 @@ export function registerExport(cli, adapterFactory = defaultAdapterFactory) {
|
|
|
172
154
|
? args.dest
|
|
173
155
|
: resolve(process.cwd(), args.dest)
|
|
174
156
|
: mkdtempSync(join(tmpdir(), 'picopilot-export-'));
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
outDir
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// ignore
|
|
192
|
-
}
|
|
157
|
+
// The labeled copy lives in the user's cart dir, so remove it on EVERY
|
|
158
|
+
// path once PICO-8 has read it, including a throw from the dir setup or
|
|
159
|
+
// the adapter.
|
|
160
|
+
let result;
|
|
161
|
+
try {
|
|
162
|
+
if (!existsSync(outDir))
|
|
163
|
+
mkdirSync(outDir, { recursive: true });
|
|
164
|
+
result = await adapter.export({
|
|
165
|
+
cartPath: labeled.cartPath,
|
|
166
|
+
outDir,
|
|
167
|
+
htmlName: EXPORT_HTML_NAME,
|
|
168
|
+
backstopMs: options.backstopMs,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
labeled.cleanup();
|
|
193
173
|
}
|
|
194
174
|
if (!result.ok) {
|
|
195
175
|
// The two-tier structured failure: PICO-8 absent. The message carries
|
|
@@ -207,7 +187,7 @@ export function registerExport(cli, adapterFactory = defaultAdapterFactory) {
|
|
|
207
187
|
return error({
|
|
208
188
|
code: 'export-failed',
|
|
209
189
|
message: labelWarning
|
|
210
|
-
? 'PICO-8 produced no bundle (the cart has no __label__ to embed). Give it one with --label <
|
|
190
|
+
? 'PICO-8 produced no bundle (the cart has no __label__ to embed). Give it one with --label-frame 0 (captures the title screen for you), or --label <screenshot.png> (a `picopilot run`/`playtest` screenshot works as-is), then re-export.'
|
|
211
191
|
: 'PICO-8 produced no export bundle. Check the cart runs without a boot error.',
|
|
212
192
|
exitCode: 1,
|
|
213
193
|
});
|
|
@@ -230,8 +210,12 @@ export function registerExport(cli, adapterFactory = defaultAdapterFactory) {
|
|
|
230
210
|
description: 'The cart has no __label__, so the export splash is blank. Add one for a nicer bundle:',
|
|
231
211
|
commands: [
|
|
232
212
|
{
|
|
233
|
-
command: 'export --label
|
|
234
|
-
description: '
|
|
213
|
+
command: 'export <cart> <dest> --label-frame 0',
|
|
214
|
+
description: 'Capture the title screen (frame 0) of the running cart as the label, no image needed.',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
command: 'export --label <screenshot.png>',
|
|
218
|
+
description: 'Bake a `run`/`playtest` screenshot in as the label splash (passed as-is, it is scaled for you), or capture one in PICO-8 (F7) before exporting.',
|
|
235
219
|
},
|
|
236
220
|
],
|
|
237
221
|
}
|
|
@@ -241,6 +225,7 @@ export function registerExport(cli, adapterFactory = defaultAdapterFactory) {
|
|
|
241
225
|
html: htmlPath,
|
|
242
226
|
js: jsPath,
|
|
243
227
|
files: [...files],
|
|
228
|
+
labelSource: labeled.labelSource,
|
|
244
229
|
labelWarning,
|
|
245
230
|
}, cta === undefined ? undefined : { cta });
|
|
246
231
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type DriveOptions, type Pico8DriveResult } from '../engine/pico8/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The remedy for both label-image errors (`label-wrong-size`,
|
|
4
|
+
* `label-unsupported-png`): the screenshot `run`/`playtest` already wrote is a
|
|
5
|
+
* valid label as-is, so point at that rather than at an external image tool.
|
|
6
|
+
*/
|
|
7
|
+
export declare const LABEL_FIX_CTA: {
|
|
8
|
+
description: string;
|
|
9
|
+
commands: {
|
|
10
|
+
command: string;
|
|
11
|
+
description: string;
|
|
12
|
+
}[];
|
|
13
|
+
};
|
|
14
|
+
/** Where the exported cart's `__label__` came from. */
|
|
15
|
+
export type LabelSource = 'cart' | 'image' | 'frame' | 'none';
|
|
16
|
+
/** A cart ready to hand to PICO-8's export, plus how to tidy up after it. */
|
|
17
|
+
export interface LabeledCart {
|
|
18
|
+
readonly ok: true;
|
|
19
|
+
/** The cart to export: the original, or a labeled throwaway copy. */
|
|
20
|
+
readonly cartPath: string;
|
|
21
|
+
/**
|
|
22
|
+
* Where the label comes from: the cart's own (`cart`), `--label` (`image`),
|
|
23
|
+
* `--label-frame` (`frame`), or nowhere (`none`, the cart stays labelless).
|
|
24
|
+
*/
|
|
25
|
+
readonly labelSource: LabelSource;
|
|
26
|
+
/** Removes the throwaway copy (if one was made). Best-effort, idempotent. */
|
|
27
|
+
readonly cleanup: () => void;
|
|
28
|
+
}
|
|
29
|
+
/** A label-preparation failure, shaped for incur's `error({...})`. */
|
|
30
|
+
export interface LabelCartFailure {
|
|
31
|
+
readonly ok: false;
|
|
32
|
+
readonly code: string;
|
|
33
|
+
readonly message: string;
|
|
34
|
+
readonly cta?: {
|
|
35
|
+
description: string;
|
|
36
|
+
commands: {
|
|
37
|
+
command: string;
|
|
38
|
+
description: string;
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** The label flags `export` and `serve` share. */
|
|
43
|
+
export interface LabelOptions {
|
|
44
|
+
/** `--label <png>`: an image (a run/playtest screenshot as-is, or 128x128). */
|
|
45
|
+
readonly label?: string;
|
|
46
|
+
/** `--label-frame <n>`: capture frame `n` of the running cart, no input. */
|
|
47
|
+
readonly labelFrame?: number;
|
|
48
|
+
/** The backstop for the `--label-frame` capture run. */
|
|
49
|
+
readonly backstopMs: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The `--label` / `--label-frame` step shared by `export` and `serve` (ADR-0013,
|
|
53
|
+
* ADR-0015, ADR-0016): give a labelless cart a `__label__` (the export splash)
|
|
54
|
+
* without an interactive F7 capture.
|
|
55
|
+
*
|
|
56
|
+
* - `--label <png>`: decode an image (a `run`/`playtest` screenshot as-is, any
|
|
57
|
+
* square integer multiple of 128x128, or any 128x128 PNG).
|
|
58
|
+
* - `--label-frame <n>`: RUN the cart headlessly through the playtest drive
|
|
59
|
+
* harness (ADR-0011) with NO input, advance `n` frames, screenshot that frame,
|
|
60
|
+
* and decode it. Needs PICO-8 (`drive`); an arbitrary cart does not have to
|
|
61
|
+
* cooperate, since the harness owns its frame loop.
|
|
62
|
+
*
|
|
63
|
+
* The two flags are mutually exclusive (`label-flags-conflict`). The user's cart
|
|
64
|
+
* is NEVER mutated: a labeled copy is written NEXT TO it (same dir, so
|
|
65
|
+
* `#include main.lua` still resolves) and the caller exports that, then calls
|
|
66
|
+
* `cleanup()`. A cart that ALREADY has a label wins (the flags are a fallback,
|
|
67
|
+
* not an override): it is returned untouched and nothing is run.
|
|
68
|
+
*
|
|
69
|
+
* @param drive the adapter's `drive`, only called for `--label-frame`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function prepareLabeledCart(cartPath: string, options: LabelOptions, drive: (options: DriveOptions) => Promise<Pico8DriveResult>): Promise<LabeledCart | LabelCartFailure>;
|
|
72
|
+
//# sourceMappingURL=label-cart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label-cart.d.ts","sourceRoot":"","sources":["../../src/commands/label-cart.ts"],"names":[],"mappings":"AAWA,OAAO,EAGN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,MAAM,0BAA0B,CAAC;AAGlC;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;CAezB,CAAC;AAEF,uDAAuD;AACvD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE9D,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE;QACd,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAC,EAAE,CAAC;KACnD,CAAC;CACF;AAED,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B;AAKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,kBAAkB,CACvC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,gBAAgB,CAAC,GACzD,OAAO,CAAC,WAAW,GAAG,gBAAgB,CAAC,CAwGzC"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { Cart, CartParseError } from '../engine/cart/index.js';
|
|
5
|
+
import { labelHexFromPng, LabelError } from '../engine/gfx/index.js';
|
|
6
|
+
import { buildDriveHarness, DriveError, } from '../engine/pico8/index.js';
|
|
7
|
+
import { makeReadInclude } from './cart-includes.js';
|
|
8
|
+
/**
|
|
9
|
+
* The remedy for both label-image errors (`label-wrong-size`,
|
|
10
|
+
* `label-unsupported-png`): the screenshot `run`/`playtest` already wrote is a
|
|
11
|
+
* valid label as-is, so point at that rather than at an external image tool.
|
|
12
|
+
*/
|
|
13
|
+
export const LABEL_FIX_CTA = {
|
|
14
|
+
description: 'Pass a picopilot screenshot directly; it is scaled for you (any 128*k square: 128x128, 256x256, 384x384, ...). No image tool needed:',
|
|
15
|
+
commands: [
|
|
16
|
+
{
|
|
17
|
+
command: 'run <cart> --shot-dir shots',
|
|
18
|
+
description: 'Capture a frame (the cart calls extcmd("screen")), then pass shots/<name>.png to --label unchanged.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
command: 'export <cart> <dest> --label shots/<name>.png',
|
|
22
|
+
description: 'Bake that screenshot in as the label splash (`serve --label` takes it the same way). playtest screenshots work too.',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
/** The base filename the `--label-frame` capture names its screenshot. */
|
|
27
|
+
const LABEL_FRAME_BASENAME = 'label';
|
|
28
|
+
/**
|
|
29
|
+
* The `--label` / `--label-frame` step shared by `export` and `serve` (ADR-0013,
|
|
30
|
+
* ADR-0015, ADR-0016): give a labelless cart a `__label__` (the export splash)
|
|
31
|
+
* without an interactive F7 capture.
|
|
32
|
+
*
|
|
33
|
+
* - `--label <png>`: decode an image (a `run`/`playtest` screenshot as-is, any
|
|
34
|
+
* square integer multiple of 128x128, or any 128x128 PNG).
|
|
35
|
+
* - `--label-frame <n>`: RUN the cart headlessly through the playtest drive
|
|
36
|
+
* harness (ADR-0011) with NO input, advance `n` frames, screenshot that frame,
|
|
37
|
+
* and decode it. Needs PICO-8 (`drive`); an arbitrary cart does not have to
|
|
38
|
+
* cooperate, since the harness owns its frame loop.
|
|
39
|
+
*
|
|
40
|
+
* The two flags are mutually exclusive (`label-flags-conflict`). The user's cart
|
|
41
|
+
* is NEVER mutated: a labeled copy is written NEXT TO it (same dir, so
|
|
42
|
+
* `#include main.lua` still resolves) and the caller exports that, then calls
|
|
43
|
+
* `cleanup()`. A cart that ALREADY has a label wins (the flags are a fallback,
|
|
44
|
+
* not an override): it is returned untouched and nothing is run.
|
|
45
|
+
*
|
|
46
|
+
* @param drive the adapter's `drive`, only called for `--label-frame`.
|
|
47
|
+
*/
|
|
48
|
+
export async function prepareLabeledCart(cartPath, options, drive) {
|
|
49
|
+
const keep = (labelSource) => ({
|
|
50
|
+
ok: true,
|
|
51
|
+
cartPath,
|
|
52
|
+
labelSource,
|
|
53
|
+
cleanup: () => { },
|
|
54
|
+
});
|
|
55
|
+
if (options.label !== undefined && options.labelFrame !== undefined) {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
code: 'label-flags-conflict',
|
|
59
|
+
message: 'pass either --label <png> (an image) or --label-frame <n> (capture a frame of the running cart), not both',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// With no label flag the cart is only read to REPORT `labelSource`; anything
|
|
63
|
+
// odd about it (unreadable, unparseable) is left for PICO-8 to judge, exactly
|
|
64
|
+
// as before the label flags existed.
|
|
65
|
+
const noFlag = options.label === undefined && options.labelFrame === undefined;
|
|
66
|
+
let cart;
|
|
67
|
+
try {
|
|
68
|
+
cart = Cart.parse(readFileSync(cartPath, 'utf8'));
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
if (noFlag)
|
|
72
|
+
return keep('none');
|
|
73
|
+
if (e instanceof CartParseError) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
code: `cart-${e.code}`,
|
|
77
|
+
message: `could not parse the cart to inject a label: ${e.message}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
const existingLabel = cart.getSection('label');
|
|
83
|
+
if (existingLabel !== undefined && existingLabel.trim() !== '') {
|
|
84
|
+
return keep('cart');
|
|
85
|
+
}
|
|
86
|
+
let png;
|
|
87
|
+
let labelSource;
|
|
88
|
+
if (options.label !== undefined) {
|
|
89
|
+
const labelPath = isAbsolute(options.label)
|
|
90
|
+
? options.label
|
|
91
|
+
: resolve(process.cwd(), options.label);
|
|
92
|
+
if (!existsSync(labelPath)) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
code: 'label-not-found',
|
|
96
|
+
message: `no label image at ${labelPath}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
png = readFileSync(labelPath);
|
|
100
|
+
labelSource = 'image';
|
|
101
|
+
}
|
|
102
|
+
else if (options.labelFrame !== undefined) {
|
|
103
|
+
const captured = await captureFrame(cartPath, options.labelFrame, options.backstopMs, drive);
|
|
104
|
+
if (!captured.ok)
|
|
105
|
+
return captured;
|
|
106
|
+
png = captured.png;
|
|
107
|
+
labelSource = 'frame';
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
return keep('none');
|
|
111
|
+
}
|
|
112
|
+
let hex;
|
|
113
|
+
try {
|
|
114
|
+
hex = labelHexFromPng(png);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
if (e instanceof LabelError) {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
code: `label-${e.code}`,
|
|
121
|
+
message: e.message,
|
|
122
|
+
cta: LABEL_FIX_CTA,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
127
|
+
cart.setSection('label', hex);
|
|
128
|
+
const copy = join(dirname(cartPath), `.picopilot-export-${process.pid}-${Date.now()}.p8`);
|
|
129
|
+
writeFileSync(copy, cart.serialize());
|
|
130
|
+
return {
|
|
131
|
+
ok: true,
|
|
132
|
+
cartPath: copy,
|
|
133
|
+
labelSource,
|
|
134
|
+
cleanup: () => {
|
|
135
|
+
try {
|
|
136
|
+
rmSync(copy, { force: true });
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// A leftover dotfile is not fatal.
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Runs the cart through the playtest drive harness with NO input for `frame + 1`
|
|
146
|
+
* frames, screenshotting after frame `frame` (0 = the first drawn frame), and
|
|
147
|
+
* returns that PNG. The driven cart and PICO-8's screenshot dir live in a temp
|
|
148
|
+
* dir that is removed before returning.
|
|
149
|
+
*/
|
|
150
|
+
async function captureFrame(cartPath, frame, backstopMs, drive) {
|
|
151
|
+
let harness;
|
|
152
|
+
try {
|
|
153
|
+
harness = buildDriveHarness(readFileSync(cartPath, 'utf8'), {
|
|
154
|
+
presses: [],
|
|
155
|
+
frames: frame + 1,
|
|
156
|
+
shotFrames: [frame],
|
|
157
|
+
shotBasename: LABEL_FRAME_BASENAME,
|
|
158
|
+
readInclude: makeReadInclude(cartPath),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
if (e instanceof DriveError) {
|
|
163
|
+
return { ok: false, code: e.code, message: e.message };
|
|
164
|
+
}
|
|
165
|
+
throw e;
|
|
166
|
+
}
|
|
167
|
+
const shotDir = mkdtempSync(join(tmpdir(), 'picopilot-label-frame-'));
|
|
168
|
+
try {
|
|
169
|
+
const drivenCart = join(shotDir, 'driven.p8');
|
|
170
|
+
writeFileSync(drivenCart, harness.cartText);
|
|
171
|
+
const result = await drive({
|
|
172
|
+
cartPath: drivenCart,
|
|
173
|
+
shotDir,
|
|
174
|
+
blocks: harness.blocks,
|
|
175
|
+
backstopMs,
|
|
176
|
+
});
|
|
177
|
+
if (!result.ok) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
code: result.reason,
|
|
181
|
+
message: `PICO-8 is not installed, so --label-frame cannot capture a frame. ${result.remedy} (needs: ${result.needs.join(', ')})`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const shot = result.value.screenshots[0];
|
|
185
|
+
if (shot === undefined) {
|
|
186
|
+
const { exitReason } = result.value;
|
|
187
|
+
const tail = result.value.printh.trim().split('\n').slice(-5).join('\n');
|
|
188
|
+
const commands = [
|
|
189
|
+
{
|
|
190
|
+
command: 'verify',
|
|
191
|
+
description: 'Run the static gate (tokens + lint + integrity).',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
command: 'playtest start <cart>',
|
|
195
|
+
description: 'Reproduce the SAME no-input frame to look at it: start a paused session (nothing held), then step and shoot.',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
command: `playtest step <id> --frames ${frame + 1}`,
|
|
199
|
+
description: `Advance to frame ${frame}, as --label-frame ${frame} does.`,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
command: 'playtest shot <id>',
|
|
203
|
+
description: 'Screenshot it; if it looks right, pass that PNG with --label <png> (then `playtest stop <id>`).',
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
if (exitReason === 'timeout') {
|
|
207
|
+
commands.push({
|
|
208
|
+
command: `export <cart> --label-frame ${frame} --backstop-ms <more>`,
|
|
209
|
+
description: 'The capture hit the backstop (it runs about one frame per PICO-8 tick): allow more time, or pick an earlier frame.',
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
code: 'label-frame-not-captured',
|
|
215
|
+
message: `the cart produced no frame ${frame} screenshot (exit: ${exitReason}).${tail === '' ? '' : ` Last output:\n${tail}`}`,
|
|
216
|
+
cta: {
|
|
217
|
+
description: 'The cart did not reach that frame. Check it boots, or look at the frame first:',
|
|
218
|
+
commands,
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return { ok: true, png: readFileSync(shot) };
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
rmSync(shotDir, { recursive: true, force: true });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=label-cart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label-cart.js","sourceRoot":"","sources":["../../src/commands/label-cart.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playtest.d.ts","sourceRoot":"","sources":["../../src/commands/playtest.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,KAAK,GAAG,EAAqB,MAAM,OAAO,CAAC;AACnD,OAAO,EAMN,KAAK,YAAY,EAajB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"playtest.d.ts","sourceRoot":"","sources":["../../src/commands/playtest.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,KAAK,GAAG,EAAqB,MAAM,OAAO,CAAC;AACnD,OAAO,EAMN,KAAK,YAAY,EAajB,MAAM,0BAA0B,CAAC;AAGlC;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,YAAY,CAAC;AA2B3E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,mBAA2C,GACzD,IAAI,CAEN"}
|