# DrawAnET Agent Guide

DrawAnET — A pixel alien battle game. Human users create pixel art, then hand it over to Agents to write battle scripts.

- Each artwork can be activated as a Combat Unit
- Upon activation, a `battle_token` is issued for external script updates
- Scripts run in a sandbox, defining `onIdle(me, enemy, game)` to determine per-frame behavior

---

## Table of Contents

1. [Core Flow](#1-core-flow)
2. [Runtime Contract](#2-runtime-contract)
3. [Coordinate System & Common Pitfalls](#3-coordinate-system--common-pitfalls)
4. [Map Terrain](#4-map-terrain)
5. [Skill Details](#5-skill-details)
6. [Instruction Timing & Frame Model](#6-instruction-timing--frame-model)
7. [API Reference](#7-api-reference)
8. [Practical Advice](#8-practical-advice)

---

## 1. Core Flow

1. The user activates an artwork as a Combat Unit on the web page, obtains a `battle_token`, and passes it to you
2. **Read unit context**: `GET /api/v1/agent/unit/{battle_token}` → get artwork name, skill, current script code, battle stats
3. Analyze the current script and track record, formulate an improvement strategy
4. Update the script: `PATCH /api/v1/agent/unit/{battle_token}/script`
5. (Optional) Test in simulated battle: `POST /api/v1/agent/unit/{battle_token}/match/simulate`
6. Start a real match: `POST /api/v1/battle/match/pvp` to challenge others
7. Check the leaderboard: `GET /api/v1/battle/leaderboard`
8. Analyze replays: `GET /api/v1/battle/replays/{match_id}`

### Authentication

- **Agent API** (`/agent/*`): Authenticated via `battle_token` in the URL path (64-character hex)
- **Web API** (`/api/v1/*`): Uses Session Cookie (automatically attached after user login)

---

## 2. Runtime Contract

### Entry Function

```js
function onIdle(me, enemy, game) {
  // Called every frame when the engine needs an instruction
}
```

- `onIdle` must be defined; the engine calls it to obtain instructions
- You may define helper functions, but the entry point must be `onIdle`
- `onIdle` is only called when the instruction queue is empty
- The parameters are snapshot objects of the current frame; modifying them does not affect game state

### Available Instructions (Global Functions)

```js
go(n)              // Move forward n cells, n defaults to 1
turn(dir)          // Relative turn: 'left' counterclockwise 90°, 'right' clockwise 90°
td(me.dir, dir)    // Calculate further turn to align current facing with an absolute direction
delta(me.dir)      // Predict or get the coordinate delta for the cell ahead
fire()             // Fire (spawn a bullet)
shoot()            // Alias for fire()
plant()            // Plant a bomb
bomb()             // Alias for plant()
shield()            // Use the shield skill
boost()             // Use the boost skill
freeze()            // Use the freeze skill
confuse()           // Use the confuse skill
overload()          // Use the overload skill
cloak()             // Use the cloak skill
poison()            // Use the poison skill
teleport(x, y)      // Use the teleport skill, pass target coordinates
speak(text)        // Speak (visible only in replays, max 40 characters, does not consume an action)
print(...args)     // Debug log (visible only in engine output)
```

### me Object (Self Info)

```js
me.position[0]    // Current x coordinate
me.position[1]    // Current y coordinate
me.dir            // Facing: 'up' | 'down' | 'left' | 'right'
me.hp             // Health: 0 or 1 (instant death)
me.crystals       // Number of crystals held
me.effects        // Array of active effects, e.g. ['shield', 'boost']
me.skill          // Skill name string, e.g. 'shield', 'boost'
me.skillCd        // Remaining cooldown frames for the skill
me.bombActive     // Whether a bomb has been planted
me.bombFuse       // Remaining fuse frames on the bomb
me.bombCooldown   // Remaining cooldown frames after bomb explosion
me.fireLock       // Remaining fire-lock frames (>0 means fire()/shoot() are silently dropped)
me.alive          // Whether alive
me.cloaked        // Whether cloaked
```

### enemy Object (Enemy Info, may be null)

```js
enemy.position[0] // Enemy x coordinate
enemy.position[1] // Enemy y coordinate
enemy.dir         // Enemy facing ('up'|'down'|'left'|'right')
enemy.effects     // Enemy effects array
enemy.bombActive  // Whether the enemy has planted a bomb
enemy.bombCooldown // Remaining cooldown frames after enemy bomb explosion
enemy.alive       // Whether the enemy is alive
```

> `enemy` is `null` in the following cases:
> - The enemy is dead
> - The enemy is cloaked (cloak skill or standing on fog `"w"`)

### game Object (Global Info)

```js
game.map[y][x]         // Terrain grid (note: y first, then x!)
game.width             // Map width (number of columns)
game.height            // Map height (number of rows)
game.frame             // Current frame number (starting from 0)
game.crystals          // All active crystals as [[x, y], ...] array (use this field for new code)
game.bullets           // List of bullets [{ x, y, dir }]
game.bombs             // List of bombs [{ x, y, fuse, owner }]
game.events            // Event log (for decision support)
```

---

## 3. Coordinate System & Common Pitfalls

### Direction System (turn/td/delta)

Directions and turns use a mix of absolute and relative:

```js
// Absolute directions
'up'    // Up (y decreases)
'down'  // Down (y increases)
'left'  // Left (x decreases)
'right' // Right (x increases)

// turn() uses relative directions, only left/right
turn('left')   // Turn counterclockwise 90°
turn('right')  // Turn clockwise 90°
```

```js
// Turn to absolute direction up/down/left/right; if already facing the target, td returns null, and turn(null) does nothing = wasted opportunity
if (me.dir !== 'right') {
  turn(td(me.dir, 'right'));
}

// Predict/get the forward-facing delta
var d = delta(me.dir);
var nextX = me.position[0] + d.x;
var nextY = me.position[1] + d.y;
```

### ⚠️ Common Pitfalls

1. **`game.map` index order is y first, then x** — Correct usage: `game.map[y][x]`, NOT `game.map[x][y]`. Forgetting this order is the most common beginner mistake.
2. **`me.position` is an `[x, y]` array** — Different from `game.map`'s `[y][x]` indexing; be careful to distinguish them.
3. **`go(n)` enqueues n instructions at once** — This loses decision-making control for the next n frames (the engine won't call `onIdle`), making you "blind-fly" and extremely vulnerable to attacks. **Best practice: only `go(1)` at a time**.
4. **`turn(null)` does nothing** — When `td()` returns `null` (already facing the target direction), `turn(null)` won't turn, but the frame's action opportunity is wasted.

---

## 4. Map Terrain

The map is a **15-row × 19-column** character grid, bordered by rocks.

| Char | Name | Passable | Bullet Passes | Description |
|---|---|---|---|---|
| `"."` | Empty | ✅ | ✅ | Basic traversable terrain |
| `"r"` | Rock | ❌ | ❌ | Blocks movement and bullets; the border is entirely rocks |
| `"k"` | Crater | ❌ | ✅ | Blocks movement, but bullets can pass through |
| `"z"` | Alien vegetation | ❌ | ❌ | Blocks movement; bullets destroy vegetation on hit and disappear there |
| `"c"` | Crystal | ✅ | ✅ | Static crystals placed by the map editor; can be picked up by stepping on them; becomes empty after pickup; all active crystals are managed via `game.crystals` |
| `"w"` | Fog | ✅ | ✅ | Entering cloaks you (cloaked=true), making you invisible to the enemy |
| `"m"`/`"n"` | Portal pair | ✅ | ✅ | Stepping in teleports you to the paired portal; **fireLock=1** after teleport (cannot shoot for 1 frame) |
| `"y"` | Acid pool | ✅ | ✅ | Instant death on step (hp=0, ignores shield). Teleport can target this |

---

## 5. Skill Details

Each combat unit is randomly assigned **one** skill. Get the skill name via `me.skill`, and call the corresponding global function: `shield()`, `boost()`, `freeze()`, `confuse()`, `overload()`, `cloak()`, `poison()`, `teleport(x, y)`.

Skill names are strings, case-sensitive. There are 8 skills:

### shield

```js
if (me.skill === 'shield' && me.skillCd <= 0) {
  shield();
}
```

- Lasts **4 frames**, blocks **1** bullet/bomb hit
- Shield disappears after blocking
- Cooldown: **25 frames**

### freeze

```js
if (me.skill === 'freeze' && me.skillCd <= 0) {
  freeze();
}
```

- Target enemy **cannot act for 2 frames** (all instructions are skipped by the engine; queued instructions are preserved and executed after thawing)
- Cooldown: **29 frames**
- 💡 **Strategy**: The 2 frames of frozen enemy are a golden window — the enemy can't move/turn/shoot. Use these 2 frames to safely pursue, pick up crystals, or reposition and shoot

### confuse

```js
if (me.skill === 'confuse' && me.skillCd <= 0) {
  confuse();
}
```

- For **5 frames**, all go/turn instructions from the target enemy are replaced with **random-direction turn (free) + go** (facing randomized)
- If the enemy executes no instruction at all, the engine automatically injects a random turn + go
- Attacks (fire/bomb) and skills are unaffected
- Cooldown: **20 frames**

### overload

```js
if (me.skill === 'overload' && me.skillCd <= 0 && me.fireLock <= 0) {
  overload();
  // Skill fires a double shot: immediately spawns 2 parallel bullets
}
```

- Upon calling, **immediately** fires 2 parallel bullets, both traveling in the facing direction
- The secondary bullet is emitted from the cell offset by 1 toward the enemy side, in the same direction as the primary (e.g., if the enemy is above, the secondary comes from the cell above the head)
- **Requires `me.fireLock <= 0`** to activate (the engine rejects activation if fire-locked, wasting cooldown)
- Maximum 2 bullets on screen at once (normal shooting allows max 1)
- Cooldown: **32 frames**

### cloak

```js
if (me.skill === 'cloak' && me.skillCd <= 0) {
  cloak();
}
```

- Lasts **6 frames**; the enemy script sees `enemy` as `null`
- The enemy can't see you while cloaked
- Cooldown: **35 frames**

### poison

```js
if (me.skill === 'poison' && me.skillCd <= 0) {
  poison();
}
```

- Target enemy is poisoned for **3 frames**; **go instructions are delayed by 1 frame**
- turn / fire / bomb / skills are unaffected; only movement rate is halved (in effect: if a go instruction is submitted during a "skip frame" while poisoned, it executes on the next frame instead, and you lose onIdle decision rights for that next frame)
- Cooldown: **20 frames**

### teleport

```js
if (me.skill === 'teleport' && me.skillCd <= 0) {
  teleport(targetX, targetY);
}
```

- Instantly moves to the specified coordinates
- Target must be traversable terrain (empty/crystal/fog/acid pool) and not occupied by another character
- If the target is a crystal, it auto-redirects to an empty cell within a 3×3 range
- Teleporting to an acid pool is allowed (instant death — caveat emptor; tests the Agent's judgment)
- **fireLock check**: If the teleport landing spot has Manhattan distance ≤4 from the enemy → fireLock=2 (cannot shoot for 2 frames); distance >4 → no fire lock
- After teleport, **crystalLock=2** (cannot pick up crystals for 2 frames)
- If the target is invalid, teleport fails and you stay in place, but cooldown is still consumed
- Cooldown: **40 frames**

### boost

```js
if (me.skill === 'boost' && me.skillCd <= 0) {
  boost();
}
```

- Lasts **2 frames**; each frame `go()` moves **2 cells** (instead of 1)
- The first `turn` instruction during boost **does not consume** that frame's action (free turn)
- If also poisoned, boost and poison cancel out, returning to normal speed
- Cooldown: **26 frames**

### Skill Summary Table

| Skill | Function Call | Parameters | Duration (frames) | Cooldown (frames) |
|---|---|---|---|---|
| shield | `shield()` | None | 4 | 25 |
| freeze | `freeze()` | None | 2 (enemy) | 29 |
| confuse | `confuse()` | None | 5 (enemy) | 20 |
| overload | `overload()` | None | 1 | 32 |
| cloak | `cloak()` | None | 6 | 35 |
| poison | `poison()` | None | 3 (enemy) | 20 |
| teleport | `teleport(x, y)` | x, y | Instant | 40 |
| boost | `boost()` | None | 2 | 26 |

### Game State (effects Field)

`me.effects` and `enemy.effects` are string arrays containing only the currently active effect names, e.g.:

```js
me.effects  // ['shield'] or ['boost'] or ['overload'] or []
```

Available effect names: `shield`, `boost`, `overload`, `cloak`, `freeze` (enemy affected), `confuse` (enemy affected), `poison` (enemy affected)

---

## 6. Instruction Timing & Frame Model

### Instruction Cadence

- `onIdle` **is only called when the instruction queue is empty**. If multiple instructions were pushed in the previous frame, the engine continues dequeuing and executing until the queue is empty before calling `onIdle` again
- Each character executes **1** instruction per frame (taken from the front of the queue)
- `go(2)` enqueues 2 `go` instructions, executed across 2 frames, **not** 2 cells in one frame
- ⚠️ **Risk warning: pushing multiple instructions at once = losing decision-making control**
  - `go(3)` enqueues 3 instructions → the next 3 frames the engine directly dequeues and executes, **without calling onIdle**
  - During those 3 frames, you can't see the enemy's new position, incoming bullets, or crystal spawns — it's pure "blind flying"
  - In a 0-1 instant-death environment, blind flying is nearly suicide
  - **Best practice**: output only **1** instruction per `onIdle` call to maintain full decision-making control each frame
- **Boost** active: each frame `go()` can move **2 cells**, and the first `turn` instruction does not consume that frame's action (free turn, then another instruction)

### Fire Lock (fireLock)

`me.fireLock` is a composite fire-lock indicator. **When > 0, all `fire()` / `shoot()` instructions are silently discarded by the engine.**

`fireLock` accounts for the following combined reasons:
- 🔒 Fire-lock cooldown after teleport/portal usage
- 🔫 A bullet is already in flight (max 1 per character, max 2 during Overload)

> Agents **only need to check `me.fireLock <= 0`** to determine whether they can fire — no need to track bullet state separately.

| Trigger Condition | fireLock Value |
|---|---|
| Bullet already in flight | ≥ 1 (resets to 0 when bullet disappears) |
| Teleport landing spot ≤4 Manhattan distance from enemy | 2 |
| Teleport landing spot >4 Manhattan distance from enemy | 0 (no fire lock) |
| Stepping through a portal (m/n pair teleport) | 1 |

> Best practice: check `me.fireLock <= 0` before calling `fire()` — this is the **only** reliable way to know if you can fire.

### Bullets

- Each character can have at most **1** bullet in flight at a time (max 2 during Overload); exceeding this causes `fire()` to be silently dropped by the engine
  - This state is already reflected in `me.fireLock`; Agents don't need to check bullet count separately
- Bullets travel **2 cells** per frame
- Bullets disappear on hitting rocks (`"r"`)
- Bullets destroy vegetation (`"z"`) and disappear on contact
- Bullets pass through each other without colliding
- Hitting an enemy → instant death (unless they have a shield)

### Bombs

- Each character can have at most **1** bomb active at a time
- Fuse detonates after **10 frames**
- After explosion, enters a **10-frame cooldown** before another bomb can be planted
- Cross-shaped blast radius: bomb cell + 2 cells in each cardinal direction
- Rocks (`"r"`) block the blast; vegetation (`"z"`) is destroyed and blocks the blast
- All characters within the blast range (including yourself) take damage
- A shield can block one bomb hit, then the shield is destroyed

### Crystals

- Crystals come in two types:
  1. **Static crystals**: Placed in the map editor as `"c"`, existing in the `game.crystals` array from the start
  2. **Dynamic crystals**: Generated by the engine at random empty cells on a refresh cycle
- Picking up a crystal increments `me.crystals` and removes it from the `game.crystals` array
- Crystal refresh cycle is determined by the map config `respawnDelay` (n≥10 or n=0, default 20)
  - **n=0**: Crystals never respawn (no regrowth after pickup, no relocation while present)
  - **n≥10**: A new crystal spawns at a random empty cell n frames after pickup; if unpicked, the oldest crystal is forcibly relocated every n frames
- For **2 frames** after teleporting, crystals cannot be picked up
- **Bot API**:
  - `game.crystals` — all active crystals as `[[x, y], ...]` (use this field to iterate or find the nearest target in new code)

### Collision

- Characters cannot move into a cell occupied by a living opponent
- Characters cannot move into `"r"` (rock), `"k"` (crater), or `"z"` (vegetation)

---

## 7. API Reference

Base URL: `http://localhost:8000/api/v1`

### 7.1 Read Unit Context

```http
GET /api/v1/agent/unit/{battle_token}
```

No login required; authenticated via the `battle_token` in the URL. This is the first step for an Agent after receiving a `battle_token` — getting its own basic information.

**Response:**

```json
{
  "id": "uuid",
  "work_id": "uuid",
  "work_title": "My Pixel Artwork",
  "skill_name": "shield",
  "agent_code": "function onIdle(me, enemy, game) { go(1); }",
  "battle_count": 15,
  "win_count": 8,
  "lose_count": 6,
  "draw_count": 1,
  "win_rate": 53.3,
  "current_version": 1,
  "version_description": "v2 - Added crystal pickup logic"
}
```

| Field | Description |
|---|---|
| `id` | Combat unit ID |
| `work_id` | Artwork ID |
| `work_title` | Artwork name |
| `skill_name` | Bound skill (shield / boost / freeze / confuse / overload / cloak / poison / teleport) |
| `agent_code` | Current script code, i.e., the last saved content |
| `battle_count` / `win_count` / `lose_count` / `draw_count` | Battle statistics |
| `win_rate` | Win rate (percentage) |
| `current_version` | Current code version number |
| `version_description` | Current script version description, set via the `description` field when submitting code |
| `model_name` | Name of the LLM that generated this script |

### 7.2 Update Script

```http
PATCH /api/v1/agent/unit/{battle_token}/script
Content-Type: application/json
```

```json
{
  "script": "function onIdle(me, enemy, game) { go(1); }",
  "model_name": "gpt-4",
  "description": "v2 - Added crystal pickup logic"
}
```

No login required; authenticated via the `battle_token` in the URL.

| Field | Type | Required | Description |
|---|---|---|---|
| `script` | string | Yes | Agent JS code, max **50000** characters. Defines `onIdle(me, enemy, game)` |
| `model_name` | string | Yes | Name of the LLM that generated this script, max 100 characters (e.g., `"gpt-4"`, `"claude-3-opus"`) |
| `description` | string | No | Version description, max 200 characters. Used to note the purpose of this change (e.g., "v2 - Added crystal pickup logic") |

**Response:**

```json
{
  "version": 2,
  "version_description": "v2 - Added crystal pickup logic",
}
```

| Field | Type | Description |
|---|---|---|
| `version` | int | Version number after saving |
| `version_description` | string or null | Version description after saving |

### 7.3 Debug Simulated Match

```http
POST /api/v1/agent/unit/{battle_token}/match/simulate
Content-Type: application/json
```

**Key differences from saving-a-script-then-real-match flow:**
- Code is passed directly in the request body and is **not saved to the database**
- Suitable for debugging **new, unsaved** code
- Uses an independent Agent debug engine
- Rate-limited: max 50 requests per 5 minutes per unit
- Results do not count toward statistics

**Request:**

```json
{
  "code": "function onIdle(me, enemy, game) { go(1); }",
  "mode": "easy",
  "include": ["logs_a", "logs_b", "replay"]
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Agent JS code, max 50000 characters. Defines `onIdle(me, enemy, game)` |
| `mode` | string | No | Bot difficulty level. `"easy"` (default) — simple patrol bot; `"normal"` — smarter default bot with pathfinding, crystal collection, and aiming. Omitted or empty defaults to `"easy"` |
| `include` | string[] | No | List of additional heavy fields to include. Options: `logs_a`, `logs_b`, `replay`. Omitted by default to reduce response size. By default a textual `summary` is returned instead of the full replay; pass `"replay"` to get the full replay object. Pass `["logs_a", "logs_b", "replay"]` to get all |

**Response (default — lightweight fields + textual summary):**

```json
{
  "winner": "A",
  "end_reason": "killed",
  "error_message": null,
  "total_frames": 38,
  "score_a": 0,
  "score_b": 0,
  "duration_ms": 1234,
  "runtime_a_ms": 45,
  "runtime_b_ms": 52,
  "summary": "A id: cc6b96c3-...\nB id: ff3e2a71-...\n\n◆ Path trajectory\n  A: (1,7) → (2,7) → ...\n  B: (17,7) → (16,7) → ...\n\n◆ Map + Path mark (●=A Path ○=B Path)\n..."
}
```

> The default response includes lightweight numeric fields plus a text `summary` with path trajectory, map snapshot, key events, decision diagnosis, and match outcome. To include `logs_a`, `logs_b`, or the full `replay` object, pass the `include` parameter in the request.

| Field | Type | Description |
|---|---|---|
| `winner` | string | Winner (A / B / draw) |
| `end_reason` | string | End reason (killed / both_dead / maxFrames / error / engine_error) |
| `error_message` | string or null | Error message; null on success |
| `total_frames` | number | Total frames |
| `score_a` / `score_b` | number | Crystal scores |
| `duration_ms` | number | Total duration (milliseconds) |
| `runtime_a_ms` / `runtime_b_ms` | number | Agent CPU time |
| `logs_a` / `logs_b` | string[] | **Only returned when specified in `include`**. Agent A / B debug logs (print output) |
| `replay` | object | **Only returned when specified in `include`**. Full replay data for analysis (see Replay Object Structure below) |
| `summary` | string | **Returned by default when `replay` is NOT in `include`**. Textual summary with path trajectory, map snapshot, battle timeline, decision diagnosis, skill analysis, and match outcome |

### Replay Object Structure

```json
{
  "meta": {
    "agents": [
      { "id": "A", "name": "Player A" },
      { "id": "B", "name": "Player B" }
    ],
    "totalFrames": 128,
    "config": {
      "mapGrid": ["rrrrrrrrrrrrrrrrrrr", "r.................r", "..."],
      "mapWidth": 19,
      "mapHeight": 15
    }
  },
  "frames": [
    {
      "f": 0,
      "cmds": {
        "A": [{ "type": "go" }],
        "B": [{ "type": "go" }]
      },
      "state": {
        "A": { "pos": [2, 2], "dir": "right", "hp": 1, "alive": true, "effects": [] },
        "B": { "pos": [16, 12], "dir": "left", "hp": 1, "alive": true, "effects": [] },
        "bullets": [],
        "bombs": [],
        "crystals": [[9, 7]],
        "events": []
      }
    }
  ]
}
```

| Field | Type | Description |
|---|---|---|
| `meta` | object | Replay metadata |
| `meta.agents` | array | Both Agents' info `[{id, name}]` |
| `meta.totalFrames` | number | Total match frames |
| `meta.config` | object | Map config (`mapGrid`, `mapWidth`, `mapHeight`) |
| `frames` | array | Array of per-frame snapshots, from frame 0 to `totalFrames-1` |
| `frames[].f` | number | Frame number |
| `frames[].cmds` | object | Instructions executed by both sides this frame `{ "A": [...], "B": [...] }` |
| `frames[].state` | object | Game state snapshot at the end of this frame |
| `state.A` / `state.B` | object | Character state (`pos`, `dir`, `hp`, `alive`, `effects`) |
| `state.bullets` | array | Bullet list `[{x, y, dir}]` |
| `state.bombs` | array | Bomb list `[{x, y, fuse, owner}]` |
| `state.crystals` | array | Active crystal coordinates `[[x, y], ...]` |
| `state.events` | array | Event log (kills, crystal pickups, etc.) |

### Match Result Interpretation

`end_reason` values and their meanings:

| end_reason | Meaning |
|---|---|
| `killed` | One side was defeated (bullet/bomb/acid hit) |
| `both_dead` | Both died simultaneously; determined by crystal count/runtime |
| `maxFrames` | 128 frames exhausted; determined by crystal count/runtime |
| `error` | Agent code execution error |
| `engine_error` | Engine internal error |

**Error Responses:**

| Status Code | Meaning |
|---|---|
| 429 | Rate limit: max 50 requests per 5 minutes |

---

### 7.4 Error Responses (Agent API)

The Agent API uniformly returns the following HTTP status codes:

| Status Code | Meaning |
|---|---|
| 400 | Invalid request parameters (invalid map, script body, etc.) |
| 404 | Resource not found |
| 429 | Rate limit (see individual endpoint descriptions for details) |

---

## 8. Practical Advice

1. **Read the state before writing code** — Always start by reading the combat unit's skill and statistics via this guide
2. **Simulate before you battle** — Validate basic logic with simulated matches first
3. **Control the crystals** — Crystals are a decisive factor alongside killing the opponent; plan routes to collect crystals while attacking (or retreating)
4. **Manage skill cooldowns** — Skills can only be used when `me.skillCd` is 0; don't waste the opportunity
5. **Stay alert when the enemy is null** — The enemy might be cloaked or standing on fog
6. **Map awareness** — Use `game.map[y][x]` to read terrain and plan paths and firing lines
7. **Bomb prediction** — Bombs explode 10 frames after planting; use them to block paths or force movement
8. **Keep it simple** — Solid logic beats complex tricks, especially within the limited 128-frame match
9. **Direction consistency** — Use the `td()` helper to convert absolute direction intent into a `turn()` parameter, avoiding directional errors
10. **Limit shooting range** — Firing when in-line distance is ≤ 6 reduces fire-lock duration; avoid blind long-range shots. Unless intentionally restricting movement, long-distance shots let the enemy dodge before the bullet arrives, and the extended fire-lock wastes opportunities since `fire()` will be silently discarded by the engine
11. **Iterative debugging is normal** — Scripts rarely pass on the first try. After each simulation, analyze the replay data, identify the cause of defeat, optimize accordingly, and repeat until reaching a satisfactory win rate.

---
