# Castle Script

## START HERE — read this before doing anything else

This is a **single-file Lua project**. You have everything you need already loaded.

**Files in this project (do NOT explore beyond these):**
- `main.lua` — **the only file you will edit**
- `CLAUDE.md` — this file (already loaded)
- `test.mjs` — automated test runner (run after every change)
- `.castle-script-port` — exists when server is running (read to get port number)
- `castle-script.json` — optional variables config (may not exist)
- `images/` — optional image assets directory (may not exist)
- `docs/images.md` — read if using `castle.image.load` or `castle-script imagine`
- `docs/physics.md` — read if using `castle.createActor` or physics
- `docs/async.md` — read if using `castle.co.*` or `castle.events.*`
- `docs/particles.md` — read if using `castle.particles.*`
- `docs/math.md` — read if using `castle.math.*`
- `docs/data.md` — read if using `castle.setVariable` or `castle.getVariable`

**Do NOT** run `ls`, glob files, explore directories, or read any file other than those listed above.
**Do NOT** spend time planning before reading `main.lua`.

**Your first action, always:** Read `main.lua` immediately, then read any `docs/` files relevant to the APIs it uses. Then make changes.

---

## Workflow

**Edit `main.lua` directly.** The web player hot-reloads automatically on every save.

**Start the server** (if not already running):
```bash
castle-script serve main.lua --open
```

