---
name: writing-cartridges
description: Write a Slopbox cartridge for cart.slopped.site - one plain-text file that is a whole small game (meta lines, then [palette] [sprite] [map] [sound] [music] [shader] [text] [game] sections) - that validates first time and plays well. Covers the workflow (write, validate with the validate_cartridge MCP tool or the builder, fix, play, share as a link), the game language, two complete worked examples, the validator's common complaints and what fixes them, design for small screens, and sound and Patter (music) tips. Use when asked to make, fix, extend or review a cartridge or a game for cart.slopped.site.
---

# Writing cartridges

A Slopbox cartridge is one UTF-8 text file, up to 64 KB, that is a whole game. It plays in the Slopbox, the sealed
console on https://cart.slopped.site/, and travels as a file or as a link with the whole text inside it. This skill is how to
write one that validates and is worth playing.

- The full specification (every keyword, built-in and limit): https://cart.slopped.site/spec.md
- The same, for people: https://cart.slopped.site/docs/
- The Slopbox Game Builder (edit and play side by side): https://cart.slopped.site/build/
- Starters to read and copy, each in the same folder as https://cart.slopped.site/carts/first.cart (the list with titles is
  https://cart.slopped.site/carts/index.json): `first.cart` (the tutorial, with notes on every section);
  `yestoall.cart`; and games from games.slopped.site rewritten as cartridges: arcade games (`eel.cart`,
  `bricked.cart`, `jumpup.cart`, `flightrisk.cart`, `falldown.cart`, `stack.cart`, `helicopter.cart`,
  `hotdesk.cart`, `invaders.cart`, `crossing.cart`, `mazechase.cart`) and grids and puzzles (`blocks.cart`,
  `2048.cart`, `mines.cart`, `memory.cart`, `slide.cart`); and pocket-sized originals that keep to what an 8-bit
  colour handheld can show (160×144, sprites of at most 16×16 in 3 colours, 8 palettes, 40 moving things, 4 voices),
  each a different genre: `cellar.cart` (a dungeon), `flotsam.cart` (fishing), `keeptime.cart` (rhythm),
  `tidy.cart` (a pushing puzzle), `paperplane.cart` (bullet patterns), `windowsill.cart` (tending),
  `ballast.cart` (a lander) and `patience.cart` (solitaire, every deal built backwards from a win).

## The workflow

1. **Write the whole file.** Start from the skeleton below or a starter. Keep it small; add one mechanic at a time.
2. **Validate.** Call the `validate_cartridge` tool on the slopped.site MCP server (https://mcp.slopped.site/mcp,
   Streamable HTTP, no sign-in) with `{ "text": "<the whole file>" }`. It runs the same validator as the builder and
   the console, and returns `ok`, every error as `{ line, col, section, msg }`, warnings and what the cartridge holds.
   No MCP? Paste the text into the builder: problems are listed beside the game with their lines.
3. **Fix every error, top first.** One mistake can cause the ones after it. Each message says what would be right.
4. **Play it.** Open it in the builder (paste it, or "Open file…"). Lose on purpose, win on purpose, and try a phone.
5. **Share it.** "Copy share link" in the builder gives `https://cart.slopped.site/play/#c=1.…`: the text, deflated,
   after the `#`, so nothing is uploaded. Links stop at 16,000 characters; bigger cartridges travel as `.cart` files.

## The shape of a file

```text
cart 1                      always the first line
title: Drip tray            meta lines, key: value (title is required)
screen: 96x128              64 to 256 each way; 160x144 if left out
touch: sides                how a phone plays it: tap hold sides swipe drag pad buttons
buttons: b                  optional: on-screen buttons beside that mode (only ones the rules read)
hud: score lives            up to three variables shown top left

[sprite tray]               pictures, in palette letters
[sound plink]               synthesizer recipes
[music drips]               tunes in Patter
[text]                      the game's words: help, over, won, and any you say
[game]                      the rules (required)
```

With no `[palette]`, these letters are colours: `k` ink, `w` paper, `a` acid, `m` muted, `l` line, `p` pink,
`o` orange, `g` moss, `s` tin, `b` blue, `r` red, `y` toast. `.` is see-through.

## The game language, in one screen

```text
var lives = 3                 a number every rule can read and change
kind drop sprite drop         a kind of thing, with a picture (or: size W H, invisible)
  vy = fall                   its fields' starting values, worked out as each one is made
scene kitchen                 rules after this belong to the scene
on enter                      when the scene starts
  spawn tray at 42, 116
on hold left                  every tick the button is down (also press, release)
  tray.x = max(0, tray.x - 2) kind.field means the first thing of that kind
on every 50                   every 50 ticks (60 ticks = 1 second)
  spawn drop at 4 + rnd(W - 8), 0
on hit tray drop              each overlapping pair: self is the tray, other the drop
  score += 1
  destroy other
on each drop                  every tick, for every drop, as self
  if self.y > H
    lose
```

