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.
@@ -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,CAuLjC;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"}
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. A green
128
- \`verify\`, a bare \`timeout\`, or a nice-looking title is NOT done.
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqLP,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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picopilot",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "An agent-first toolchain for PICO-8 game development, built on the incur CLI framework.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -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. A green
137
- \`verify\`, a bare \`timeout\`, or a nice-looking title is NOT done.
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
- Load the ONE that matches your game:
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.