# Locke Cartridge Primer

A one-page spec of the Locke console and how to write a program for it. Read it to learn
the system and write your own, or vibe-code: hand the whole file to a coding model and it
can build you a game. Either way the rules are the same, and they are all right here.

Everything here runs in the **Forge** console at installatlas.com/forge (paste the code
into the editor) and exports to a real `game.cpp` cartridge that compiles and plays on a
Locke. Stay inside these rules and your program does both.

---

## The console

- **Screen:** 480 wide by 270 tall pixels, full colour, scaled up to fill a TV.
- **Origin:** (0, 0) is the top-left. `x` grows right, `y` grows down.
- **Constants:** `W` is 480, `H` is 270.
- **Players:** two USB gamepads, read as `ctx.pad[0]` and `ctx.pad[1]`.
- **Frame rate:** about 60 frames a second.

## The shape of a program

A cartridge is ONE file. You may define these functions (all optional except you almost
always want `update`):

```
function init(ctx) { }      // runs once at the start
function update(ctx) { }    // runs every frame, ~60 times a second. Draw the whole screen here.
function on_exit(ctx) { }   // runs once when the game closes
```

You may also declare your own top-level variables and your own helper functions.

```
let score = 0;                          // a value that lives between frames
function box(ctx, x, y) {               // your own helper
  rect(ctx, x, y, 10, 10, rgb(255, 0, 0));
}
```

## Colour

`rgb(red, green, blue)`, each from 0 to 255. There is no palette; every pixel is its own
colour.

```
let gold = rgb(255, 200, 70);
```

## Drawing (each function takes `ctx` first)

- `fill(ctx, col)` — paint the whole screen one colour. Call it FIRST each frame to clear.
- `rect(ctx, x, y, w, h, col)` — a filled rectangle.
- `frame(ctx, x, y, w, h, col)` — a rectangle outline.
- `line(ctx, x0, y0, x1, y1, col)` — a straight line.
- `circle(ctx, cx, cy, r, col)` — a filled circle at its centre.
- `pixel(ctx, x, y, col)` — a single pixel.
- `number(ctx, x, y, value, col, size)` — draws a whole number (a score).
- `text(ctx, x, y, "WORDS", col, size)` — draws letters and digits. The font is uppercase;
  lowercase is drawn as uppercase. `size` is how big (e.g. 3).

**Draw order matters.** Whatever you draw later sits on top. So `fill()` belongs at the
top of `update`, or it will paint over everything else and hide it.

## Reading the controls

Each pad is a set of buttons packed into one number. Test a button with `&`:

```
if (ctx.pad[0] & LK_PAD_LEFT)  x -= 1;
if (ctx.pad[0] & LK_PAD_A)     jump();
```

The buttons: `LK_PAD_LEFT`, `LK_PAD_RIGHT`, `LK_PAD_UP`, `LK_PAD_DOWN`, `LK_PAD_A`,
`LK_PAD_B`, `LK_PAD_X`, `LK_PAD_Y`, `LK_PAD_L`, `LK_PAD_R`, `LK_PAD_START`, `LK_PAD_SELECT`.

In the Forge preview the keyboard maps to pad 0: arrows = move, Z = A, X = B, A = X, S = Y,
Enter = Start, Shift = Select.

**Fire once on a press** (not every frame it is held): remember the last state.

```
let prevA = false;
function update(ctx) {
  let a = (ctx.pad[0] & LK_PAD_A) != 0;
  if (a && !prevA) {  /* this runs once per press */  }
  prevA = a;
}
```

## Time and motion

- `ctx.dt` — seconds since the last frame. Multiply movement by it so the speed is fair on
  any console: `x += 120 * ctx.dt;`
- `ctx.millis` — milliseconds since the program started. Good for animation: `sin(ctx.millis / 300)`.

## Sound

- `ctx.sfx(NAME)` — play a sound effect once. Names: `SFX_TICK`, `SFX_SELECT`, `SFX_BACK`,
  `SFX_SHOOT`, `SFX_HIT`, `SFX_BOOM`, `SFX_SCORE`, `SFX_JUMP`, `SFX_BOUNCE`, `SFX_THRUST`,
  `SFX_LAND`, `SFX_WIN`, `SFX_LOSE`, `SFX_WRONG`, `SFX_RIGHT`, `SFX_STEP`, `SFX_FLAP`,
  `SFX_DICE`, `SFX_PICKUP`, `SFX_GROWL`, `SFX_PAIN`, `SFX_THROW`, `SFX_WHOOSH`, `SFX_MYSTIC`.