Indent with spaces. Events: `start enter tick every press release hold hit each out`. Statements: `spawn destroy
bounce block scatter sound music go win lose say shake burst place if else` and assignments (`= += -= *= /=`).
Functions: `abs min max floor ceil round sign sqrt clamp rnd held pressed count`. Every thing has
`x y vx vy w h frame life hidden flip`; every game has `score camx camy` and can read `tick W H px py pointing`.
Movement is automatic: each tick `x += vx`, `y += vy`. Buttons: `left right up down a b` (`any` in events).

## Worked example: a whole game

Drip tray: catch the drips, three misses and the floor is wet. It validates, and a bot that follows the lowest drop
plays for two minutes without missing; standing still loses in about five seconds.

```cart
cart 1
title: Drip tray
by: an example
seed: 7
screen: 96x128
background: w
touch: sides
hud: score lives

[sprite tray]
kkkkkkkkkkkk
.kssssssssk.

[sprite drop]
.b.
bbb
.b.

[sound plink]
wave sine
pitch 880
slide 1320
attack 0.004
decay 0.12
release 0.04
volume -18

[sound splat]
wave noise
attack 0.004
decay 0.2
release 0.05
volume -16
filter lowpass 900

[sound tick]
wave triangle
pitch 440
attack 0.004
decay 0.08
release 0.03
volume -24

[music drips]
bpm 100
tick tick -6 | c5 ~ e5 ~ <g5 a5> ~ e5 ~

[text]
help: Left and right, or hold a side. Catch the drips.
over: The floor is wet.
miss: Missed one.

[game]
var lives = 3
var fall = 0.6

kind tray sprite tray
kind drop sprite drop
  vy = fall

scene kitchen
on enter
  spawn tray at 42, 116
  music drips

on hold left
  tray.x = max(0, tray.x - 2)
on hold right
  tray.x = min(W - tray.w, tray.x + 2)

on every 50
  spawn drop at 4 + rnd(W - 8), 0

on hit tray drop
  score += 1
  fall = min(fall + 0.03, 2)
  sound plink
  destroy other

on each drop
  if self.y > H
    lives -= 1
    sound splat
    shake 6
    say miss
    destroy
    if lives <= 0
      lose
```

What to notice: the difficulty ramps by a variable the kind reads when each drop is made (`vy = fall`), capped with
`min`; every event that matters has a sound and something you can see (`shake`, `say`); `touch: sides` matches a game
that only uses left and right.

## Patterns from the starters

Turning on a grid without reversing into yourself (Eel in the tin, `touch: swipe`):

```text
on press left
  if dx == 0
    ndx = -1
    ndy = 0
```

A paddle that follows the mouse or finger and the keys (Bricked, `touch: drag`):

```text
on tick
  if pointing
    ruler.x = clamp(px - 12, 0, W - 24)
  if held(left)
    ruler.x = max(0, ruler.x - 3)
```

One-button flight with gravity and a speed cap (Flight Risk, `touch: tap`):

```text
on press a
  moth.vy = -3.1
  sound flap
on each moth
  self.vy = min(self.vy + 0.18, 4)
```

Obstacles made from maps placed off the right edge, chosen by the run's seed (Flight Risk):

```text
on every 96
  r = rnd(4)
  if r == 0
    place gap1 at W, 0
  else
    place gap2 at W, 0
```

Looking before you move, without arrays (Falling Blocks, Mines, Maze Chase): spawn an invisible probe where you
want to look, let `on hit` mark what it found, and act on it the next tick.

```text
kind probe size 8 8
  life = 2
on hit probe brick
  blocked = 1
```

A thing that builds its own pictures the first tick it exists, so one marker carries a whole row (Fall Down's
shelves; spawn the marker with fields, then):

```text
on each shelf
  if self.built == 0
    self.built = 1
    spawn board at self.gx - 32, self.y
```

