picopilot 0.0.0 → 0.1.1
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/LICENSE +661 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/audio.d.ts +17 -0
- package/dist/commands/audio.d.ts.map +1 -0
- package/dist/commands/audio.js +315 -0
- package/dist/commands/audio.js.map +1 -0
- package/dist/commands/export.d.ts +36 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +257 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/gfx.d.ts +28 -0
- package/dist/commands/gfx.d.ts.map +1 -0
- package/dist/commands/gfx.js +521 -0
- package/dist/commands/gfx.js.map +1 -0
- package/dist/commands/init.d.ts +34 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +151 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +20 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +90 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/minify.d.ts +20 -0
- package/dist/commands/minify.d.ts.map +1 -0
- package/dist/commands/minify.js +129 -0
- package/dist/commands/minify.js.map +1 -0
- package/dist/commands/music.d.ts +13 -0
- package/dist/commands/music.d.ts.map +1 -0
- package/dist/commands/music.js +241 -0
- package/dist/commands/music.js.map +1 -0
- package/dist/commands/playtest.d.ts +29 -0
- package/dist/commands/playtest.d.ts.map +1 -0
- package/dist/commands/playtest.js +631 -0
- package/dist/commands/playtest.js.map +1 -0
- package/dist/commands/run.d.ts +30 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +224 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/serve.d.ts +50 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +213 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/sfx.d.ts +13 -0
- package/dist/commands/sfx.d.ts.map +1 -0
- package/dist/commands/sfx.js +254 -0
- package/dist/commands/sfx.js.map +1 -0
- package/dist/commands/tokens.d.ts +36 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +129 -0
- package/dist/commands/tokens.js.map +1 -0
- package/dist/commands/verify.d.ts +57 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +213 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/version.d.ts +12 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +24 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/engine/audio/index.d.ts +14 -0
- package/dist/engine/audio/index.d.ts.map +1 -0
- package/dist/engine/audio/index.js +14 -0
- package/dist/engine/audio/index.js.map +1 -0
- package/dist/engine/audio/music.d.ts +116 -0
- package/dist/engine/audio/music.d.ts.map +1 -0
- package/dist/engine/audio/music.js +138 -0
- package/dist/engine/audio/music.js.map +1 -0
- package/dist/engine/audio/section.d.ts +34 -0
- package/dist/engine/audio/section.d.ts.map +1 -0
- package/dist/engine/audio/section.js +67 -0
- package/dist/engine/audio/section.js.map +1 -0
- package/dist/engine/audio/sfx.d.ts +141 -0
- package/dist/engine/audio/sfx.d.ts.map +1 -0
- package/dist/engine/audio/sfx.js +506 -0
- package/dist/engine/audio/sfx.js.map +1 -0
- package/dist/engine/cart/cart.d.ts +75 -0
- package/dist/engine/cart/cart.d.ts.map +1 -0
- package/dist/engine/cart/cart.js +153 -0
- package/dist/engine/cart/cart.js.map +1 -0
- package/dist/engine/cart/errors.d.ts +29 -0
- package/dist/engine/cart/errors.d.ts.map +1 -0
- package/dist/engine/cart/errors.js +26 -0
- package/dist/engine/cart/errors.js.map +1 -0
- package/dist/engine/cart/gfx.d.ts +79 -0
- package/dist/engine/cart/gfx.d.ts.map +1 -0
- package/dist/engine/cart/gfx.js +171 -0
- package/dist/engine/cart/gfx.js.map +1 -0
- package/dist/engine/cart/index.d.ts +15 -0
- package/dist/engine/cart/index.d.ts.map +1 -0
- package/dist/engine/cart/index.js +15 -0
- package/dist/engine/cart/index.js.map +1 -0
- package/dist/engine/cart/map.d.ts +46 -0
- package/dist/engine/cart/map.d.ts.map +1 -0
- package/dist/engine/cart/map.js +118 -0
- package/dist/engine/cart/map.js.map +1 -0
- package/dist/engine/config/config.d.ts +74 -0
- package/dist/engine/config/config.d.ts.map +1 -0
- package/dist/engine/config/config.js +103 -0
- package/dist/engine/config/config.js.map +1 -0
- package/dist/engine/config/index.d.ts +7 -0
- package/dist/engine/config/index.d.ts.map +1 -0
- package/dist/engine/config/index.js +7 -0
- package/dist/engine/config/index.js.map +1 -0
- package/dist/engine/gfx/grid.d.ts +51 -0
- package/dist/engine/gfx/grid.d.ts.map +1 -0
- package/dist/engine/gfx/grid.js +116 -0
- package/dist/engine/gfx/grid.js.map +1 -0
- package/dist/engine/gfx/index.d.ts +17 -0
- package/dist/engine/gfx/index.d.ts.map +1 -0
- package/dist/engine/gfx/index.js +17 -0
- package/dist/engine/gfx/index.js.map +1 -0
- package/dist/engine/gfx/label.d.ts +34 -0
- package/dist/engine/gfx/label.d.ts.map +1 -0
- package/dist/engine/gfx/label.js +190 -0
- package/dist/engine/gfx/label.js.map +1 -0
- package/dist/engine/gfx/overlap.d.ts +42 -0
- package/dist/engine/gfx/overlap.d.ts.map +1 -0
- package/dist/engine/gfx/overlap.js +57 -0
- package/dist/engine/gfx/overlap.js.map +1 -0
- package/dist/engine/gfx/palette.d.ts +29 -0
- package/dist/engine/gfx/palette.d.ts.map +1 -0
- package/dist/engine/gfx/palette.js +46 -0
- package/dist/engine/gfx/palette.js.map +1 -0
- package/dist/engine/gfx/png.d.ts +31 -0
- package/dist/engine/gfx/png.d.ts.map +1 -0
- package/dist/engine/gfx/png.js +165 -0
- package/dist/engine/gfx/png.js.map +1 -0
- package/dist/engine/gfx/render.d.ts +42 -0
- package/dist/engine/gfx/render.d.ts.map +1 -0
- package/dist/engine/gfx/render.js +72 -0
- package/dist/engine/gfx/render.js.map +1 -0
- package/dist/engine/index.d.ts +22 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +22 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/pico8/adapter.d.ts +267 -0
- package/dist/engine/pico8/adapter.d.ts.map +1 -0
- package/dist/engine/pico8/adapter.js +37 -0
- package/dist/engine/pico8/adapter.js.map +1 -0
- package/dist/engine/pico8/drive.d.ts +308 -0
- package/dist/engine/pico8/drive.d.ts.map +1 -0
- package/dist/engine/pico8/drive.js +581 -0
- package/dist/engine/pico8/drive.js.map +1 -0
- package/dist/engine/pico8/harness.d.ts +146 -0
- package/dist/engine/pico8/harness.d.ts.map +1 -0
- package/dist/engine/pico8/harness.js +251 -0
- package/dist/engine/pico8/harness.js.map +1 -0
- package/dist/engine/pico8/index.d.ts +21 -0
- package/dist/engine/pico8/index.d.ts.map +1 -0
- package/dist/engine/pico8/index.js +21 -0
- package/dist/engine/pico8/index.js.map +1 -0
- package/dist/engine/pico8/sentinel.d.ts +63 -0
- package/dist/engine/pico8/sentinel.d.ts.map +1 -0
- package/dist/engine/pico8/sentinel.js +86 -0
- package/dist/engine/pico8/sentinel.js.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts +10 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.js +11 -0
- package/dist/engine/pico8/session-daemon-entry.js.map +1 -0
- package/dist/engine/pico8/session-daemon-main.d.ts +44 -0
- package/dist/engine/pico8/session-daemon-main.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-main.js +71 -0
- package/dist/engine/pico8/session-daemon-main.js.map +1 -0
- package/dist/engine/pico8/session-daemon.d.ts +68 -0
- package/dist/engine/pico8/session-daemon.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon.js +264 -0
- package/dist/engine/pico8/session-daemon.js.map +1 -0
- package/dist/engine/pico8/session.d.ts +140 -0
- package/dist/engine/pico8/session.d.ts.map +1 -0
- package/dist/engine/pico8/session.js +250 -0
- package/dist/engine/pico8/session.js.map +1 -0
- package/dist/engine/pico8/shell.d.ts +103 -0
- package/dist/engine/pico8/shell.d.ts.map +1 -0
- package/dist/engine/pico8/shell.js +324 -0
- package/dist/engine/pico8/shell.js.map +1 -0
- package/dist/engine/pico8/supervisor.d.ts +137 -0
- package/dist/engine/pico8/supervisor.d.ts.map +1 -0
- package/dist/engine/pico8/supervisor.js +140 -0
- package/dist/engine/pico8/supervisor.js.map +1 -0
- package/dist/engine/scaffold/index.d.ts +8 -0
- package/dist/engine/scaffold/index.d.ts.map +1 -0
- package/dist/engine/scaffold/index.js +8 -0
- package/dist/engine/scaffold/index.js.map +1 -0
- package/dist/engine/scaffold/scaffold.d.ts +49 -0
- package/dist/engine/scaffold/scaffold.d.ts.map +1 -0
- package/dist/engine/scaffold/scaffold.js +259 -0
- package/dist/engine/scaffold/scaffold.js.map +1 -0
- package/dist/engine/shrinko/adapter.d.ts +167 -0
- package/dist/engine/shrinko/adapter.d.ts.map +1 -0
- package/dist/engine/shrinko/adapter.js +107 -0
- package/dist/engine/shrinko/adapter.js.map +1 -0
- package/dist/engine/shrinko/index.d.ts +13 -0
- package/dist/engine/shrinko/index.d.ts.map +1 -0
- package/dist/engine/shrinko/index.js +13 -0
- package/dist/engine/shrinko/index.js.map +1 -0
- package/dist/engine/shrinko/shell.d.ts +69 -0
- package/dist/engine/shrinko/shell.d.ts.map +1 -0
- package/dist/engine/shrinko/shell.js +156 -0
- package/dist/engine/shrinko/shell.js.map +1 -0
- package/dist/engine/skills/index.d.ts +8 -0
- package/dist/engine/skills/index.d.ts.map +1 -0
- package/dist/engine/skills/index.js +8 -0
- package/dist/engine/skills/index.js.map +1 -0
- package/dist/engine/skills/skills.d.ts +104 -0
- package/dist/engine/skills/skills.d.ts.map +1 -0
- package/dist/engine/skills/skills.js +149 -0
- package/dist/engine/skills/skills.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +19 -0
- package/dist/version.js.map +1 -0
- package/package.json +48 -2
- package/src/bin.ts +4 -0
- package/src/cli.ts +61 -0
- package/src/commands/audio.test.ts +280 -0
- package/src/commands/audio.ts +410 -0
- package/src/commands/export.test.ts +310 -0
- package/src/commands/export.ts +316 -0
- package/src/commands/gfx-io.test.ts +178 -0
- package/src/commands/gfx.test.ts +470 -0
- package/src/commands/gfx.ts +645 -0
- package/src/commands/init.test.ts +482 -0
- package/src/commands/init.ts +193 -0
- package/src/commands/lint.test.ts +137 -0
- package/src/commands/lint.ts +117 -0
- package/src/commands/minify.test.ts +166 -0
- package/src/commands/minify.ts +156 -0
- package/src/commands/music.test.ts +272 -0
- package/src/commands/music.ts +288 -0
- package/src/commands/playtest.test.ts +317 -0
- package/src/commands/playtest.ts +773 -0
- package/src/commands/run.test.ts +264 -0
- package/src/commands/run.ts +285 -0
- package/src/commands/serve.test.ts +227 -0
- package/src/commands/serve.ts +272 -0
- package/src/commands/sfx.test.ts +349 -0
- package/src/commands/sfx.ts +300 -0
- package/src/commands/tokens.test.ts +212 -0
- package/src/commands/tokens.ts +168 -0
- package/src/commands/verify.test.ts +238 -0
- package/src/commands/verify.ts +256 -0
- package/src/commands/version.test.ts +70 -0
- package/src/commands/version.ts +24 -0
- package/src/engine/audio/index.ts +47 -0
- package/src/engine/audio/music.test.ts +188 -0
- package/src/engine/audio/music.ts +218 -0
- package/src/engine/audio/section.test.ts +124 -0
- package/src/engine/audio/section.ts +79 -0
- package/src/engine/audio/sfx.test.ts +410 -0
- package/src/engine/audio/sfx.ts +694 -0
- package/src/engine/cart/cart.test.ts +304 -0
- package/src/engine/cart/cart.ts +211 -0
- package/src/engine/cart/errors.ts +39 -0
- package/src/engine/cart/fixtures/crlf.p8 +6 -0
- package/src/engine/cart/fixtures/full.p8 +174 -0
- package/src/engine/cart/fixtures/header-only.p8 +2 -0
- package/src/engine/cart/fixtures/malformed-no-header.p8 +2 -0
- package/src/engine/cart/fixtures/minimal.p8 +10 -0
- package/src/engine/cart/fixtures/no-trailing-newline.p8 +6 -0
- package/src/engine/cart/gfx.ts +195 -0
- package/src/engine/cart/index.ts +27 -0
- package/src/engine/cart/map.ts +134 -0
- package/src/engine/config/config.ts +115 -0
- package/src/engine/config/index.ts +12 -0
- package/src/engine/gfx/grid.test.ts +128 -0
- package/src/engine/gfx/grid.ts +136 -0
- package/src/engine/gfx/index.ts +34 -0
- package/src/engine/gfx/label.test.ts +105 -0
- package/src/engine/gfx/label.ts +229 -0
- package/src/engine/gfx/overlap.test.ts +71 -0
- package/src/engine/gfx/overlap.ts +66 -0
- package/src/engine/gfx/palette.test.ts +35 -0
- package/src/engine/gfx/palette.ts +54 -0
- package/src/engine/gfx/png.test.ts +111 -0
- package/src/engine/gfx/png.ts +196 -0
- package/src/engine/gfx/render.test.ts +180 -0
- package/src/engine/gfx/render.ts +106 -0
- package/src/engine/index.ts +21 -0
- package/src/engine/pico8/adapter.ts +303 -0
- package/src/engine/pico8/drive.test.ts +415 -0
- package/src/engine/pico8/drive.ts +729 -0
- package/src/engine/pico8/harness.test.ts +181 -0
- package/src/engine/pico8/harness.ts +354 -0
- package/src/engine/pico8/index.ts +20 -0
- package/src/engine/pico8/sentinel.test.ts +80 -0
- package/src/engine/pico8/sentinel.ts +93 -0
- package/src/engine/pico8/session-daemon-entry.ts +12 -0
- package/src/engine/pico8/session-daemon-main.ts +98 -0
- package/src/engine/pico8/session-daemon.test.ts +212 -0
- package/src/engine/pico8/session-daemon.ts +306 -0
- package/src/engine/pico8/session.test.ts +239 -0
- package/src/engine/pico8/session.ts +321 -0
- package/src/engine/pico8/shell.test.ts +583 -0
- package/src/engine/pico8/shell.ts +412 -0
- package/src/engine/pico8/supervisor.test.ts +105 -0
- package/src/engine/pico8/supervisor.ts +197 -0
- package/src/engine/scaffold/index.ts +15 -0
- package/src/engine/scaffold/scaffold.ts +278 -0
- package/src/engine/shrinko/adapter.ts +241 -0
- package/src/engine/shrinko/fixtures/count-no-compressed.txt +2 -0
- package/src/engine/shrinko/fixtures/count-over-budget.txt +3 -0
- package/src/engine/shrinko/fixtures/count-under-budget.txt +3 -0
- package/src/engine/shrinko/index.ts +33 -0
- package/src/engine/shrinko/shell.ts +224 -0
- package/src/engine/shrinko/shrinko.test.ts +182 -0
- package/src/engine/skills/index.ts +14 -0
- package/src/engine/skills/skills.test.ts +376 -0
- package/src/engine/skills/skills.ts +205 -0
- package/src/index.ts +13 -0
- package/src/skills/game-design-reference/SKILL.md +70 -0
- package/src/skills/game-jam/SKILL.md +45 -0
- package/src/skills/picopilot-art/SKILL.md +62 -0
- package/src/skills/picopilot-audio/SKILL.md +194 -0
- package/src/skills/picopilot-code/SKILL.md +97 -0
- package/src/skills/picopilot-code/reference/README.md +42 -0
- package/src/skills/picopilot-code/reference/mode7-racing.md +95 -0
- package/src/skills/picopilot-code/reference/platformer.md +119 -0
- package/src/skills/picopilot-code/reference/puzzle-grid.md +106 -0
- package/src/skills/picopilot-code/reference/rpg-menus-dialog.md +122 -0
- package/src/skills/picopilot-code/reference/top-down-adventure.md +117 -0
- package/src/skills/picopilot-code/reference/twin-stick-arcade.md +128 -0
- package/src/skills/picopilot-debug/SKILL.md +195 -0
- package/src/skills/picopilot-overview/SKILL.md +52 -0
- package/src/version.ts +22 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: game-design-reference
|
|
3
|
+
description: Reference body of universal game-design principles (fairness, human reaction budget, readability, visibility, originality method). A shared reference other skills point at (game-jam, and any make-a-game skill); not fired on its own.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# game design reference
|
|
8
|
+
|
|
9
|
+
Universal principles of a GOOD game, true of any game regardless of engine, clock, or theme. This is a shared REFERENCE body: another skill (e.g. `game-jam`) points you here and you apply every principle below. It is engine-agnostic; where a check needs a concrete PICO-8 procedure (drive a cart, count frames), that lives in the loop skill `picopilot-debug` (playtest), which you follow the pointer into.
|
|
10
|
+
|
|
11
|
+
The one failure mode that unites the traps below: **an agent playtests as a frame-perfect machine and never models a human player.** You can hit any button on the exact right frame and see every pixel, so a game that is fair-and-playable TO YOU can be unfair or impossible for a human. Every check here is you deliberately putting on the human's eyes and hands.
|
|
12
|
+
|
|
13
|
+
## Fairness: never a dead state
|
|
14
|
+
|
|
15
|
+
A game is UNFAIR when it can reach a state the player did nothing wrong to reach, from which EVERY available action leads to a loss. A death the player could not have avoided reads as the GAME cheating, not the player failing, and it damages fairness far more than a hard-but-fair challenge does. Fairness is "the player can always still win, even after a setback": from every state, at least one legal move is non-losing.
|
|
16
|
+
|
|
17
|
+
This is distinct from DIFFICULTY. Hard is fine; a dead state punishes the player for the game's own design gap.
|
|
18
|
+
|
|
19
|
+
**The self-check (the one that gets skipped).** When a game has HAZARDS plus LIMITED control (a few discrete actions), verify BOTH properties, not just the first:
|
|
20
|
+
|
|
21
|
+
1. **Goal reachability** — from spawn/rest states, some sequence of actions reaches the objective. (Agents check this one.)
|
|
22
|
+
2. **Hazard avoidability** — from EVERY state the player can settle/rest into, at least ONE available action is non-fatal. (Agents SKIP this one.) If a resting position exists where all actions force a loss, that is a DEAD STATE: a fairness defect. Fix the geometry (widen an exit, move the hazard, add a safe lane) so no such pocket exists.
|
|
23
|
+
|
|
24
|
+
For a discrete-control game this is enumerable: drive the player into every corner/pocket it can settle in, then try EACH action and assert not-all-fatal. Do this with the playtest loop (`picopilot-debug`) deliberately, it will not happen by accident.
|
|
25
|
+
|
|
26
|
+
## Reaction budget: a human is not frame-perfect
|
|
27
|
+
|
|
28
|
+
Simple human reaction time is ~200-300ms (see a thing, press a button) and that is DETECTION only, before deciding what to do. At PICO-8's 30fps that is roughly **6-9 frames** just to react. So:
|
|
29
|
+
|
|
30
|
+
- **Any required-input WINDOW narrower than ~6-9 frames is effectively frame-perfect** and unplayable by a human as a reaction (only as a memorised pattern).
|
|
31
|
+
- **Do not tune difficulty against your own `--input` scripts.** If the ONLY way you can clear a level is a hand-timed frame sequence you authored, a human cannot clear it. That is the trap: you tuned against a frame-perfect player (yourself).
|
|
32
|
+
- **Telegraph, then require.** Fast games stay fair by relying on PREDICTION: give a readable wind-up so the player acts on anticipation, not on a sub-reaction-time window. Difficulty should come from planning/prediction pressure, not from beating the reaction-time floor.
|
|
33
|
+
|
|
34
|
+
**The self-check.** Before calling a level done, ask: could a HUMAN with ~250ms reaction clear this, relying on telegraphed anticipation rather than frame-perfect timing? If clearing it needs your exact scripted frames, widen the window or add telegraphing.
|
|
35
|
+
|
|
36
|
+
## Visibility: the player must see the player
|
|
37
|
+
|
|
38
|
+
If you `spr(n)` an entity, sprite n must actually be DRAWN (a non-empty sprite), or the entity is invisible and the game is unplayable however correct the logic. Prefer primitives (`circfill`, `rectfill`, `line`) for the player and key entities unless you have authored real sprites, primitives can never be an empty slot. The player, and every entity the player must track, must be visible and distinguishable on screen.
|
|
39
|
+
|
|
40
|
+
## Readability and feedback
|
|
41
|
+
|
|
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
|
+
|
|
44
|
+
## Difficulty curve and progression
|
|
45
|
+
|
|
46
|
+
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.
|
|
47
|
+
|
|
48
|
+
Progression can take MANY FORMS, a ladder from cheapest to richest:
|
|
49
|
+
|
|
50
|
+
1. **A difficulty ramp within one loop** (speed / spawn rate / hazard density rises as you play) — the smallest way to "go somewhere".
|
|
51
|
+
2. **Escalating waves / rounds** (new hazard TYPES arrive in later waves, not just more) — deepens one screen without new scenes.
|
|
52
|
+
3. **Discrete levels / stages** (hand-built rooms, each teaching or combining a mechanic) — a real authored arc.
|
|
53
|
+
4. **Worlds / acts** (sustained progression across many levels, new mechanics unlocking) — the top of the ladder.
|
|
54
|
+
|
|
55
|
+
Each rung "goes somewhere" more than the one below; each also costs more to build and finish. WHICH rung a given game should reach is not universal, it depends on how much you can build and FINISH (a rough over-reach loses to a finished smaller arc). Pick the highest rung you can complete well.
|
|
56
|
+
|
|
57
|
+
## Originality: a method, not a menu
|
|
58
|
+
|
|
59
|
+
Novelty is a real design quality and it is the axis judges cap hardest. But it cannot be handed to you as a list of mechanics, if it were, every game would converge on the same listed idea and it would be the list-writer's creativity, not yours. So this section teaches a METHOD to GENERATE and then TEST your own idea. It deliberately lists NO example mechanics. (If you are editing this skill: do not add concrete example mechanics here. A listed lever becomes the answer every agent copies, run after run, the exact trap this framing exists to avoid.)
|
|
60
|
+
|
|
61
|
+
**Interrogate your idea (questions to ask about YOUR idea, not answers to copy):**
|
|
62
|
+
|
|
63
|
+
- What is the OBVIOUS version of this theme, the first thing anyone would build? (Name it, so you can avoid it.)
|
|
64
|
+
- Could you SUBVERT the expectation the theme sets, rather than meet it?
|
|
65
|
+
- Could you INVERT the role, act on the world instead of the avatar, or control the thing usually controlled against you?
|
|
66
|
+
- Could the CONSTRAINT itself become the mechanic, rather than a limit you work around?
|
|
67
|
+
- Could two unrelated ideas COMBINE into something neither is alone?
|
|
68
|
+
- Could you RECONTEXTUALISE a familiar mechanic into a setting that changes its meaning?
|
|
69
|
+
|
|
70
|
+
**The self-check (load-bearing).** Before you commit to an idea: name the obvious version of this theme; is your idea just that with a reskin? If a judge (or player) has almost certainly seen this exact game before, go again. Commit only to an idea that passes this check, then build it well, a fresh idea executed roughly beats a stale idea executed perfectly.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: game-jam
|
|
3
|
+
description: Build a complete, original, playable PICO-8 game against a deadline and a theme. Use for a game jam, a timed game-build, or a jam benchmark run. Carries the clock discipline; applies the universal design principles via game-design-reference.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# game jam
|
|
7
|
+
|
|
8
|
+
A jam is building a GOOD game (the universal part) UNDER A CLOCK and to a THEME (the jam part). This skill carries only the jam-specific part; the design principles live in `game-design-reference`.
|
|
9
|
+
|
|
10
|
+
**FIRST STEP, before anything else: use the `read` tool to LOAD AND FOLLOW `game-design-reference` (`../game-design-reference/SKILL.md`, resolved against THIS skill's directory).** It is the shared body of universal design principles (fairness / no dead state, human reaction budget, visibility, readability, difficulty curve, and the originality method) that you apply throughout the jam. This skill does not repeat them; it tells you the calls they force when you are racing a clock, so you need the reference open to make those calls. Do not skip this read: the fairness and reaction-budget self-checks live there, and they are exactly what a jam entry gets wrong. Then use the picopilot loop skills as you go: `picopilot-overview` (the `#include` layout + verify loop), `picopilot-code` (the token budget), `picopilot-art` / `picopilot-audio` (assets), and `picopilot-debug` (run + playtest, your eyes on the running game).
|
|
11
|
+
|
|
12
|
+
## Interpret the theme yourself
|
|
13
|
+
|
|
14
|
+
The theme is a seed, not a brief. YOU interpret it into a game. Run `game-design-reference`'s originality method and self-check on the bare theme and commit to your OWN idea, do not wait to be told what to build, and do not settle for the obvious version. This is where the jam is won or lost: a fresh, well-chosen idea sets your ceiling.
|
|
15
|
+
|
|
16
|
+
## The clock: slice, deepen, triage
|
|
17
|
+
|
|
18
|
+
Three phases you map onto whatever time budget you are given, not a rigid schedule:
|
|
19
|
+
|
|
20
|
+
- **SLICE (first third).** Get to a PLAYABLE slice fast. A playable slice = it boots, responds to input (the button actually changes what happens), and has a goal with a win/lose, and `verify` is green. Scope tiny: one screen, one mechanic, one goal. A rough playable game scores; a broken ambitious one scores zero.
|
|
21
|
+
- **DEEPEN (middle).** Once the slice works, spend the BULK of your remaining time making the game DEEPER (go somewhere), not just more finished. Reach UP the progression ladder (`game-design-reference`: difficulty ramp -> escalating waves -> levels/stages -> worlds) as far as your budget can FINISH. Polish (juice, audio, extra feedback) is worth some time but is NOT the main use of a long budget: a short game buffed to a shine still under-uses the clock. Deepening and polishing both stay gated behind a green `verify`, playable at every checkpoint.
|
|
22
|
+
- **TRIAGE (last stretch).** Stop adding features. Confirm it boots and is playable, finalise the writeup. But "stop adding features" is NOT "stop improving": if you have found a real design DEFECT (below), fixing it beats holding, and beats gold-plating. Do not idle a proven build if it still has a fairness or playability flaw.
|
|
23
|
+
|
|
24
|
+
## Scale your ambition to the clock
|
|
25
|
+
|
|
26
|
+
The RIGHT size of game depends on the time you are given, and you know your budget from the brief. Match your ambition to it:
|
|
27
|
+
|
|
28
|
+
- **Short clock** (minutes): one screen, one mechanic; deepen via a difficulty ramp or a few escalating waves. Levels would over-reach and ship broken. Getting a tight, finished, fair slice IS the win.
|
|
29
|
+
- **Long clock** (an hour-plus, a day, more): a playable slice in the first third is just the beginning. Then reach real progression, hand-built levels, unlocking mechanics, an authored difficulty arc, a bigger, deeper game. Idling a proven short game or only polishing it WASTES the budget; more time should buy more GAME.
|
|
30
|
+
|
|
31
|
+
Never over-reach past what you can FINISH: a rough, unfinished big game loses to a finished smaller arc. Pick the highest rung of the ladder you can complete well, and let the clock, not a fixed template, decide which rung that is. YOU choose the form (ramp / waves / levels / worlds) that fits your idea and your time.
|
|
32
|
+
|
|
33
|
+
## The design calls the clock forces
|
|
34
|
+
|
|
35
|
+
When you find one of these under time pressure, this is the jam-time decision (the principle is in `game-design-reference`):
|
|
36
|
+
|
|
37
|
+
- **A dead state (unfair forced-loss).** You found a spot where the player has no non-losing move. FIX IT even late: a dead state tanks the playability/fairness a judge and a player feel far more than a missing feature costs. This outranks polish.
|
|
38
|
+
- **A superhuman level (frame-perfect tuning).** You tuned a level against your own frame-perfect `--input` scripts and only YOU can clear it. Budget a reaction margin (widen the window, add telegraphing) before you call it done. If a human with ~250ms reaction cannot clear it, it is not done, it is broken.
|
|
39
|
+
- **An invisible entity.** You `spr()` something that is an empty slot, or the player cannot tell player from hazard from goal. Fix visibility/readability before anything else; an unreadable game is unplayable.
|
|
40
|
+
|
|
41
|
+
Do not declare "done, holding" while any of these is unfixed. The plateau trap is stopping because you cannot SEE what to improve, run the `game-design-reference` self-checks (fairness: test hazard-avoidability from every rest state, not just reachability; reaction: could a human clear this) precisely to find the work that remains.
|
|
42
|
+
|
|
43
|
+
## Finish
|
|
44
|
+
|
|
45
|
+
At the deadline you ship: a playable `main.p8` (boots, responds, has win/lose, `verify` green) and a short `JAM.md` (theme interpretation, the mechanic, controls, and the honest calls you made under the clock). A rough playable game with a fresh idea beats a broken ambitious one, every time.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: picopilot-art
|
|
3
|
+
description: Edit and JUDGE PICO-8 sprites with picopilot. The char-grid edit surface, the rendered-PNG judge surface, the render to look, set to fix, render again loop, and the non-multimodal fallback for models that cannot read images. Also the gfx/map overlap smart-refuse. Use when editing __gfx__ sprites.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# picopilot art
|
|
7
|
+
|
|
8
|
+
Sprites live in the cart as hex blobs you cannot read. picopilot gives you two
|
|
9
|
+
distinct surfaces so you can both EDIT precisely and JUDGE honestly. (See
|
|
10
|
+
`picopilot-overview` for the `#include` discipline; never hand-edit `__gfx__`.)
|
|
11
|
+
|
|
12
|
+
## Two surfaces: the char grid (edit) and the PNG (judge)
|
|
13
|
+
|
|
14
|
+
- **Char grid** (`picopilot gfx show <sprite>` / `gfx set <sprite>`): a per-pixel
|
|
15
|
+
hex grid, `.` = transparent, `0-F` = the 16 fixed PICO-8 colours. This maps 1:1
|
|
16
|
+
to `__gfx__` and is your EXACT, round-trippable EDIT surface. It is the
|
|
17
|
+
SVG-for-pixels source you change.
|
|
18
|
+
- **Rendered PNG** (`picopilot gfx render <sprite|sheet>`): an upscaled,
|
|
19
|
+
true-16-colour PNG written to a known path. This is the JUDGE surface: it is
|
|
20
|
+
how you tell whether the sprite READS as intended.
|
|
21
|
+
|
|
22
|
+
You need both, because coding models generate structured visual source (a grid,
|
|
23
|
+
like SVG) well but read it back poorly for gestalt and colour. Edit on the grid;
|
|
24
|
+
judge on the PNG.
|
|
25
|
+
|
|
26
|
+
## The art loop: render → look → set → render
|
|
27
|
+
|
|
28
|
+
1. `picopilot gfx render <sprite>`: produce the PNG (and get the grid).
|
|
29
|
+
2. LOOK at it, then
|
|
30
|
+
3. `picopilot gfx set <sprite>`: fix pixels on the char grid.
|
|
31
|
+
4. `picopilot gfx render <sprite>`: re-render and look again.
|
|
32
|
+
|
|
33
|
+
These steps are wired as CTAs, so follow the suggested next command.
|
|
34
|
+
|
|
35
|
+
## The non-multimodal fallback (load-bearing)
|
|
36
|
+
|
|
37
|
+
The tool ALWAYS emits both the PNG path AND the char grid, because whether you
|
|
38
|
+
can read an image is YOUR capability, not something picopilot can detect.
|
|
39
|
+
|
|
40
|
+
- If you CAN read images: open the `gfx render` PNG and judge the pixels there.
|
|
41
|
+
- If you CANNOT read images: do NOT pretend to. Fall back to reasoning over the
|
|
42
|
+
`gfx show` grid, read the hex nibbles row by row, reconstruct the shape and
|
|
43
|
+
colours in your head, and judge from that. The eyes-loop degrades honestly to
|
|
44
|
+
the grid rather than to guessing.
|
|
45
|
+
|
|
46
|
+
## gfx/map overlap: the smart-refuse
|
|
47
|
+
|
|
48
|
+
Sprites 128..255 (`0x1000`-`0x1fff`) ALIAS `__map__` rows 32..63, the SAME
|
|
49
|
+
memory. Writing such a sprite can clobber real map tiles you cannot see.
|
|
50
|
+
|
|
51
|
+
`picopilot gfx set` inspects the overlapping map region:
|
|
52
|
+
|
|
53
|
+
- overlap empty/all-zero, or overlap authorised: the write succeeds (the result
|
|
54
|
+
notes the aliasing).
|
|
55
|
+
- real map tiles at risk AND nothing authorised the loss: it REFUSES with a
|
|
56
|
+
structured `map-overlap` result and a nonzero exit, and leaves `__map__`
|
|
57
|
+
UNCHANGED. This is not a warning you may ignore; it is a stop.
|
|
58
|
+
|
|
59
|
+
To authorise overlap deliberately, pass `--allow-map-overlap` on the command or
|
|
60
|
+
set `allowMapOverlap: true` in `picopilot.json`. Or just use sprites 0..127,
|
|
61
|
+
which are gfx-only. The one guaranteed invariant: picopilot never SILENTLY
|
|
62
|
+
overwrites existing map tiles.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: picopilot-audio
|
|
3
|
+
description: Compose PICO-8 sound as text with picopilot. The #1 reframe (no tempo, compose in TRACKER ROWS at one speed), the picopilot-MML grammar (waveform/volume/effect per note + the 5 SFX filters for designed sounds like explosion/engine/pad), structural music assembly from SFX references, the loud refusals, and the compose to hear ears loop. Use when authoring __sfx__ / __music__.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# picopilot audio
|
|
7
|
+
|
|
8
|
+
SFX and music live in the cart as hex blobs you cannot read. picopilot lets you
|
|
9
|
+
compose sound as TEXT you can reason about, then transpiles it into `__sfx__` /
|
|
10
|
+
`__music__`. (See `picopilot-overview` for the `#include` discipline; never
|
|
11
|
+
hand-write the audio hex.) The exact byte layout + the full grammar/mapping
|
|
12
|
+
tables are the canonical reference in
|
|
13
|
+
`work/notes/findings/pico8-audio-sfx-music-layout-and-mml-subset.md` (Part A =
|
|
14
|
+
layout, Part B = the picopilot-MML grammar + mapping). This skill carries what a
|
|
15
|
+
model gets WRONG, not the whole finding.
|
|
16
|
+
|
|
17
|
+
## Read this first: no tempo, compose in TRACKER ROWS (ADR-0008)
|
|
18
|
+
|
|
19
|
+
The one thing a model gets wrong: it reaches for sheet music. PICO-8 has NO
|
|
20
|
+
per-note duration and NO tempo/BPM. An SFX is a column of up to 32 tracker ROWS,
|
|
21
|
+
and ALL rows share ONE SFX `speed` (ticks/row, set with `s<N>`). So a
|
|
22
|
+
picopilot-MML length is a ROW COUNT, not a musical note value: `c4` = the note C
|
|
23
|
+
held for 4 ROWS (emitted as the note plus 3 tie rows), NOT a quarter note. There
|
|
24
|
+
is no tempo token; a tempo-style input is refused with a clear message, not
|
|
25
|
+
guessed at. Consequences you must design around:
|
|
26
|
+
|
|
27
|
+
- **No true sustain across rows.** A held note is RE-STRUCK each following row
|
|
28
|
+
(PICO-8 has no cross-row sustain bit). For a smooth hold use effect `e1`
|
|
29
|
+
(slide) or a low `s<N>` speed, not a long length. Do not expect a legato tie.
|
|
30
|
+
- **Dotted notes round to whole rows.** `c4.` = 6 rows; a fractional result
|
|
31
|
+
rounds to the nearest whole row and the result WARNS. Rows are integers.
|
|
32
|
+
- **32-row hard cap.** A melody past 32 rows is refused (see below). Splitting it
|
|
33
|
+
into several SFX arranged by a music pattern is a STRUCTURAL choice YOU make,
|
|
34
|
+
not something the transpiler does silently.
|
|
35
|
+
|
|
36
|
+
## The ears loop: `sfx from-mml` -> `music from-patterns` -> `audio render`
|
|
37
|
+
|
|
38
|
+
This is the audio analogue of the `picopilot-art` render/look/set eyes loop:
|
|
39
|
+
|
|
40
|
+
1. `picopilot sfx from-mml <slot> "<mml>"`: compose one sound as text.
|
|
41
|
+
2. `picopilot music from-patterns "<json>"`: arrange SFX into a song.
|
|
42
|
+
3. `picopilot audio render`: RECORD a WAV and HEAR it, then iterate.
|
|
43
|
+
|
|
44
|
+
These steps are wired as CTAs (a merged SFX or song points you at `verify` then
|
|
45
|
+
`audio render`), so follow the suggested next command.
|
|
46
|
+
|
|
47
|
+
## `audio render` / `audio record`: get a WAV by RECORDING (ADR-0009)
|
|
48
|
+
|
|
49
|
+
PICO-8 has NO working offline WAV export (it SIGFPEs upstream), so picopilot gets
|
|
50
|
+
audio-to-WAV the only way that works: by RECORDING a running session
|
|
51
|
+
(`extcmd("audio_rec")`/`audio_end`). Two commands, both PICO-8-gated and both
|
|
52
|
+
REAL-TIME recordings that need a real audio+video session (a developer machine,
|
|
53
|
+
NOT headless CI), NOT deterministic offline exports:
|
|
54
|
+
|
|
55
|
+
- **`picopilot audio render`** injects a THROWAWAY play-harness for a chosen
|
|
56
|
+
target and records it, so you write NO playback code. Pick the target with
|
|
57
|
+
`--sfx <n>` (one SFX slot), `--pattern <p>` (one music pattern), or NEITHER
|
|
58
|
+
(the whole song, `music 0`). The record window is derived from an `--sfx`
|
|
59
|
+
target's speed+rows; override any target with `--seconds <n>`. An empty slot or
|
|
60
|
+
out-of-range target is REFUSED loudly (nothing to render), never a silent
|
|
61
|
+
recording of silence.
|
|
62
|
+
- **`picopilot audio record <cart>`** records the cart's OWN playback (it keeps
|
|
63
|
+
the cart's code and appends a cooperative recorder); use it to capture a whole
|
|
64
|
+
run's audio. `picopilot run --record-audio` does the same alongside a normal
|
|
65
|
+
`run` (a WAV next to the screenshots).
|
|
66
|
+
|
|
67
|
+
The WAV goes to an isolated dir (`--wav-dir`, default a temp dir), never
|
|
68
|
+
`~/Desktop` or the carts root (the `audio_end(1)`-saves-to-the-current-folder
|
|
69
|
+
trap; picopilot steers it with PICO-8's `root_path`). PICO-8 absent -> a
|
|
70
|
+
structured `pico8-not-found` (remedy `set PICO8_PATH or install PICO-8`), never a
|
|
71
|
+
crash. On a headless machine the recording succeeds but captures NO audio
|
|
72
|
+
(`captured:false` + a nudge to run on a machine with a display + audio device):
|
|
73
|
+
that is honest, not a failure of the recipe.
|
|
74
|
+
|
|
75
|
+
## `sfx from-mml`: one MML string -> one SFX slot
|
|
76
|
+
|
|
77
|
+
`picopilot sfx from-mml <slot> "<mml>"` transpiles picopilot-MML into one
|
|
78
|
+
`__sfx__` slot (`0-63`) and merges it (only that slot changes; every other
|
|
79
|
+
section stays byte-identical). Cart is `--cart` (default `main.p8`); pass a
|
|
80
|
+
longer source with `--file` (give EITHER the argument OR `--file`).
|
|
81
|
+
|
|
82
|
+
Write notes AND their waveform/volume/effect in MML: that is exactly the
|
|
83
|
+
information PICO-8 needs and hand-written hex hides. This is why picopilot-MML,
|
|
84
|
+
not ABC: ABC is score-level and cannot name a per-note waveform, volume, or
|
|
85
|
+
effect, so an ABC-authored SFX is timbre-less ("blind audio in a nicer font").
|
|
86
|
+
|
|
87
|
+
### The picopilot-MML grammar in brief
|
|
88
|
+
|
|
89
|
+
Modal state (sticky until changed): `@<0-7>` waveform (`@8`-`@f` = SFX 0-7 as a
|
|
90
|
+
custom instrument), `v<0-7>` volume, `e<0-7>` effect (0 none, 1 slide, 2 vibrato,
|
|
91
|
+
3 drop, 4 fade in, 5 fade out, 6 arpeggio fast, 7 arpeggio slow), `o<0-5>` octave
|
|
92
|
+
(default `o2`), `l<N>` default length in rows, `>`/`<` octave shift. A bare note
|
|
93
|
+
(`c d e f g a b`, `+`/`#` sharp, `-` flat) emits one row in the current state.
|
|
94
|
+
`r<N>` = N silent rows; `^<N>` extends the previous note; `s<N>` = SFX speed
|
|
95
|
+
(default `s16`); `{`/`}` = loop markers (a LONE `{` marks the pattern LEN, the
|
|
96
|
+
finding's LEN special case). SFX-level FILTERS (per-SFX, order-free): `!dampen`/
|
|
97
|
+
`!dampen2`, `!reverb`, `!noiz`, `!buzz`, `!detune1`/`!detune2` (see the filters
|
|
98
|
+
section below). Whitespace + `|` bars are cosmetic. See finding Part B / A.7 for
|
|
99
|
+
the full tables.
|
|
100
|
+
|
|
101
|
+
Note `e` is BOTH a note letter AND the effect prefix: `e0`-`e7` is the EFFECT; a
|
|
102
|
+
bare `e` is note E (hold it with `l4 e`, not `e4`). Effects have NO letter
|
|
103
|
+
mnemonics (the finding's `sl`/`vb`/... sugar did NOT ship: each collides with a
|
|
104
|
+
real note-plus-length sequence); use the numeric `e0`-`e7`.
|
|
105
|
+
|
|
106
|
+
Example: `picopilot sfx from-mml 0 "s8 @3 v6 e2 c e g > c"` is a square-wave
|
|
107
|
+
(`@3`) vibrato (`e2`) arpeggio at speed 8.
|
|
108
|
+
|
|
109
|
+
### SFX FILTERS: how to reach DESIGNED sounds (explosion, engine, pad)
|
|
110
|
+
|
|
111
|
+
Pitch + waveform + volume + effect get you notes; the 5 per-SFX FILTERS are what
|
|
112
|
+
make a sound DESIGNED. They are `!`-prefixed SFX-level directives (per-SFX like
|
|
113
|
+
`s<N>`, place them anywhere in the string, order-free): they set the SFX's first
|
|
114
|
+
header byte and change nothing per-note. Use them when a boom reads as a
|
|
115
|
+
machine-gun rattle, or an engine as a buzz: no amount of pitch/volume MML can
|
|
116
|
+
synthesize the low BODY or resonant TAIL, only these filters can.
|
|
117
|
+
|
|
118
|
+
- **`!dampen` / `!dampen2`** — a low-pass at 2 levels. This is the BODY/weight:
|
|
119
|
+
turns raw `@6` noise from a hiss/rattle into a "boom". `!dampen2` is heavier.
|
|
120
|
+
The single biggest lever for an explosion.
|
|
121
|
+
- **`!reverb`** — an echo/tail. The resonant TAIL that makes a boom ring out (and
|
|
122
|
+
a pad bloom). On/off (a single switch, see the caveat below).
|
|
123
|
+
- **`!noiz`** — pure white-noise texture; applies ONLY to instrument 6 (`@6`), a
|
|
124
|
+
no-op on any other waveform. The noise character under an explosion.
|
|
125
|
+
- **`!buzz`** — a buzzier alteration (all waveforms). With `@6` + `!buzz` and NOIZ
|
|
126
|
+
off you get brown noise.
|
|
127
|
+
- **`!detune1`** (flange, second detuned voice) / **`!detune2`** (a voice tuned up
|
|
128
|
+
~an octave) — thickness for a lead or pad. `!detune` needs an explicit 1 or 2.
|
|
129
|
+
|
|
130
|
+
The canonical DESIGNED explosion: `!dampen2 !reverb @6` over a descending pitch
|
|
131
|
+
sweep + volume decay, e.g.
|
|
132
|
+
`picopilot sfx from-mml 3 "s6 !dampen2 !reverb @6 o3 v7 g o1 c o0 g v6 e v5 c v4 g v3 c v2 g v1 c"`.
|
|
133
|
+
Strip the two filters and the SAME notes read as a descending zap / rattle: the
|
|
134
|
+
filters are the tool gap the audio dogfood surfaced (they set header byte `e0`).
|
|
135
|
+
|
|
136
|
+
CAVEAT (finding A.7): the filter byte layout was decoded byte-for-byte on PICO-8
|
|
137
|
+
v0.2.7, but the byte has no non-colliding room for a REVERB level 2 under the
|
|
138
|
+
verified layout, so picopilot exposes `!reverb` as a single on/off switch (DAMPEN
|
|
139
|
+
keeps its 2 levels). A bad level (`!dampen3`, `!noiz2`, `!reverb2`, `!detune`
|
|
140
|
+
without 1/2) is a loud `audio-mml-filter-level-out-of-range` refusal, not a clamp.
|
|
141
|
+
|
|
142
|
+
## `music from-patterns`: structural, NOT a notation
|
|
143
|
+
|
|
144
|
+
`__music__` holds NO melody: melody lives entirely in the referenced SFX. A song
|
|
145
|
+
is an ORDERED LIST of up-to-4 SFX-channel references per pattern.
|
|
146
|
+
`picopilot music from-patterns "<json>"` writes `__music__` (only `__music__`
|
|
147
|
+
changes; `__sfx__` and every other section stay byte-identical). So author each
|
|
148
|
+
voice as an SFX with `sfx from-mml`, THEN arrange the references here; you never
|
|
149
|
+
write music in a melodic notation. Cart is `--cart` (default `main.p8`); longer
|
|
150
|
+
songs via `--file`.
|
|
151
|
+
|
|
152
|
+
### The pattern-list JSON
|
|
153
|
+
|
|
154
|
+
An array of patterns, each
|
|
155
|
+
`{"channels":[c0,c1,c2,c3], "loopStart"?, "loopBack"?, "stop"?}`. Pattern order =
|
|
156
|
+
song order. A channel is an SFX index `0-63`, OR `null` = OFF (silent this
|
|
157
|
+
pattern). **`null` is NOT `sfx 0`**: `null` sets the channel's off bit; `0` is a
|
|
158
|
+
real, playing SFX. Keep them distinct. The 3 flow flags (optional, default
|
|
159
|
+
false): `loopStart`, `loopBack` (jump back to the loop-start pattern at this
|
|
160
|
+
pattern's end), `stop` (music stops after this pattern); they combine. A
|
|
161
|
+
`loopBack` with NO `loopStart` anywhere is allowed but WARNS (PICO-8 falls back
|
|
162
|
+
to pattern 0).
|
|
163
|
+
|
|
164
|
+
Example: `[{"channels":[0,1,null,3],"loopStart":true},{"channels":[0,1,null,3],"loopBack":true}]`
|
|
165
|
+
is a 2-pattern loop whose channel 2 is silent.
|
|
166
|
+
|
|
167
|
+
**Pattern length is INHERITED from the SFX, not stored here.** PICO-8 ends a
|
|
168
|
+
pattern when its LEFT-MOST non-looping channel's SFX finishes, so pattern timing
|
|
169
|
+
comes from the referenced SFX (its `speed` + LEN). To change how long a pattern
|
|
170
|
+
plays, change the referenced SFX, not the music.
|
|
171
|
+
|
|
172
|
+
## The two refusals: loud, never clamped
|
|
173
|
+
|
|
174
|
+
Both stop you with a structured error + nonzero exit (no bytes change on a
|
|
175
|
+
refusal) so a refusal is ACTIONABLE, not a dead end:
|
|
176
|
+
|
|
177
|
+
- **`audio-mml-pitch-out-of-range`** (`sfx from-mml`): a note fell below C0 or
|
|
178
|
+
above D#5 (`0-63`). PICO-8 has no notes outside that range. Fix: TRANSPOSE, pick
|
|
179
|
+
an octave (`o0`-`o5`) that keeps every note in range, rather than transposing
|
|
180
|
+
off the keyboard.
|
|
181
|
+
- **`audio-mml-sfx-overflow`** (`sfx from-mml`): more than 32 rows. Fix: SPLIT the
|
|
182
|
+
melody across multiple SFX slots yourself, then arrange them with
|
|
183
|
+
`music from-patterns`.
|
|
184
|
+
- **`audio-mml-filter-level-out-of-range`** (`sfx from-mml`): a filter level PICO-8
|
|
185
|
+
does not have (`!dampen3`, `!reverb2`, `!noiz2`, or `!detune` without a mode).
|
|
186
|
+
Fix: `!dampen` has levels 1-2 (`!dampen`/`!dampen2`); `!reverb`/`!noiz`/`!buzz`
|
|
187
|
+
are on/off; `!detune` needs `!detune1` (flange) or `!detune2` (octave).
|
|
188
|
+
|
|
189
|
+
Related: `music from-patterns` refuses an out-of-range SFX index with
|
|
190
|
+
`audio-music-sfx-out-of-range` (to silence a channel use `null`, not an
|
|
191
|
+
out-of-range number); a malformed JSON payload is `audio-music-parse-error`. And
|
|
192
|
+
`audio render`/`audio record` require PICO-8, so they return a structured
|
|
193
|
+
`pico8-not-found` (remedy `set PICO8_PATH or install PICO-8`) when absent, never
|
|
194
|
+
a crash. The text authoring + transpile steps need neither PICO-8 nor shrinko.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: picopilot-code
|
|
3
|
+
description: Write low-token PICO-8 Lua and stay under the 8,192-token budget. How to read picopilot tokens output, the PICO-8 shorthands that reclaim budget, and how minify (safe-only) and verify fit the loop. Use when writing or shrinking cart Lua.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# picopilot code
|
|
7
|
+
|
|
8
|
+
The #1 way an agent fails at PICO-8 is token bloat: verbose Lua silently blows
|
|
9
|
+
past the 8,192-token budget and the cart will not load. picopilot's job here is
|
|
10
|
+
to make the budget visible and cheap to fix. (See `picopilot-overview` for the
|
|
11
|
+
`#include` discipline: you edit `main.lua`, never the `.p8` binary sections.)
|
|
12
|
+
|
|
13
|
+
## Read the token breakdown every iteration
|
|
14
|
+
|
|
15
|
+
Run `picopilot tokens` after each change. It reports:
|
|
16
|
+
|
|
17
|
+
- `tokens` and `pct`: the count and its percentage of the 8,192 budget.
|
|
18
|
+
- `overBudget`: true when you are over 8,192 (the cart will not load).
|
|
19
|
+
- `chars` and `compressed`: size, not the load-bearing limit; tokens are.
|
|
20
|
+
|
|
21
|
+
When you are over budget, the result CTAs you to `picopilot minify`. Treat the
|
|
22
|
+
token count as the number to drive down, not something to check once.
|
|
23
|
+
|
|
24
|
+
If shrinko is not installed, `tokens` returns a structured `shrinko-not-found`
|
|
25
|
+
result (nonzero exit) with the exact remedy `uv pip install shrinko`. That is not
|
|
26
|
+
a crash; install shrinko and re-run.
|
|
27
|
+
|
|
28
|
+
## Token discipline (write cheap Lua the first time)
|
|
29
|
+
|
|
30
|
+
Models blow the budget by writing verbose Lua. Prefer PICO-8 shorthands:
|
|
31
|
+
|
|
32
|
+
- `?` for `print`.
|
|
33
|
+
- inline if: `if(c) x=1` instead of `if c then x=1 end`.
|
|
34
|
+
- compound assignment: `+=`, `-=`, `*=`, `/=`.
|
|
35
|
+
- `\` for integer divide.
|
|
36
|
+
- reuse locals; avoid needless globals; fold repeated expressions into one local.
|
|
37
|
+
|
|
38
|
+
## Reclaim budget with minify (safe-only)
|
|
39
|
+
|
|
40
|
+
`picopilot minify` runs SAFE minification by default and reports the before/after
|
|
41
|
+
token delta. Safe-only never changes behaviour, so it is the first move when over
|
|
42
|
+
budget. Aggressive minification is never silent; if it is ever offered it is an
|
|
43
|
+
explicit opt-in.
|
|
44
|
+
|
|
45
|
+
## Gate it
|
|
46
|
+
|
|
47
|
+
`picopilot verify` is the single static acceptance gate: tokens + integrity, no
|
|
48
|
+
run. A green verify is well-formed, not proven-to-run, so it points you at
|
|
49
|
+
`picopilot run`. With shrinko absent, verify is `gate-incapable` (never green),
|
|
50
|
+
because it cannot check token bloat, the failure this loop exists to catch.
|
|
51
|
+
|
|
52
|
+
## PICO-8 gotchas that break carts silently
|
|
53
|
+
|
|
54
|
+
These are NOT token issues; they are behaviours that differ from mainstream Lua
|
|
55
|
+
/ math and make a cart compile-and-run-but-behave-wrong. The ones models get
|
|
56
|
+
wrong most:
|
|
57
|
+
|
|
58
|
+
- `sin(t)`/`cos(t)` take TURNS (0..1), not radians, and `sin` is INVERTED for
|
|
59
|
+
screen space (+y is down). A full circle is `t: 0->1`. Do not multiply by
|
|
60
|
+
`2*pi`; do not re-negate `sin`.
|
|
61
|
+
- `/` does NOT truncate (`5/2` is `2.5`). Integer divide is `\` (`5\2` is `2`),
|
|
62
|
+
which floors toward negative infinity (`-5\2` is `-3`).
|
|
63
|
+
- `rnd(x)` returns a FLOAT in `[0,x)`. Wrap with `flr` for an integer
|
|
64
|
+
(`flr(rnd(6))` is 0..5). `rnd(table)` returns a random element.
|
|
65
|
+
- Tables are 1-BASED. `del(t,val)` removes by VALUE; `deli(t,i)` removes by
|
|
66
|
+
INDEX. Deleting the current item inside `foreach` is safe; deleting inside an
|
|
67
|
+
ascending `for i=1,#t` loop ERRORS once the list shrinks (iterate descending).
|
|
68
|
+
- Draw state PERSISTS across frames: `camera`, `pal`, `palt`, `clip`, `color`,
|
|
69
|
+
`fillp` stay set until you reset them (no-arg form: `camera()`, `pal()`).
|
|
70
|
+
Reset before drawing the HUD or offsets/palette swaps leak.
|
|
71
|
+
- Variables are GLOBAL unless declared `local`. The `x = cond and a or b` ternary
|
|
72
|
+
returns `b` when `a` is falsy; use an explicit `if` when `a` can be `false`/`nil`.
|
|
73
|
+
|
|
74
|
+
## Data-as-split-strings (a top token-saver)
|
|
75
|
+
|
|
76
|
+
`split"a,b,c"` turns a comma string into a 1-based array in one cheap token.
|
|
77
|
+
Declare frames, hitboxes, direction lookups, and whole level rows this way
|
|
78
|
+
instead of table literals. `unpack(split"...")` spreads it as a vararg (e.g.
|
|
79
|
+
`poke(addr, unpack(split"..."))` writes a byte blob in one call). Prefer it for
|
|
80
|
+
any homogeneous list.
|
|
81
|
+
|
|
82
|
+
## Genre code references (load the one you need)
|
|
83
|
+
|
|
84
|
+
The right STRUCTURE differs by game type: a platformer's gravity + per-axis
|
|
85
|
+
collision is nothing like a grid puzzle's transactional tile-step or a shooter's
|
|
86
|
+
entity swarm. When you start building a specific genre, load the matching file
|
|
87
|
+
from this skill's `reference/` folder (see `reference/README.md` for the index):
|
|
88
|
+
|
|
89
|
+
- `reference/platformer.md`: gravity/jump, per-axis tile collision, camera follow.
|
|
90
|
+
- `reference/puzzle-grid.md`: grid state, discrete tile-step moves, rule passes.
|
|
91
|
+
- `reference/twin-stick-arcade.md`: many entities, spawn/despawn at scale, juice.
|
|
92
|
+
- `reference/top-down-adventure.md`: tiled world, 4-dir movement, tile interaction.
|
|
93
|
+
- `reference/mode7-racing.md`: pseudo-3D `tline` floor projection, driving.
|
|
94
|
+
- `reference/rpg-menus-dialog.md`: UI state stack, cursor menus, timed text reveal.
|
|
95
|
+
|
|
96
|
+
Each carries a minimal idiomatic implementation and a genre-pitfalls checklist.
|
|
97
|
+
Adapt them to your game.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# PICO-8 game-type code references
|
|
2
|
+
|
|
3
|
+
Per-genre reference implementations for PICO-8 carts. The right structure for a
|
|
4
|
+
platformer is not the right structure for a puzzle game, so pick the file that
|
|
5
|
+
matches what you are building and load it on demand. These are minimal
|
|
6
|
+
implementations written in idiomatic, token-cheap PICO-8 Lua (see the
|
|
7
|
+
`picopilot-code` SKILL for the token discipline they follow). They teach the
|
|
8
|
+
STRUCTURE and the genre-specific pitfalls; adapt them to your game.
|
|
9
|
+
|
|
10
|
+
Load the ONE that matches your game:
|
|
11
|
+
|
|
12
|
+
- `platformer.md`: side-view gravity/jump, per-axis tile collision, camera
|
|
13
|
+
follow.
|
|
14
|
+
- `puzzle-grid.md`: a grid of cells, discrete tile-by-tile movement, rule
|
|
15
|
+
evaluation, no physics.
|
|
16
|
+
- `twin-stick-arcade.md`: many entities at once (bullets, enemies, particles),
|
|
17
|
+
spawn/despawn at scale, screen juice.
|
|
18
|
+
- `top-down-adventure.md`: walk a tiled world, room/map streaming, 4-direction
|
|
19
|
+
movement and facing, interact with tiles.
|
|
20
|
+
- `mode7-racing.md`: pseudo-3D projected floor via `tline`, per-scanline
|
|
21
|
+
perspective, heading-based driving.
|
|
22
|
+
- `rpg-menus-dialog.md`: UI state STACK, cursor menus, timed text reveal,
|
|
23
|
+
turn-based battle menus.
|
|
24
|
+
|
|
25
|
+
Cross-cutting truths that apply to ALL genres live in the always-loaded
|
|
26
|
+
`AGENTS.md` (palette, memory map, the loop) and in this skill's other sections
|
|
27
|
+
(token budget). The genre files assume you already know those.
|
|
28
|
+
|
|
29
|
+
## Idioms every genre file relies on (quick recap)
|
|
30
|
+
|
|
31
|
+
- `add(list,obj)` / `del(list,obj)` / `deli(list,i)` for entity lists. Deleting
|
|
32
|
+
the CURRENT item inside `foreach` is safe; deleting inside an ASCENDING numeric
|
|
33
|
+
`for i=1,#list` loop is NOT (it errors once the list shrinks). Iterate a
|
|
34
|
+
descending loop or `foreach` when you delete while iterating.
|
|
35
|
+
- `split"a,b,c"` turns a comma string into a 1-based array in one cheap token;
|
|
36
|
+
it is the cheap way to declare data tables (frames, hitboxes, level rows).
|
|
37
|
+
- `sin(t)`/`cos(t)` take TURNS (0..1), not radians, and `sin` is INVERTED for
|
|
38
|
+
screen space (+y is down). A full circle is `t: 0->1`.
|
|
39
|
+
- `mid(lo,x,hi)` is the idiomatic clamp.
|
|
40
|
+
- Draw state (`camera`, `pal`, `palt`, `clip`, `color`, `fillp`) PERSISTS across
|
|
41
|
+
frames and calls. Reset with the no-arg form (`camera()`, `pal()`) before
|
|
42
|
+
drawing the HUD, or it leaks.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Mode-7 / pseudo-3D racing reference (tline floor projection)
|
|
2
|
+
|
|
3
|
+
Structure for a pseudo-3D game with a projected ground plane (arcade racer,
|
|
4
|
+
first-person floor). PICO-8 has no real 3D; you FAKE it by drawing the map as a
|
|
5
|
+
perspective-warped floor with `tline` (textured line), one horizontal scanline at
|
|
6
|
+
a time, each sampling the map further away as it climbs toward the horizon. This
|
|
7
|
+
is structurally unlike every other genre here: there are no entity-vs-tile
|
|
8
|
+
collisions in world pixels, the "world" is a texture you sample.
|
|
9
|
+
|
|
10
|
+
## The load-bearing rules
|
|
11
|
+
|
|
12
|
+
1. Draw the floor BOTTOM-UP: for each screen row `py` from the horizon down to the
|
|
13
|
+
bottom, compute how far into the world that row is, and `tline` one row of the
|
|
14
|
+
map at that depth. Rows near the bottom are close (map moves fast); rows near
|
|
15
|
+
the horizon are far (map moves slowly).
|
|
16
|
+
2. `tline(x0,y0,x1,y1, u,v, du,dv)` draws screen pixels `x0,y0..x1,y1` sampling the
|
|
17
|
+
MAP starting at map-coord `(u,v)` and stepping `(du,dv)` per pixel. The scale
|
|
18
|
+
(how much world per screen pixel) is what creates perspective: bigger step near
|
|
19
|
+
the bottom, smaller near the horizon.
|
|
20
|
+
3. Rotation uses `sin`/`cos` in TURNS (0..1). The camera angle rotates the `(du,dv)`
|
|
21
|
+
sampling basis; do NOT feed radians.
|
|
22
|
+
4. This is CPU-heavy. Keep it to the floor; draw sprites (player, opponents) on top
|
|
23
|
+
with normal `spr`, scaled by their distance.
|
|
24
|
+
|
|
25
|
+
## Minimal floor projection
|
|
26
|
+
|
|
27
|
+
```lua
|
|
28
|
+
-- camera: world pos (cx,cy), heading angle (ang, in TURNS), height above floor
|
|
29
|
+
cam={x=0,y=0,ang=0,h=16}
|
|
30
|
+
horizon=48 -- screen row where the floor starts (sky above)
|
|
31
|
+
|
|
32
|
+
function draw_floor()
|
|
33
|
+
local ca,sa=cos(cam.ang),sin(cam.ang)
|
|
34
|
+
for py=horizon,127 do
|
|
35
|
+
-- depth: how far this scanline is into the world. Rows just below the horizon
|
|
36
|
+
-- are very far (large scale); rows at the bottom are near. This 1/(py-horizon)
|
|
37
|
+
-- shape is the perspective.
|
|
38
|
+
local d=cam.h/(py-horizon+1) -- world distance for this row
|
|
39
|
+
local scale=d/64 -- world units per screen pixel
|
|
40
|
+
-- the world point at the CENTER of this scanline, then the left/right ends
|
|
41
|
+
local wx=cam.x + d*ca
|
|
42
|
+
local wy=cam.y + d*sa
|
|
43
|
+
-- sampling basis perpendicular to heading, scaled by depth
|
|
44
|
+
local du= sa*scale
|
|
45
|
+
local dv=-ca*scale
|
|
46
|
+
-- start at the left screen edge: back off 64 pixels along the basis
|
|
47
|
+
local u=wx - du*64
|
|
48
|
+
local v=wy - dv*64
|
|
49
|
+
tline(0,py,127,py, u,v, du,dv) -- one textured scanline of the map
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Tune `horizon`, `cam.h`, and the `d`/`scale` constants to taste; the exact numbers
|
|
55
|
+
depend on your track's map scale. The SHAPE (1/row depth, perpendicular basis from
|
|
56
|
+
`sin`/`cos`, `tline` per row) is what matters.
|
|
57
|
+
|
|
58
|
+
## Driving (heading + speed, no tile collision in the usual sense)
|
|
59
|
+
|
|
60
|
+
```lua
|
|
61
|
+
speed=0
|
|
62
|
+
function update_car()
|
|
63
|
+
if btn(2) then speed=min(speed+0.1,4) end -- accelerate
|
|
64
|
+
if btn(3) then speed=max(speed-0.1,0) end -- brake
|
|
65
|
+
if btn(0) then cam.ang-=0.005*speed end -- steer (turn amount scales w/ speed)
|
|
66
|
+
if btn(1) then cam.ang+=0.005*speed end
|
|
67
|
+
-- move along heading (TURNS: cos/sin)
|
|
68
|
+
cam.x+=cos(cam.ang)*speed
|
|
69
|
+
cam.y+=sin(cam.ang)*speed
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Off-track detection reads the map tile under the car (`mget(cam.x\8, cam.y\8)`) and
|
|
74
|
+
slows the car, rather than blocking movement like a platformer wall.
|
|
75
|
+
|
|
76
|
+
## Draw order
|
|
77
|
+
|
|
78
|
+
```lua
|
|
79
|
+
function _draw()
|
|
80
|
+
cls(12) -- sky
|
|
81
|
+
draw_floor() -- projected map
|
|
82
|
+
draw_opponents() -- spr, scaled by distance, sorted far-to-near
|
|
83
|
+
draw_hud() -- speed, lap; camera is not offset here (floor is drawn directly)
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Genre pitfalls checklist
|
|
88
|
+
|
|
89
|
+
- [ ] Floor drawn per-scanline with `tline`, horizon down to row 127.
|
|
90
|
+
- [ ] Depth uses a 1/(row) shape so far rows compress toward the horizon.
|
|
91
|
+
- [ ] Rotation via `sin`/`cos` in TURNS; sampling basis is perpendicular to heading.
|
|
92
|
+
- [ ] It is CPU-heavy: profile with `picopilot run`, keep the floor loop tight,
|
|
93
|
+
consider `_update60`->`_update` (30fps) if it cannot keep up.
|
|
94
|
+
- [ ] Sprites drawn on top, scaled and sorted by distance, NOT via the floor.
|
|
95
|
+
- [ ] Off-track is a tile lookup that slows you, not a solid-wall collision.
|