picopilot 0.1.2 → 0.1.4
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/engine/scaffold/scaffold.d.ts.map +1 -1
- package/dist/engine/scaffold/scaffold.js +45 -2
- package/dist/engine/scaffold/scaffold.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/init.test.ts +26 -0
- package/src/engine/scaffold/scaffold.ts +45 -2
- package/src/engine/skills/skills.test.ts +28 -0
- package/src/skills/game-design-reference/SKILL.md +2 -0
- package/src/skills/picopilot-code/SKILL.md +15 -1
- package/src/skills/picopilot-code/reference/README.md +7 -1
- package/src/skills/picopilot-code/reference/pico8-api.md +118 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/engine/scaffold/scaffold.ts"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAEpD;;;;GAIG;AACH,wBAAgB,MAAM,IAAI,MAAM,CAM/B;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,IAAI,MAAM,CAehC;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/engine/scaffold/scaffold.ts"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAEpD;;;;GAIG;AACH,wBAAgB,MAAM,IAAI,MAAM,CAM/B;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,IAAI,MAAM,CAehC;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CAkOjC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAI,YAAY,EAAE,CAO9C"}
|
|
@@ -124,8 +124,40 @@ should). So do NOT check a fixed checklist: enumerate the state transitions YOUR
|
|
|
124
124
|
specific game defines (whatever its own actions, events, and restart do), then
|
|
125
125
|
drive each one and confirm the before/after state is what you intended, before
|
|
126
126
|
AND after a full round or restart. If reading state off the screen is hard, add
|
|
127
|
-
a temporary \`printh\` to dump the values and confirm them directly.
|
|
128
|
-
|
|
127
|
+
a temporary \`printh\` to dump the values and confirm them directly.
|
|
128
|
+
|
|
129
|
+
Two checks that catch the bugs models ship most:
|
|
130
|
+
|
|
131
|
+
- **Every DISTINCT outcome, in the right direction.** When the game has more than
|
|
132
|
+
one kind of interaction (different objects, hazards, or actions), drive EACH
|
|
133
|
+
kind separately and confirm its effect has the intended SIGN, a reward raises
|
|
134
|
+
what it should, a penalty lowers what it should. Do not verify one happy path
|
|
135
|
+
and assume the rest; the classic bug is two different interactions wired to the
|
|
136
|
+
same or opposite-signed effect (a hazard that adds score instead of costing a
|
|
137
|
+
life).
|
|
138
|
+
- **Is it actually a GAME (can the player lose)?** After driving it, run the
|
|
139
|
+
design self-checks against what you SAW, not just the design on paper: can the
|
|
140
|
+
player genuinely fail (is there real pressure, or did you never come close to
|
|
141
|
+
losing, meaning it is too easy)? does it escalate? is it fair (no unavoidable
|
|
142
|
+
loss)? If you drove a full round and nothing threatened you, tune it before
|
|
143
|
+
calling it done. (The \`game-design-reference\` skill carries these self-checks.)
|
|
144
|
+
- **JUDGE the frame, do not just confirm it drew.** When you look at a gameplay
|
|
145
|
+
screenshot, actively hunt for what is WRONG, do not narrate that it rendered.
|
|
146
|
+
"It renders" is NOT "it is good". Ask concretely:
|
|
147
|
+
- **Readability / contrast:** can you tell EVERY entity apart, from the
|
|
148
|
+
background AND from each other, at a glance? If a hazard or pickup blends
|
|
149
|
+
into the scenery (same or near colour), that is a bug, recolour it. Every
|
|
150
|
+
thing the player must react to has to be instantly distinguishable.
|
|
151
|
+
- **Layout fairness:** are hazards spaced and placed so a HUMAN can actually
|
|
152
|
+
clear them (react in ~250ms, not frame-perfect)? Gaps too tight to pass, or
|
|
153
|
+
an obstacle you cannot see coming, are fairness defects, not difficulty.
|
|
154
|
+
- **Honest quality:** does it look intentional, or just non-broken? If a
|
|
155
|
+
player saw this, would the visuals read as deliberate?
|
|
156
|
+
These are the \`game-design-reference\` readability + fairness lenses applied to
|
|
157
|
+
the IMAGE you are looking at, not only to the logic. Apply them to the actual
|
|
158
|
+
frame; fix what fails before calling it done.
|
|
159
|
+
|
|
160
|
+
A green \`verify\`, a bare \`timeout\`, or a nice-looking title is NOT done.
|
|
129
161
|
|
|
130
162
|
## Handing the finished cart to the user
|
|
131
163
|
|
|
@@ -207,6 +239,17 @@ in \`picopilot.json\`), so you never silently destroy map data you cannot see.
|
|
|
207
239
|
- **Cart data:** \`cartdata(id)\`, \`dget(i)\`, \`dset(i,v)\`.
|
|
208
240
|
- **Debug:** \`printh(str)\` (to host stdout, \`picopilot run\` captures it), \`stat(n)\`.
|
|
209
241
|
|
|
242
|
+
**Do NOT guess an API name.** If a function is not in this list and you are not
|
|
243
|
+
SURE it exists, look it up before writing the call, do not invent one. The
|
|
244
|
+
common crashes: random is \`rnd(x)\` (a float in \`[0,x)\`; integer \`flr(rnd(n))\`),
|
|
245
|
+
NOT \`rand\`/\`ranf\`/\`random\`; there is NO built-in collision (write an AABB
|
|
246
|
+
overlap yourself); \`spr\` is \`spr(n,x,y,[w,h],[fx],[fy])\` with NO color/rotate
|
|
247
|
+
arg; persistence is \`cartdata\`+\`dset\`/\`dget\`, not \`poke4\`; bare \`flr\`/\`abs\`/\`min\`
|
|
248
|
+
(no \`math.\`); \`add\`/\`del\`/\`deli\` (no \`table.insert\`). The \`picopilot-code\` skill
|
|
249
|
+
ships a full \`reference/pico8-api.md\` (exact signatures + a wrong-name table);
|
|
250
|
+
read it when unsure. A nil-call crash costs a whole playtest round; the lookup
|
|
251
|
+
does not.
|
|
252
|
+
|
|
210
253
|
## Gotchas that silently break carts (these differ from normal Lua/math)
|
|
211
254
|
|
|
212
255
|
These compile and run but behave WRONG. They are the traps models hit most:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/engine/scaffold/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAC,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAErD;;;;;;;;;GASG;AAEH,0FAA0F;AAC1F,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,UAAU,MAAM;IACrB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CACtB,sDAAsD,YAAY,aAAa,CAC/E,CAAC;IACF,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,gBAAgB,IAAI,CAAC,CAAC;IAChD,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;AACzB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO;IACtB,OAAO;;;;;;;;;;;;;CAaP,CAAC;AACF,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,aAAa;IAC5B,OAAO,iBAAiB,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ;IACvB,OAAO
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/engine/scaffold/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAC,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAErD;;;;;;;;;GASG;AAEH,0FAA0F;AAC1F,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,UAAU,MAAM;IACrB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CACtB,sDAAsD,YAAY,aAAa,CAC/E,CAAC;IACF,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,gBAAgB,IAAI,CAAC,CAAC;IAChD,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;AACzB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO;IACtB,OAAO;;;;;;;;;;;;;CAaP,CAAC;AACF,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,aAAa;IAC5B,OAAO,iBAAiB,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ;IACvB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgOP,CAAC;AACF,CAAC;AAYD;;;;;;GAMG;AACH,MAAM,UAAU,aAAa;IAC5B,OAAO;QACN,EAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,EAAC;QACpC,EAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAC;QACtC,EAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAC;QACxC,EAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,EAAE,EAAC;KAClD,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -137,6 +137,32 @@ describe('picopilot init: AGENTS.md carries the curated PICO-8 reference', () =>
|
|
|
137
137
|
expect(lower).toContain('enumerate');
|
|
138
138
|
expect(lower).toMatch(/your (specific )?game/);
|
|
139
139
|
expect(lower).toContain('do not check a fixed checklist');
|
|
140
|
+
// Two named checks: distinct-outcome correctness (sign of the effect) and
|
|
141
|
+
// is-it-a-game / can-the-player-lose (the too-easy check), both generic.
|
|
142
|
+
expect(lower).toContain('distinct');
|
|
143
|
+
expect(lower).toMatch(/sign|direction/);
|
|
144
|
+
expect(lower).toMatch(/can the player lose|player.*(lose|fail)/);
|
|
145
|
+
expect(lower).toContain('too easy');
|
|
146
|
+
// Judge the FRAME, not just confirm it drew: readability/contrast, layout
|
|
147
|
+
// fairness (spacing), and honest quality. These catch the tree-contrast /
|
|
148
|
+
// too-tight-gap / weak-graphics issues a large model still shipped.
|
|
149
|
+
expect(lower).toMatch(/judge the frame|judge the rendered/);
|
|
150
|
+
expect(lower).toContain('contrast');
|
|
151
|
+
expect(lower).toMatch(/distinguish/);
|
|
152
|
+
expect(lower).toMatch(/spaced|spacing|gap/);
|
|
153
|
+
// Framed as hunting for what is WRONG, and "renders" is not "good".
|
|
154
|
+
expect(lower).toContain('wrong');
|
|
155
|
+
expect(agents).toContain('"It renders" is NOT "it is good"');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('warns against guessing the API and points at the shipped pico8-api reference', () => {
|
|
159
|
+
const lower = agents.toLowerCase();
|
|
160
|
+
expect(lower).toContain('do not guess');
|
|
161
|
+
expect(agents).toContain('reference/pico8-api.md');
|
|
162
|
+
// Names the exact traps from the observed run: rnd-not-rand, no collision.
|
|
163
|
+
expect(agents).toContain('rnd(x)');
|
|
164
|
+
expect(agents).toContain('rand');
|
|
165
|
+
expect(agents.toLowerCase()).toContain('no built-in collision');
|
|
140
166
|
});
|
|
141
167
|
|
|
142
168
|
it('hands the finished cart off via `pico8 -run`, NOT `serve`', () => {
|
|
@@ -133,8 +133,40 @@ should). So do NOT check a fixed checklist: enumerate the state transitions YOUR
|
|
|
133
133
|
specific game defines (whatever its own actions, events, and restart do), then
|
|
134
134
|
drive each one and confirm the before/after state is what you intended, before
|
|
135
135
|
AND after a full round or restart. If reading state off the screen is hard, add
|
|
136
|
-
a temporary \`printh\` to dump the values and confirm them directly.
|
|
137
|
-
|
|
136
|
+
a temporary \`printh\` to dump the values and confirm them directly.
|
|
137
|
+
|
|
138
|
+
Two checks that catch the bugs models ship most:
|
|
139
|
+
|
|
140
|
+
- **Every DISTINCT outcome, in the right direction.** When the game has more than
|
|
141
|
+
one kind of interaction (different objects, hazards, or actions), drive EACH
|
|
142
|
+
kind separately and confirm its effect has the intended SIGN, a reward raises
|
|
143
|
+
what it should, a penalty lowers what it should. Do not verify one happy path
|
|
144
|
+
and assume the rest; the classic bug is two different interactions wired to the
|
|
145
|
+
same or opposite-signed effect (a hazard that adds score instead of costing a
|
|
146
|
+
life).
|
|
147
|
+
- **Is it actually a GAME (can the player lose)?** After driving it, run the
|
|
148
|
+
design self-checks against what you SAW, not just the design on paper: can the
|
|
149
|
+
player genuinely fail (is there real pressure, or did you never come close to
|
|
150
|
+
losing, meaning it is too easy)? does it escalate? is it fair (no unavoidable
|
|
151
|
+
loss)? If you drove a full round and nothing threatened you, tune it before
|
|
152
|
+
calling it done. (The \`game-design-reference\` skill carries these self-checks.)
|
|
153
|
+
- **JUDGE the frame, do not just confirm it drew.** When you look at a gameplay
|
|
154
|
+
screenshot, actively hunt for what is WRONG, do not narrate that it rendered.
|
|
155
|
+
"It renders" is NOT "it is good". Ask concretely:
|
|
156
|
+
- **Readability / contrast:** can you tell EVERY entity apart, from the
|
|
157
|
+
background AND from each other, at a glance? If a hazard or pickup blends
|
|
158
|
+
into the scenery (same or near colour), that is a bug, recolour it. Every
|
|
159
|
+
thing the player must react to has to be instantly distinguishable.
|
|
160
|
+
- **Layout fairness:** are hazards spaced and placed so a HUMAN can actually
|
|
161
|
+
clear them (react in ~250ms, not frame-perfect)? Gaps too tight to pass, or
|
|
162
|
+
an obstacle you cannot see coming, are fairness defects, not difficulty.
|
|
163
|
+
- **Honest quality:** does it look intentional, or just non-broken? If a
|
|
164
|
+
player saw this, would the visuals read as deliberate?
|
|
165
|
+
These are the \`game-design-reference\` readability + fairness lenses applied to
|
|
166
|
+
the IMAGE you are looking at, not only to the logic. Apply them to the actual
|
|
167
|
+
frame; fix what fails before calling it done.
|
|
168
|
+
|
|
169
|
+
A green \`verify\`, a bare \`timeout\`, or a nice-looking title is NOT done.
|
|
138
170
|
|
|
139
171
|
## Handing the finished cart to the user
|
|
140
172
|
|
|
@@ -216,6 +248,17 @@ in \`picopilot.json\`), so you never silently destroy map data you cannot see.
|
|
|
216
248
|
- **Cart data:** \`cartdata(id)\`, \`dget(i)\`, \`dset(i,v)\`.
|
|
217
249
|
- **Debug:** \`printh(str)\` (to host stdout, \`picopilot run\` captures it), \`stat(n)\`.
|
|
218
250
|
|
|
251
|
+
**Do NOT guess an API name.** If a function is not in this list and you are not
|
|
252
|
+
SURE it exists, look it up before writing the call, do not invent one. The
|
|
253
|
+
common crashes: random is \`rnd(x)\` (a float in \`[0,x)\`; integer \`flr(rnd(n))\`),
|
|
254
|
+
NOT \`rand\`/\`ranf\`/\`random\`; there is NO built-in collision (write an AABB
|
|
255
|
+
overlap yourself); \`spr\` is \`spr(n,x,y,[w,h],[fx],[fy])\` with NO color/rotate
|
|
256
|
+
arg; persistence is \`cartdata\`+\`dset\`/\`dget\`, not \`poke4\`; bare \`flr\`/\`abs\`/\`min\`
|
|
257
|
+
(no \`math.\`); \`add\`/\`del\`/\`deli\` (no \`table.insert\`). The \`picopilot-code\` skill
|
|
258
|
+
ships a full \`reference/pico8-api.md\` (exact signatures + a wrong-name table);
|
|
259
|
+
read it when unsure. A nil-call crash costs a whole playtest round; the lookup
|
|
260
|
+
does not.
|
|
261
|
+
|
|
219
262
|
## Gotchas that silently break carts (these differ from normal Lua/math)
|
|
220
263
|
|
|
221
264
|
These compile and run but behave WRONG. They are the traps models hit most:
|
|
@@ -21,6 +21,11 @@ function readSkill(name: string): string {
|
|
|
21
21
|
return readFileSync(join(skillsSourceDir(), name, 'SKILL.md'), 'utf8');
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** Reads a skill RESOURCE file (a non-SKILL.md sibling, e.g. a reference doc). */
|
|
25
|
+
function readRef(skill: string, rel: string): string {
|
|
26
|
+
return readFileSync(join(skillsSourceDir(), skill, rel), 'utf8');
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
describe('picopilot skills: generation output (the authored discipline skills)', () => {
|
|
25
30
|
it('ships exactly the named authored skills, each with valid frontmatter', () => {
|
|
26
31
|
expect([...SKILL_NAMES].sort()).toEqual(
|
|
@@ -63,6 +68,12 @@ describe('picopilot skills: generation output (the authored discipline skills)',
|
|
|
63
68
|
expect(md).toContain('frame-perfect');
|
|
64
69
|
// Visibility (draw what you spr()).
|
|
65
70
|
expect(md).toContain('spr(');
|
|
71
|
+
// Readability judged on the RENDERED FRAME, not just the code: the
|
|
72
|
+
// contrast/spacing lens a large model skipped (it confirmed "renders" but
|
|
73
|
+
// never asked if entities were distinguishable or gaps clearable).
|
|
74
|
+
expect(md.toLowerCase()).toContain('rendered frame');
|
|
75
|
+
expect(md.toLowerCase()).toContain('contrast');
|
|
76
|
+
expect(md.toLowerCase()).toMatch(/distinguish|tell every entity apart/);
|
|
66
77
|
// Originality is a METHOD with a self-check, and lists NO concrete
|
|
67
78
|
// mechanics (the answer-menu trap). Assert the caveat that guards it.
|
|
68
79
|
expect(md.toLowerCase()).toContain('self-check');
|
|
@@ -130,6 +141,22 @@ describe('picopilot skills: generation output (the authored discipline skills)',
|
|
|
130
141
|
expect(md).toContain('+=');
|
|
131
142
|
});
|
|
132
143
|
|
|
144
|
+
it('picopilot-code POINTS AT the pico8-api reference (do-not-guess), and the reference names the traps', () => {
|
|
145
|
+
const md = readSkill('picopilot-code');
|
|
146
|
+
// The SKILL.md pointer: an on-demand "do not guess, read the API" pointer.
|
|
147
|
+
expect(md).toContain('reference/pico8-api.md');
|
|
148
|
+
expect(md.toLowerCase()).toMatch(/do not guess|don't guess/);
|
|
149
|
+
// The reference resource itself: exact signatures + the wrong-name table that
|
|
150
|
+
// covers the exact mistakes a weak model made (rnd-not-rand, no collision,
|
|
151
|
+
// spr arg count).
|
|
152
|
+
const api = readRef('picopilot-code', 'reference/pico8-api.md');
|
|
153
|
+
expect(api).toContain('rnd(x)');
|
|
154
|
+
expect(api).toContain('rand');
|
|
155
|
+
expect(api).toContain('rectcol');
|
|
156
|
+
expect(api).toContain('spr(n,x,y,[w,h]');
|
|
157
|
+
expect(api.toLowerCase()).toContain('do not exist');
|
|
158
|
+
});
|
|
159
|
+
|
|
133
160
|
it('picopilot-art carries the render→look→set→render loop AND the non-multimodal fallback', () => {
|
|
134
161
|
const md = readSkill('picopilot-art');
|
|
135
162
|
// The art loop.
|
|
@@ -314,6 +341,7 @@ describe('picopilot skills: isolated --install-skills (the load-bearing shared-w
|
|
|
314
341
|
expect(existsSync(join(codeSkill, 'SKILL.md'))).toBe(true);
|
|
315
342
|
for (const res of [
|
|
316
343
|
'reference/README.md',
|
|
344
|
+
'reference/pico8-api.md',
|
|
317
345
|
'reference/platformer.md',
|
|
318
346
|
'reference/puzzle-grid.md',
|
|
319
347
|
'reference/twin-stick-arcade.md',
|
|
@@ -41,6 +41,8 @@ If you `spr(n)` an entity, sprite n must actually be DRAWN (a non-empty sprite),
|
|
|
41
41
|
|
|
42
42
|
The player must be able to READ the game state at a glance and get FEEDBACK for every action: what am I, what is a hazard, what is a goal, did my input register, did I score or get hit. Motion trails, colour contrast, a HUD, particles/shake/sfx on events, these are not polish for its own sake, they are how the player perceives the state they must act on.
|
|
43
43
|
|
|
44
|
+
**The self-check (apply it to the RENDERED FRAME, not just the code).** When you look at a gameplay screenshot, judge it, do not just confirm it drew. Looking for what is WRONG: can you tell EVERY entity apart from the background and from each other at a glance (a hazard the same colour as the scenery is a readability bug, recolour it)? is every hazard telegraphed and spaced so a human can react in ~250ms rather than by frame-perfect timing (a gap too tight to pass, or an off-screen hazard, is a fairness defect, not difficulty)? does it look intentional, or merely non-broken? "It renders" is not "it is readable and fair". Apply these lenses to the actual image and fix what fails before calling the game done.
|
|
45
|
+
|
|
44
46
|
## Difficulty curve and progression
|
|
45
47
|
|
|
46
48
|
A good game GOES SOMEWHERE: it escalates. Ramp difficulty by introducing or combining MECHANICS (a new hazard type, a new interaction) over the playthrough, not merely by piling more of the same obstacle. More-of-the-same reads as samey and does not deepen; a new wrinkle does. Aim for a difficulty ARC with a satisfying peak, not a flat loop. Keep the first contact with each new mechanic gentle enough to learn it before it is tested under pressure.
|
|
@@ -49,6 +49,19 @@ run. A green verify is well-formed, not proven-to-run, so it points you at
|
|
|
49
49
|
`picopilot run`. With shrinko absent, verify is `gate-incapable` (never green),
|
|
50
50
|
because it cannot check token bloat, the failure this loop exists to catch.
|
|
51
51
|
|
|
52
|
+
## Do NOT guess the API: read `reference/pico8-api.md`
|
|
53
|
+
|
|
54
|
+
The most expensive weak-model mistake is calling a function that does not exist
|
|
55
|
+
(`rand`, `ranf`, `rectcol`, a `spr` with too many args) and only finding out when
|
|
56
|
+
a playtest round crashes with `attempt to call nil value`. When you are unsure a
|
|
57
|
+
function exists or what its arguments are, READ `reference/pico8-api.md` before
|
|
58
|
+
writing the call: it lists the exact names + signatures of the common API and a
|
|
59
|
+
table of the wrong names LLMs reach for (from mainstream Lua / other engines) and
|
|
60
|
+
their real PICO-8 equivalents. Random is `rnd(x)` (a float in `[0,x)`), not
|
|
61
|
+
`rand`/`ranf`; there is NO built-in collision (write an AABB); `spr` is
|
|
62
|
+
`spr(n,x,y,[w,h],[fx],[fy])` with no color/rotate arg. Checking the reference is
|
|
63
|
+
far cheaper than burning a run on a nil-call crash.
|
|
64
|
+
|
|
52
65
|
## PICO-8 gotchas that break carts silently
|
|
53
66
|
|
|
54
67
|
These are NOT token issues; they are behaviours that differ from mainstream Lua
|
|
@@ -94,4 +107,5 @@ from this skill's `reference/` folder (see `reference/README.md` for the index):
|
|
|
94
107
|
- `reference/rpg-menus-dialog.md`: UI state stack, cursor menus, timed text reveal.
|
|
95
108
|
|
|
96
109
|
Each carries a minimal idiomatic implementation and a genre-pitfalls checklist.
|
|
97
|
-
Adapt them to your game.
|
|
110
|
+
Adapt them to your game. `reference/pico8-api.md` (the exact API surface) sits in
|
|
111
|
+
the same folder for when you need to look a function up.
|
|
@@ -7,7 +7,13 @@ implementations written in idiomatic, token-cheap PICO-8 Lua (see the
|
|
|
7
7
|
`picopilot-code` SKILL for the token discipline they follow). They teach the
|
|
8
8
|
STRUCTURE and the genre-specific pitfalls; adapt them to your game.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
First, whenever you are unsure a PICO-8 function exists or what its arguments
|
|
11
|
+
are, read the API surface (do NOT guess a name like `rand`/`rectcol`):
|
|
12
|
+
|
|
13
|
+
- `pico8-api.md`: EXACT names + signatures for the whole common API, plus a
|
|
14
|
+
table of wrong names that do NOT exist in PICO-8 and their real equivalents.
|
|
15
|
+
|
|
16
|
+
Then load the ONE genre file that matches your game:
|
|
11
17
|
|
|
12
18
|
- `platformer.md`: side-view gravity/jump, per-axis tile collision, camera
|
|
13
19
|
follow.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# PICO-8 API reference (exact names and signatures)
|
|
2
|
+
|
|
3
|
+
The PICO-8 function surface with EXACT names and argument order. Read this instead of guessing: the #1 way an agent wastes a run is calling a function that does not exist (`rand`, `ranf`, `rectcol`) or passing the wrong arguments, then burning a playtest round on an `attempt to call nil value` crash. If you are unsure a function exists or what its arguments are, look it up HERE first. `[x]` marks an optional argument.
|
|
4
|
+
|
|
5
|
+
## Do NOT guess: names that do NOT exist in PICO-8
|
|
6
|
+
|
|
7
|
+
These are the wrong names an LLM reaches for out of habit (mainstream Lua, JS, other engines). Each one crashes or silently misbehaves. Use the RIGHT column.
|
|
8
|
+
|
|
9
|
+
| You may type (WRONG) | PICO-8 reality |
|
|
10
|
+
|----------------------|----------------|
|
|
11
|
+
| `rand()`, `ranf()`, `random()`, `math.random` | `rnd(x)` returns a float in `[0,x)`; `rnd()` is `[0,1)`. Integer: `flr(rnd(n))`. `rnd(tbl)` picks a random element. |
|
|
12
|
+
| `rand(lo,hi)` (a range) | there is NO range form. Do `lo+rnd(hi-lo)` (float) or `lo+flr(rnd(hi-lo))` (int). |
|
|
13
|
+
| `rectcol`, `collide`, `intersect`, `overlap` | NO built-in collision. Write your own AABB (see below). |
|
|
14
|
+
| `math.floor`, `math.abs`, `math.min`, `math.max`, `math.sqrt` | bare `flr`, `abs`, `min`, `max`, `sqrt` (no `math.` table). |
|
|
15
|
+
| `table.insert`, `table.remove` | `add(t,v)`, `del(t,v)` (by value), `deli(t,i)` (by index). |
|
|
16
|
+
| `string.sub`, `s:sub(..)`, `#s` for chars | `sub(s,i,j)` (1-based, inclusive both ends). |
|
|
17
|
+
| `print(x,y,...)` with x,y swapped, or `printf` | `print(str,[x,y,[col]])` (string FIRST). |
|
|
18
|
+
| `pset(x,y,col)` ok, but `pget(x,y,col)` | `pget(x,y)` takes NO color (it READS one). |
|
|
19
|
+
| `2*pi`, `sin(radians)` | `sin`/`cos` take TURNS `0..1`, and `sin` is INVERTED. A full circle is `t:0->1`. Do NOT multiply by pi. |
|
|
20
|
+
| `poke4` for "save a number" | persistence is `cartdata(id)` then `dset(i,v)` / `dget(i)`. (`poke2/poke4` exist but are raw 16/32-bit RAM writes, not a save API.) |
|
|
21
|
+
| `//` (Lua 5.3 int-divide) | `\` is integer divide (`5\2`==2). `/` does NOT truncate. |
|
|
22
|
+
| shadowing builtins as locals: `local type`, `local add`, `local t=...` near `t=time()` | do not name locals after builtins/globals you use (`type`, `add`, `del`, `t`, `x`, `y`, `pal`, `time`, `sub`). It silently breaks calls to them. |
|
|
23
|
+
|
|
24
|
+
## AABB overlap (there is no built-in collision, write this)
|
|
25
|
+
|
|
26
|
+
```lua
|
|
27
|
+
function hit(ax,ay,aw,ah, bx,by,bw,bh)
|
|
28
|
+
return ax<bx+bw and bx<ax+aw and ay<by+bh and by<ay+ah
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Graphics
|
|
33
|
+
|
|
34
|
+
- `cls([col])` clear screen (to `col`, default 0).
|
|
35
|
+
- `spr(n,x,y,[w,h],[flip_x],[flip_y])` draw sprite `n` at `x,y`. `w,h` are in SPRITES (default 1,1), NOT pixels. There is NO color/rotate/scale arg; that is `sspr`/`pal` territory. Do NOT pass extra args.
|
|
36
|
+
- `sspr(sx,sy,sw,sh,dx,dy,[dw,dh],[flip_x],[flip_y])` stretch a spritesheet rect.
|
|
37
|
+
- `map(cx,cy,sx,sy,cw,ch,[layer])` draw map cels `cx,cy` (size `cw,ch`) at screen `sx,sy`.
|
|
38
|
+
- `pset(x,y,[col])`, `pget(x,y)` (pget reads, no col arg).
|
|
39
|
+
- `sget(x,y)`, `sset(x,y,[col])` read/write a spritesheet pixel.
|
|
40
|
+
- `print(str,[x,y,[col]])` string is the FIRST arg. Returns the x cursor after.
|
|
41
|
+
- `rect(x0,y0,x1,y1,[col])`, `rectfill(x0,y0,x1,y1,[col])` (two CORNERS, not x,y,w,h).
|
|
42
|
+
- `circ(x,y,r,[col])`, `circfill(x,y,r,[col])`.
|
|
43
|
+
- `oval(x0,y0,x1,y1,[col])`, `ovalfill(...)` (bounding-box corners).
|
|
44
|
+
- `line(x0,y0,x1,y1,[col])`.
|
|
45
|
+
- `pal([c0,c1,[p]])` remap color `c0`->`c1` (p=0 draw, 1 screen). No-arg `pal()` RESETS.
|
|
46
|
+
- `palt([col,t])` set color `col` transparent (`t` true/false). No-arg resets (0 transparent).
|
|
47
|
+
- `camera([x,y])` offset all later draws. No-arg resets.
|
|
48
|
+
- `clip([x,y,w,h])` clip rect. No-arg resets.
|
|
49
|
+
- `color([col])` set the default pen color.
|
|
50
|
+
- `fillp([pat])` set the fill pattern for shape fns.
|
|
51
|
+
- `flip()` present the frame (rarely needed; `_draw` flips for you).
|
|
52
|
+
- `cursor([x,y,[col]])`, `fget`/`fset` (flags, under Map).
|
|
53
|
+
|
|
54
|
+
## Map and sprite flags
|
|
55
|
+
|
|
56
|
+
- `mget(x,y)` read the map cel (sprite index) at tile `x,y`.
|
|
57
|
+
- `mset(x,y,v)` write a map cel.
|
|
58
|
+
- `fget(n,[f])` get sprite `n` flags (all as a bitfield, or bit `f`).
|
|
59
|
+
- `fset(n,[f],v)` set sprite flags.
|
|
60
|
+
|
|
61
|
+
## Input
|
|
62
|
+
|
|
63
|
+
- `btn([i,[p]])` is button `i` HELD? (i=0..5 = left,right,up,down,O,X; p=player 0..7). No-arg returns a bitfield.
|
|
64
|
+
- `btnp([i,[p]])` was button `i` just PRESSED this frame (with auto-repeat)?
|
|
65
|
+
- Button ids: 0 left, 1 right, 2 up, 3 down, 4 O (z/c/n), 5 X (x/v/m).
|
|
66
|
+
|
|
67
|
+
## Audio
|
|
68
|
+
|
|
69
|
+
- `sfx(n,[channel,[offset,[length]]])` play sfx `n`. `n=-1` stops. `channel=-1` any, `-2` stop on the channel.
|
|
70
|
+
- `music([n,[fade_ms,[channel_mask]]])` play music pattern `n`. `n=-1` stops.
|
|
71
|
+
|
|
72
|
+
## Math (all bare, no `math.` table)
|
|
73
|
+
|
|
74
|
+
- `flr(x)` floor (round toward -inf). `ceil(x)` = `-flr(-x)`.
|
|
75
|
+
- `abs`, `min(a,b)`, `max(a,b)`, `mid(a,b,c)` (the middle value = idiomatic CLAMP), `sgn(x)`.
|
|
76
|
+
- `sqrt(x)`, `rnd([x])` (float `[0,x)`; `rnd(tbl)` picks an element), `srand(x)` (seed).
|
|
77
|
+
- `sin(t)`, `cos(t)` take TURNS `0..1`; `sin` is INVERTED (screen +y down). `atan2(dx,dy)` returns a turn `0..1`.
|
|
78
|
+
- `%` modulo, `\` integer divide, `&` `|` `^^` `~` `<<` `>>` bitops (Lua-5.3-style ops are NOT all present; use these).
|
|
79
|
+
|
|
80
|
+
## Tables and iteration (1-BASED)
|
|
81
|
+
|
|
82
|
+
- `add(t,v,[i])` append (or insert at `i`), returns `v`.
|
|
83
|
+
- `del(t,v)` remove the first element EQUAL to `v` (by value).
|
|
84
|
+
- `deli(t,[i])` remove the element at INDEX `i` (default last), returns it.
|
|
85
|
+
- `count(t,[v])` length (or count of `v`). `#t` also works for a contiguous array.
|
|
86
|
+
- `all(t)` iterator for `for x in all(t) do`. `foreach(t,fn)` call `fn(v)` per element.
|
|
87
|
+
- `pairs(t)`, `ipairs(t)`, `next(t)`. `unpack(t)`, `pack(...)`.
|
|
88
|
+
- Deleting the CURRENT item is safe in `foreach` and in a DESCENDING `for i=#t,1,-1`; an ASCENDING `for i=1,#t` ERRORS once the list shrinks.
|
|
89
|
+
|
|
90
|
+
## Strings
|
|
91
|
+
|
|
92
|
+
- `sub(s,i,[j])` substring, 1-based, INCLUSIVE both ends (`sub("abc",2,3)`=="bc").
|
|
93
|
+
- `#s` length, `..` concat, `tostr(x,[hex])`, `tonum(s)`, `chr(n)`, `ord(s,[i])`.
|
|
94
|
+
- `split(s,[sep,[convert]])` -> a 1-based array (great for cheap data tables). NO Lua `string` stdlib (`string.format`, `gsub`, etc. do NOT exist).
|
|
95
|
+
|
|
96
|
+
## Memory (raw RAM; wrong addresses corrupt gfx/sound/state)
|
|
97
|
+
|
|
98
|
+
- `peek(addr)`, `poke(addr,v)` one byte. `peek2/poke2` 16-bit, `peek4/poke4` 32-bit (fixed-point).
|
|
99
|
+
- `memcpy(dst,src,len)`, `memset(dst,val,len)`, `reload(...)`, `cstore(...)`.
|
|
100
|
+
- Only poke DOCUMENTED addresses (see the memory map in `AGENTS.md`). Prefer the draw functions above over poking the screen.
|
|
101
|
+
|
|
102
|
+
## Cart data (persistent save)
|
|
103
|
+
|
|
104
|
+
- `cartdata(id)` register a 64-slot save block (call ONCE in `_init`; `id` is a unique string).
|
|
105
|
+
- `dget(i)` read slot `i` (0..63), `dset(i,v)` write it. This is how you save a high score, NOT `poke4`.
|
|
106
|
+
|
|
107
|
+
## System / debug
|
|
108
|
+
|
|
109
|
+
- `time()` / `t()` seconds since start (a fixed-point float). Do NOT name a local `time`.
|
|
110
|
+
- `stat(n)` system info (`stat(6)` is the `-p`/playtest input string; others: memory, fps, etc.).
|
|
111
|
+
- `printh(str,[filename])` print to the HOST stdout (picopilot `run`/`playtest` capture this) -> your main debug channel.
|
|
112
|
+
- `menuitem(...)`, `extcmd(cmd)` (`extcmd("screen")` screenshots, used by the run probe), `stop()`, `assert(cond,[msg])`, `trace()`.
|
|
113
|
+
|
|
114
|
+
## The execution loop
|
|
115
|
+
|
|
116
|
+
- `_init()` once at start. `_update()` at 30fps (or `_update60()` at 60fps, not both). `_draw()` once per visible frame. Mutate state ONLY in `_update`; `_draw` can be skipped under load.
|
|
117
|
+
|
|
118
|
+
For the behaviours that differ from mainstream Lua/math (turns-not-radians, integer divide, 1-based tables, persistent draw state, the `and/or` ternary trap), see the gotchas in `AGENTS.md`. This file is WHAT EXISTS; that is HOW IT BEHAVES.
|