A bar of any width from pieces of 32, 16, 8, 4, 2 and 1 pixels: its width in binary, so trimming it is just making it
again narrower (Stack's jelly layers). Fields of a new kind are worked out when it is made and can read variables set
just before the `spawn`, which is how to pass it values.

A shuffle: `scatter` onto a grid that exactly fills the screen puts each card in a different random cell.

A thing that collects and comes back somewhere else: `scatter other 8` in `on hit` (it moves to a random empty cell
of an 8-pixel grid on screen). A timer: `var time = 30` and `on every 60` with `time -= 1`, shown with `hud: score time`.

## What the validator says, and what fixes it

| It says | Fix |
| --- | --- |
| The first line of a cartridge is "cart 1" | Make `cart 1` line 1, with nothing before it. |
| There is no "controls:" line. Meta keys: … | Only `title by version seed screen background touch buttons hud about` go before the first section. Instructions belong in `[text]` as `help:`. |
| "touch:" is how a phone plays it: tap, hold, sides, swipe, drag, pad, buttons | Pick one of those words. |
| This row is 5 wide; crumb is 4 wide | Every row of a sprite frame has the same number of letters. Pad with `.`. |
| "z" isn't in the palette | Use a palette letter, or add `z #rrggbb` to a `[palette]` section. |
| the envelope needs "attack" … / "volume" in dB … | A sound needs `wave`, `pitch` (not for noise), `attack`, `decay` and `volume`. |
| Indent with spaces, not tabs | Two spaces per level is the house style. |
| There is no variable "life": … (or did you mean self.life?) | Fields always say whose they are: `self.life = 300`, `other.vx = 1`. A bare name is a variable. |
| There is no self here | `self` exists in `on hit`, `on each`, `on out` and under `spawn`. In `on hold left`, write `tray.x`. |
| "sond" isn't a statement. … Did you mean "sound"? | Spelling. The list in the message is every statement. |
| spawn what? There is no kind "crum" | Declare `kind crum …` above the rules that use it, or fix the spelling. |
| Unexpected "=" after the expression. Compare with == | In `if`, compare with `==`. |
| There is no function "sin" | There is no trigonometry (it would differ between browsers). Use `abs`, `min`, `max`, `clamp`, steps. |
| scatter [other] <grid>: … like scatter 8 | The grid is a written number from 2 to 64. |
| There is no [sound gulp] | Every `sound`, `music`, `say`, `place` and `sprite` name must exist as a section or text line. |

Warnings (no `help:` or `over:` line) don't stop a cartridge, but add them: `help` is the title screen's only
instruction.

## Design for a small screen

- **Pick the screen for the game.** The default is 160×144. Tall games (falling, climbing) want portrait, like Jump
  Up's 128×192; a game of words wants room, like Yes to all's 192×176. Every side is 64 to 256 pixels; the console
  scales by whole pixels to fit, so small numbers look chunky and sharp.
- **Text is 3×5 pixel capitals.** A `say` panel holds 4 lines of (width − 16) ÷ 4 characters: 36 on a 160-wide screen.
  Keep `help` to two short sentences, and say which keys and which touch.
- **Sprites of 6 to 12 pixels read best** at these sizes; give the player a colour nothing else uses.
- **Keep the HUD to what changes** (`hud:` shows up to three variables, top left), and keep the play area clear of
  that corner.
- **Choose `touch:` for the buttons you use.** Only left/right: `sides`. One button: `tap`. Four directions on a grid:
  `swipe`. Free movement: `pad`. A pointer: `drag`, and read `px`, `py`, `pointing`. Two at once (steer and
  accelerate, run and jump, move and fire), or any game that reads `b`: `buttons`, an on-screen d-pad and A/B where
  each thumb counts on its own. `pad` is one finger, so it can never steer and hold a button together.
  When a gesture mode suits the game but one button is out of its reach (a restart in a swipe puzzle, depth in a
  one-finger fishing game), keep the mode and add `buttons: b` (or `buttons: up down`): the on-screen controller
  shows just those, and a finger on the game still plays as before. Say so in `help`.
- **Fairness.** `rnd` comes from the seed, so test several seeds. Never spawn on top of the player; give a beat before
  the first danger; ramp difficulty with a capped variable.
- **Feedback.** Every score, hit and loss gets a sound and a visible change: `burst`, `shake`, `say`, a frame change.
- **Limits worth knowing:** 256 things at once, 32 kinds, 64 sprites of up to 32×32, 8 maps, 32 sounds, 200 rules.

## Sound tips

- Start every sound with `attack 0.003` or more and end with a fade (`release 0.01`+), or it clicks.
- Keep effects short (0.05 to 0.3 s) and quiet (`volume -14` to `-24`); music voices quieter still (−6 dB in the
  voice line).
- `slide` up sounds like a reward (pitch 520 → 780); down sounds like a loss. `noise` with a `filter lowpass` is a
  splat or a crumble; `filter bandpass 900 2` makes it papery.
- Different jobs, different waves: `triangle` and `sine` for soft blips, `square` for a bright lead, `noise` for hits.

## Patter (music) tips

- One line per voice: `name sound [dB] | pattern`. One pattern is one bar of four beats; the steps share it.
- `~` or `.` rests, `_` holds, `[a b]` squeezes two into one step, `[c4,e4,g4]` is a chord, `<c5 d5>` takes turns bar
  by bar (cheap variation), `x*4` repeats.
- A short lead plus a bass two octaves down is plenty. `bpm 90` to `132` suits most games.
- The sound sets the timbre; a sound with `sustain 0` plays its own length (a pluck), so give long notes a sustain.
- Music ends with `music off`, and restarts when a scene starts it again.

## Before you hand it over

- It validates with no errors, and `help` and `over` exist.
- You lost it on purpose and it ended; if it can be won, you won it.
- Every rule you wrote happens at least once when you play.
- The title, `about:` and `[text]` lines are your own words.