**After each significant change:**
1. Save — browser reloads within ~1 second
2. **Run `node test.mjs` automatically** (don't wait to be asked) — check for a `.castle-script-port` file first; if missing, start the server with `castle-script serve main.lua &` and wait ~2 seconds before running
3. Read `screenshot.png` to confirm visual output
4. Check terminal for Lua `print()` output and errors

**Mobile CLI Preview tools** (requires Castle app open on CLI Preview screen):
- `castle-script screenshot <path>` — save a screenshot of the running game
- `castle-script restart` — restart the game
- `castle.screenshot(filename)` in Lua — capture a screenshot at a specific moment; saved to the project directory by the running `serve` process (only works from top-level script, only in CLI Preview)

**Lua errors** appear in the terminal running `castle-script serve`, prefixed with `[Script]`.

---

## Mobile-first design

Castle games run on **mobile devices** (phones and tablets). Design for touch input, not keyboard or mouse.

**Primary input: touch** — use `castle.getTouches()` for all user interaction. Do NOT use keyboard events or mouse APIs — they don't exist in the mobile runtime.

**Design guidelines:**
- Touch targets should be large (at least 0.5 units wide/tall)
- Support tapping anywhere on screen for simple interactions
- For tilt-based games, use `castle.getDeviceTilt()`
- The canvas is portrait (5:7 ratio) — design layouts accordingly

---

## Coordinate system

In `onDraw()`, the visible world is:

- **x**: `-5` (left edge) → `5` (right edge)
- **y**: `-7` (top edge) → `7` (bottom edge)
- `(0, 0)` is the center of the screen

The canvas is 5:7 aspect ratio (450×630 px at full size). Leave at least 0.3 units of margin from `±5` and `±7` to keep content fully visible.

---

## Lifecycle callbacks

```lua
function onUpdate(dt)
  -- Called every frame; dt is delta time in seconds
end

function onDraw()
  -- Called every frame for drawing
  -- castle.draw.* functions ONLY work inside this callback
end
```

Physics collision callbacks (`onCollide`, `onSeparate`) are in `docs/physics.md`.

Code at module top-level (outside any callback) executes once at startup — safe for initializing state, starting coroutines, and subscribing to events.

---

## Touch input

### `castle.getTouches()`

Returns an array of all active touches this frame.

```lua
local touches = castle.getTouches()
for _, touch in ipairs(touches) do
  -- touch.id       -- unique touch identifier
  -- touch.x        -- current x position (same coordinate space as onDraw)
  -- touch.y        -- current y position
  -- touch.deltaX   -- change in x since last frame
  -- touch.deltaY   -- change in y since last frame
  -- touch.initialX -- x position when touch started
  -- touch.initialY -- y position when touch started
  -- touch.pressed  -- true only on the frame the touch began
  -- touch.released -- true only on the frame the touch ended
  -- touch.duration -- seconds since touch started
end
```

**Tap detection:**
```lua
function onUpdate(dt)
  local touches = castle.getTouches()
  for _, touch in ipairs(touches) do
    if touch.pressed then
      -- user tapped at (touch.x, touch.y)
    end
  end
end
```

### `castle.getTouch([touchId])`

Returns a single touch by ID, or the first active touch if `touchId` is nil. Returns `nil` if no touch is active.

```lua
local touch = castle.getTouch()  -- first touch, or nil
if touch then
  -- use touch.x, touch.y, etc.
end
```

### `castle.getDeviceTilt()`

Returns accelerometer tilt values. Useful for tilt-to-move games.

```lua
local tiltX, tiltY = castle.getDeviceTilt()
-- tiltX: negative = tilted left, positive = tilted right
-- tiltY: negative = tilted forward, positive = tilted back
```

---

## `castle.draw.*` functions

`castle.draw.*` functions only work inside `onDraw()`. The graphics state (color, line width, transforms) is automatically saved before `onDraw()` and restored after.

### `castle.draw.setColor(r, g, b, a)`

Sets the drawing color. `r`, `g`, `b` are floats from 0 to 1. `a` (alpha/opacity) is optional and defaults to 1.

```lua
castle.draw.setColor(1, 0, 0)        -- red, fully opaque
castle.draw.setColor(0, 0.5, 1, 0.5) -- light blue, half transparent
```

### `castle.draw.rectangle(mode, x, y, width, height)`

Draws a rectangle. `mode` is `"fill"` or `"line"`. `x`, `y` is the top-left corner.

### `castle.draw.roundedRectangle(mode, x, y, width, height, rx, ry)`

Draws a rectangle with rounded corners. `rx` and `ry` are the corner radius.

### `castle.draw.circle(mode, x, y, radius)`

Draws a circle. `mode` is `"fill"` or `"line"`.

### `castle.draw.ellipse(mode, x, y, radiusX, radiusY)`

Draws an ellipse.

### `castle.draw.line(x1, y1, x2, y2)`

Draws a line between two points.

### `castle.draw.polygon(mode, x1, y1, x2, y2, x3, y3, ...)`

Draws a polygon with an arbitrary number of vertices (at least 3 points). `mode` is `"fill"` or `"line"`.

```lua
castle.draw.polygon("fill", -1.5, -1.5, 1.5, -1.5, 0, 1.5)
```

### `castle.draw.triangle(mode, x1, y1, x2, y2, x3, y3)`

Draws a triangle. `mode` is `"fill"` or `"line"`.

### `castle.draw.setLineWidth(width)`

Sets the line width for `"line"` mode shapes and `castle.draw.line`.

**Important:** Line strokes are centered on the shape boundary — half the line width extends *outside* the shape's geometry. When drawing tightly-packed shapes, use `"fill"` mode instead.

### `castle.draw.push()` / `castle.draw.pop()`

Saves/restores the graphics state (color, line width, transform) onto a stack.

```lua
castle.draw.setColor(1, 0, 0)
castle.draw.push()
  castle.draw.setColor(0, 0, 1)
  castle.draw.circle("fill", -1.5, 0, 0.8) -- blue
castle.draw.pop()
castle.draw.circle("fill", 1.5, 0, 0.8)    -- red (restored)
```

### `castle.draw.translate(x, y)` / `castle.draw.rotate(angle)` / `castle.draw.scale(sx, sy)`

Transform the coordinate system. `rotate` angle is in radians.

```lua
castle.draw.push()
  castle.draw.translate(2, 1)
  castle.draw.rotate(math.pi / 4)
  castle.draw.rectangle("fill", -0.5, -0.5, 1, 1)
castle.draw.pop()
```

### `castle.draw.origin()`

Resets the coordinate transform to the default (screen-centered origin).

### `castle.draw.text(text, x, y, size [, halign, valign, font])`

Draws text at position `(x, y)`. `size=10` is 1 world unit tall (45px). Common values: `4`–`7` for scores/small text, `8`–`12` for labels/prompts, `12`–`20` for headlines. For long strings, keep size ≤ 12 or the text will overflow the 10-unit wide screen.

Optional parameters:
- `halign`: `"left"` (default), `"center"`, `"right"`
- `valign`: `"top"` (default), `"middle"`, `"bottom"`
- `font`: defaults to `"DMSans"`. Available: `DMSans`, `Glacier`, `HelicoCentrica`, `Piazzolla`, `YatraOne`, `Bore`, `Synco`, `Tektur`, `CourierPrime`

```lua
function onDraw()
  castle.draw.setColor(1, 1, 1)
  castle.draw.text("Score: 42", -4, -6, 6)

  castle.draw.setColor(1, 0.5, 0)
  castle.draw.text("GAME OVER", 0, 0, 14, "center", "middle", "Bore")
end
```

### `castle.draw.measureText(text, size [, font])`

Returns the width and height of the given text. Useful for centering or layout.

```lua
local w, h = castle.draw.measureText("Hello", 6)
castle.draw.setColor(0, 0, 0, 0.5)
castle.draw.rectangle("fill", -w/2, -h/2, w, h)
castle.draw.setColor(1, 1, 1)
castle.draw.text("Hello", 0, 0, 6, "center", "middle")
```

### `castle.draw.image(handle, x, y [, angle, sx, sy, ox, oy])`

Draws a loaded image at position `(x, y)`. Must be called inside `onDraw()`. See `docs/images.md` for how to load images.

- `handle` — image handle returned by `castle.image.load`
- `x`, `y` — position in world coordinates (top-left corner by default; use `ox`/`oy` to change anchor)
- `angle` — rotation in radians (default 0)
- `sx`, `sy` — x and y scale (default 1). If only `sx` is given, `sy` defaults to match it
- `ox`, `oy` — origin offset in **pixels** — shifts the anchor point (default 0, 0 = top-left)

**Images are drawn at their native pixel size by default** (`sx=sy=1` means 1 pixel = 1 world unit, which is very large). Scale down using `sx`/`sy`.

---

## Time

```lua
local t = castle.getTime()  -- seconds since scene start (float)
```

---

## Minimal working example

**Touch-controlled ball:**
```lua
local ballX, ballY = 0, 0

function onUpdate(dt)
  local touch = castle.getTouch()
  if touch then ballX, ballY = touch.x, touch.y end
end

function onDraw()
  castle.draw.setColor(0.05, 0.05, 0.15)
  castle.draw.rectangle("fill", -5, -7, 10, 14)
  castle.draw.setColor(1, 0.4, 0.1)
  castle.draw.circle("fill", ballX, ballY, 0.4)
end
```
