picopilot 0.1.2 → 0.1.3

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,CAmNjC;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,25 @@ 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
+
145
+ A green \`verify\`, a bare \`timeout\`, or a nice-looking title is NOT done.
129
146
 
130
147
  ## Handing the finished cart to the user
131
148
 
@@ -207,6 +224,17 @@ in \`picopilot.json\`), so you never silently destroy map data you cannot see.
207
224
  - **Cart data:** \`cartdata(id)\`, \`dget(i)\`, \`dset(i,v)\`.
208
225
  - **Debug:** \`printh(str)\` (to host stdout, \`picopilot run\` captures it), \`stat(n)\`.
209
226
 
227
+ **Do NOT guess an API name.** If a function is not in this list and you are not
228
+ SURE it exists, look it up before writing the call, do not invent one. The
229
+ common crashes: random is \`rnd(x)\` (a float in \`[0,x)\`; integer \`flr(rnd(n))\`),
230
+ NOT \`rand\`/\`ranf\`/\`random\`; there is NO built-in collision (write an AABB
231
+ overlap yourself); \`spr\` is \`spr(n,x,y,[w,h],[fx],[fy])\` with NO color/rotate
232
+ arg; persistence is \`cartdata\`+\`dset\`/\`dget\`, not \`poke4\`; bare \`flr\`/\`abs\`/\`min\`
233
+ (no \`math.\`); \`add\`/\`del\`/\`deli\` (no \`table.insert\`). The \`picopilot-code\` skill
234
+ ships a full \`reference/pico8-api.md\` (exact signatures + a wrong-name table);
235
+ read it when unsure. A nil-call crash costs a whole playtest round; the lookup
236
+ does not.
237
+
210
238
  ## Gotchas that silently break carts (these differ from normal Lua/math)
211
239
 
212
240
  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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiNP,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.3",
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,22 @@ 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
+ });
147
+
148
+ it('warns against guessing the API and points at the shipped pico8-api reference', () => {
149
+ const lower = agents.toLowerCase();
150
+ expect(lower).toContain('do not guess');
151
+ expect(agents).toContain('reference/pico8-api.md');
152
+ // Names the exact traps from the observed run: rnd-not-rand, no collision.
153
+ expect(agents).toContain('rnd(x)');
154
+ expect(agents).toContain('rand');
155
+ expect(agents.toLowerCase()).toContain('no built-in collision');
140
156
  });
141
157
 
142
158
  it('hands the finished cart off via `pico8 -run`, NOT `serve`', () => {
@@ -133,8 +133,25 @@ 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
+
154
+ A green \`verify\`, a bare \`timeout\`, or a nice-looking title is NOT done.
138
155
 
139
156
  ## Handing the finished cart to the user
140
157
 
@@ -216,6 +233,17 @@ in \`picopilot.json\`), so you never silently destroy map data you cannot see.
216
233
  - **Cart data:** \`cartdata(id)\`, \`dget(i)\`, \`dset(i,v)\`.
217
234
  - **Debug:** \`printh(str)\` (to host stdout, \`picopilot run\` captures it), \`stat(n)\`.
218
235
 
236
+ **Do NOT guess an API name.** If a function is not in this list and you are not
237
+ SURE it exists, look it up before writing the call, do not invent one. The
238
+ common crashes: random is \`rnd(x)\` (a float in \`[0,x)\`; integer \`flr(rnd(n))\`),
239
+ NOT \`rand\`/\`ranf\`/\`random\`; there is NO built-in collision (write an AABB
240
+ overlap yourself); \`spr\` is \`spr(n,x,y,[w,h],[fx],[fy])\` with NO color/rotate
241
+ arg; persistence is \`cartdata\`+\`dset\`/\`dget\`, not \`poke4\`; bare \`flr\`/\`abs\`/\`min\`
242
+ (no \`math.\`); \`add\`/\`del\`/\`deli\` (no \`table.insert\`). The \`picopilot-code\` skill
243
+ ships a full \`reference/pico8-api.md\` (exact signatures + a wrong-name table);
244
+ read it when unsure. A nil-call crash costs a whole playtest round; the lookup
245
+ does not.
246
+
219
247
  ## Gotchas that silently break carts (these differ from normal Lua/math)
220
248
 
221
249
  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(
@@ -130,6 +135,22 @@ describe('picopilot skills: generation output (the authored discipline skills)',
130
135
  expect(md).toContain('+=');
131
136
  });
132
137
 
138
+ it('picopilot-code POINTS AT the pico8-api reference (do-not-guess), and the reference names the traps', () => {
139
+ const md = readSkill('picopilot-code');
140
+ // The SKILL.md pointer: an on-demand "do not guess, read the API" pointer.
141
+ expect(md).toContain('reference/pico8-api.md');
142
+ expect(md.toLowerCase()).toMatch(/do not guess|don't guess/);
143
+ // The reference resource itself: exact signatures + the wrong-name table that
144
+ // covers the exact mistakes a weak model made (rnd-not-rand, no collision,
145
+ // spr arg count).
146
+ const api = readRef('picopilot-code', 'reference/pico8-api.md');
147
+ expect(api).toContain('rnd(x)');
148
+ expect(api).toContain('rand');
149
+ expect(api).toContain('rectcol');
150
+ expect(api).toContain('spr(n,x,y,[w,h]');
151
+ expect(api.toLowerCase()).toContain('do not exist');
152
+ });
153
+
133
154
  it('picopilot-art carries the render→look→set→render loop AND the non-multimodal fallback', () => {
134
155
  const md = readSkill('picopilot-art');
135
156
  // The art loop.
@@ -314,6 +335,7 @@ describe('picopilot skills: isolated --install-skills (the load-bearing shared-w
314
335
  expect(existsSync(join(codeSkill, 'SKILL.md'))).toBe(true);
315
336
  for (const res of [
316
337
  'reference/README.md',
338
+ 'reference/pico8-api.md',
317
339
  'reference/platformer.md',
318
340
  'reference/puzzle-grid.md',
319
341
  'reference/twin-stick-arcade.md',
@@ -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.