- `ctx.music(TRACK)` — loop background music. `MUS_NONE` (stop), `MUS_MENU`, `MUS_PLAY`, `MUS_CALM`.

## Saving (high scores)

- `saveInt(ctx, "best", value)` — store a whole number through a power-off.
- `loadInt(ctx, "best", 0)` — read it back, with a default if it was never saved.

## Maths

Use these directly (no `Math.` prefix): `sin`, `cos`, `tan`, `abs`, `min`, `max`, `floor`,
`ceil`, `round`, `sqrt`, `random` (returns 0 to 1), and `PI`.

---

## What runs AND compiles (the constraints)

The Forge runs your code live in the browser and also turns it into a real `game.cpp`. To
make sure it both runs and compiles to a cartridge, keep to this:

- **Numbers (scalars)** for all your variables, declared with `let`, `const`, or `var`.
- The **drawing, sound, save, and maths functions** listed above.
- Your **own functions**: `function name(ctx) { }` or `function name() { }`.
- Normal **control flow**: `if`, `else`, `for`, `while`, and the operators
  `+ - * / % && || ! < > <= >= == !=` and the bitwise `&` for reading pads.
- The **constants**: `W`, `H`, the `LK_PAD_*`, `SFX_*`, and `MUS_*` names.

**Avoid these** (they may run in the preview but will NOT compile onto a Locke):

- Objects `{ }`, classes, or `this`.
- Strings, except a literal inside `text(ctx, ..., "LIKE THIS", ...)`.
- Arrays that grow or change length, `push`, `pop`, `map`, etc. (A fixed list of numbers,
  like an 8-by-8 sprite, runs in the preview but needs a small hand edit to export.)
- Anything from the browser or web: `fetch`, `document`, `window`, `setTimeout`, `console`,
  `JSON`, `async`, promises.

Keep state in your top-level `let` variables and update it every frame. That is the whole model.

## A minimal cartridge

```
function update(ctx) {
  fill(ctx, rgb(20, 30, 60));               // clear the screen
  rect(ctx, 220, 120, 40, 40, rgb(255, 200, 70));  // a gold square in the middle
}
```

## A small complete game (move and catch)

```
let px = 240, dx = 240, dy = 0, score = 0;
function update(ctx) {
  if (ctx.pad[0] & LK_PAD_LEFT)  px -= 240 * ctx.dt;
  if (ctx.pad[0] & LK_PAD_RIGHT) px += 240 * ctx.dt;
  if (px < 22) px = 22;  if (px > W - 22) px = W - 22;
  dy += 150 * ctx.dt;                          // the dot falls
  if (dy > H - 16) {
    if (abs(dx - px) < 26) { score += 1; ctx.sfx(SFX_SCORE); }
    else { score = 0; ctx.sfx(SFX_LOSE); }
    dy = 0;  dx = 20 + floor(random() * (W - 40));
  }
  fill(ctx, rgb(14, 16, 26));
  circle(ctx, dx, dy, 6, rgb(255, 120, 80));   // the falling dot
  rect(ctx, px - 22, H - 12, 44, 6, rgb(120, 220, 160));  // the paddle
  number(ctx, 8, 8, score, rgb(255, 255, 255), 2);
}
```

---

## For vibe coding (notes for a coding model)

If a coding model is reading this, here is the brief. You are writing a single,
self-contained program for the **Locke** console using ONLY the functions and constants in
this primer. The screen is 480 by 270, there are two gamepads, and `update(ctx)` runs about
60 times a second.

1. Ask the person what they want: the kind of game, the goal, and the controls.
2. Write ONE program with `init` and `update` (and helper functions if useful). Clear the
   screen with `fill` at the top of `update`, move things with `ctx.dt`, read input from
   `ctx.pad[0]` (and `ctx.pad[1]` for two players), and keep score in top-level `let`
   variables.
3. Stay inside the constraints above (scalars and the listed functions only) so it both
   runs in the Forge and compiles to a real cartridge. No objects, no browser APIs, no
   growing arrays.
4. Output just the code, ready to paste into the Forge editor. Add short comments so the
   person can change the numbers and colours themselves.

When they paste it into the Forge, it runs live; pressing **Export game.cpp** saves the
cartridge to put on a USB stick and plug into a Locke.
