MCP server for PICO-8 cart development: validation, headless simulation, gfx/sfx/music authoring, run + screenshot
Server has 8 tools with complete input schemas and descriptions, but quality is uneven. Naming is verb-forward and clear (count_tokens, read_cart, validate_cart, simulate_cart, run_headless, run_cart, stop_cart, send_keys). Descriptions range from adequate to excellent, simulate_cart and validate_cart are detailed and actionable (200+ chars), while stop_cart is minimal (5 words). All tools have typed parameters with descriptions. However, output schemas are NOT documented, LLMs cannot predict what fields to expect from responses. Error handling is implicit (no recovery guidance in descriptions). No tool annotations (readOnlyHint/destructiveHint) despite clear risk levels. Parameter descriptions are good but lack format constraints (e.g., timeout ranges, key syntax for send_keys). Composition is sound, each tool has one responsibility, and tools chain logically (read_cart → validate_cart → simulate_cart → run_cart).
Count tokens, characters, and compressed size in a PICO-8 cart
Read a PICO-8 cart and return its sections (code, sprites, map, sfx, music)
Launch the cart in a real PICO-8 window. By default this KILLS any PICO-8 already running - if a human may be playing in their own window, pass restart=false to leave it alone (a second window opens). Default window 1024x1024 (PICO-8's screen is always 128x128; only the window scales). Follow with send_keys/capture_game to play and look at it. Prefer simulate_cart for anything measurable; use this to SEE the game.
Low-level: append arbitrary driver Lua to a cart's code and execute it with `pico8 -x` (headless). printh() output is returned. Use for unit-test style checks of functions ('assert(tms(90)=="1:30")'). If the cart defines _update/_draw, pico8 will start its game loop and the call will time out - prefer simulate_cart for whole-game runs. Loops must stay below 32767 iterations.
Send key presses to the running PICO-8 window (focuses it first). keys: space-separated names: x z c v up down left right enter esc p space r f6, plus 'wait:MS' and 'hold:KEY:MS' (e.g. 'hold:right:600'). PICO-8 buttons: arrows, O = z/c, X = x/v. Enter/p opens the pause menu, esc exits to the console.
Output schemas not documented. LLMs cannot predict response structure (fields, types, nesting). Forces agents to guess what data is available for downstream operations.
No tool annotations (readOnlyHint/destructiveHint/idempotentHint). Tools like run_cart and send_keys are clearly destructive (launch/kill PICO-8, send input), but LLMs have no formal signal to avoid unsafe retries or composition.
stop_cart description is 5 words ('Kill the running PICO-8 window.'). Lacks context on when to call it, what happens if no window is running, or whether it's safe to retry.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | B | 71 | 2026-07-28+ | v2 |
Run a cart's game loop HEADLESS at full CPU speed (no window) and return printed telemetry. Calls _init(), runs setup_lua, then _update()/_update60() for `seconds` of game time, evaluating log_lua every log_every seconds. A 25-minute game simulates in a few seconds. Use it for: balance telemetry, crash detection (runtime errors are reported with line numbers), regression checks. There is NO input headless - btn() and btnp() are always false, so a title screen waiting for a key press never advances: skip it in setup_lua (e.g. 'st="play" newgame()') and patch in an autopilot / god mode with `patches` (exact string replacements against the cart's CURRENT text, each must match exactly once; re-read the cart after editing it or the patch goes stale) - e.g. replace the btn() block with AI, or make hurt() count hits. Remember PICO-8 numbers wrap above 32767: never let a frame counter run past 18 minutes.
Kill the running PICO-8 window.
Validate a PICO-8 cart: token/char/compressed limits, syntax errors, and lint. By default PICO-8-style global usage is NOT reported (carts use globals by convention); what remains is actionable: parse errors, unused/duplicate locals, typos. Run this after every code edit. Note it cannot catch runtime nil errors - use simulate_cart for that.
Parameter constraints not formalized. send_keys accepts 'keys' as free-form string with space-separated names; no enum or regex pattern. simulate_cart's 'timeout' and 'log_every' lack min/max bounds. LLMs may pass invalid values.
Error handling lacks recovery guidance. Descriptions do not explain what to do if a cart fails validation, simulation times out, or PICO-8 is not found. Agents have no actionable next steps.