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.
- package/dist/engine/scaffold/scaffold.d.ts.map +1 -1
- package/dist/engine/scaffold/scaffold.js +30 -2
- package/dist/engine/scaffold/scaffold.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/init.test.ts +16 -0
- package/src/engine/scaffold/scaffold.ts +30 -2
- package/src/engine/skills/skills.test.ts +22 -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,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.
|
|
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
|
+
|
|
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
|
|
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
|
@@ -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.
|
|
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
|
+
|
|
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
|
-
|
|
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.
|