Building Ceasefire with an AI Agent

  • The prompts
  • How it was built
    • 1. Read the guide
    • 2. Pick the fleet and write version 1
    • 3. First game, first surprise
    • 4. The bug that mattered
    • 5. Version 2, and a TypeScript trap
    • 6. Prove the cooldowns keep counting
    • 7. Numbers, a tuning call and the final version
  • Mistakes and fixes
    • Turn number isn't round number
    • A one-word TypeScript trap
    • A log too short to read
    • Fair isn't the same as fun
  • Results
  • Screenshots
  • The final mod
  • Try it yourself

In Ceasefire, every third round is quiet: nobody fires, but abilities keep charging, so the round after a ceasefire is when the big ships come out. The fleet is the Kayak, Medium Galleon, Sailboat and Raft.

The player's request:

Make a BattleTabs mod called Ceasefire: every third round, both sides hold fire, but ability cooldowns keep counting down.

It took 25 calls to the mod tool, three versions and one typecheck error. We've kept in both places it went wrong, including a bug that only showed up in the full game log.

The prompts

All in one go: Paste this into your agent with the battletabs-mod tool connected, and it runs the whole loop by itself: build, test, tune.

Make a BattleTabs mod called Ceasefire: every third round, both sides hold fire, but ability cooldowns keep counting down.
Read the battletabs-mod ship-mods docs first, and only use ships that are allowed in the Arcade.
Typecheck it, then play a few test games on different seeds and check the rule works.
Run 500 bot games and compare the win split and game length with normal rules. If one side wins much more often, or games run much longer, change it and test again.
Save it as case-ceasefire, then tell me what you changed along the way and why.

Or step by step, as we did it: Going one prompt at a time lets you look at each result and steer, the way the decisions on this page were made. Some prompts react to what came back, so read the result before you send the next one.

  1. Make a BattleTabs mod called Ceasefire: every third round, both sides hold fire, but ability cooldowns keep counting down.
  2. Use four ships that have real cooldowns and are allowed in the Arcade. Typecheck it.
  3. Play a game with bots and show me the log.
  4. On a ceasefire turn, try to use the Kayak. Does it still fire? Show me the whole log.
  5. Combos like the Galleon's double shot are throwing the count off. Count each player's turns instead, so both sides always hold fire in the same round.
  6. Prove the cooldowns really keep counting down during a ceasefire. Use a ship with a long cooldown and check when it's ready.
  7. Run 200 bot games and tell me the win split and how long games are compared with normal rules.
  8. Players shouldn't have to click during a ceasefire. Just skip both turns automatically, but keep the cooldowns counting.
  9. Play a couple of seeds again, including one where the other side moves first.
  10. Run 500 bot games on the final version and give me the win split, game length and any errors.
  11. Save a picture of the board just after a ceasefire, and save the mod as case-ceasefire.

How it was built

1. Read the guide

Prompt 1

Make a BattleTabs mod called Ceasefire: every third round, both sides hold fire, but ability cooldowns keep counting down.
What the agent did
Read the ship-mods guide, looking for a way to stop a player firing.
Tool calls
get_docs (ship-mods, then examples/second-wind)
What came back
Two ways. An onAttack hook that returns "cancel": the player takes their turn, but the shot does nothing ("A cancelled attack is not a skip"). Or ctx.skipNextTurn(seat): the turn is skipped, and "the skipped seat's cooldowns still tick".
What changed
Started with "cancel", the most literal reading of "hold fire".

2. Pick the fleet and write version 1

Prompt 2

Use four ships that have real cooldowns and are allowed in the Arcade. Typecheck it.
What the agent did
Picked four Arcade ships with cooldowns worth waiting for: the Kayak (2), Medium Galleon (3), Sailboat (4) and Raft (5). Version 1 worked out the round from the turn number, floor(ctx.turn / 2) + 1, and cancelled shots in rounds 3, 6, 9 and so on.
Tool calls
list_ships (arcade_only), save_mod, typecheck
What came back
"No type errors, and a match starts." Clean the first time.
What changed
Version 1 saved as case-ceasefire.

3. First game, first surprise

Prompt 3

Play a game with bots and show me the log.
What the agent did
Played one bot game and read the log.
Tool calls
play_once (seed 1)
What came back
Seat 1 won after 34 turns, and turns 4 and 5 were silent, as planned. But seat 1 moved on turn 6 and turn 7. The Medium Galleon "attacks twice", and each shot uses up a turn number.
What changed
Nothing yet, but a suspicion: ctx.turn counts actions, not player turns, so the round sum will drift.

4. The bug that mattered

Prompt 4

On a ceasefire turn, try to use the Kayak. Does it still fire? Show me the whole log.
What the agent did
Scripted a Kayak shot on a ceasefire turn. The first try printed only 12 log lines and cut off the part that mattered, so it ran again with 60.
Tool calls
play_once (seed 2, with a scripted Kayak shot, run twice)
What came back
The Kayak shot left no mark, so abilities are blocked too. But a Galleon fired on turn 15, and its second shot fell on turn 16, which the formula called a ceasefire. That shot was silently cancelled, and only the other player sat out turn 17. The same thing happened at turns 22/23, 34/35 and 40/41.
What changed
"Both sides hold fire" wasn't true whenever a combo shifted the count. The fix: count each player's own turns in onTurnStart, which runs once per player turn, not once per shot.

5. Version 2, and a TypeScript trap

Prompt 5

Combos like the Galleon's double shot are throwing the count off. Count each player's turns instead, so both sides always hold fire in the same round.
What the agent did
Rewrote the count per player, with a holdFire: false flag in the mod's data.
Tool calls
save_mod, typecheck (twice), play_once (seed 2)
What came back
"Type 'boolean' is not assignable to type 'false'." After the fix, typecheck was clean, and the seed 2 game showed ceasefire turns in pairs, 6 for each side, with both Galleon shots landing.
What changed
holdFire: false as boolean fixed the type. Both sides now always hold fire in the same round.

6. Prove the cooldowns keep counting

Prompt 6

Prove the cooldowns really keep counting down during a ceasefire. Use a ship with a long cooldown and check when it's ready.
What the agent did
There's no cooldown counter in the text view, so the agent used a trick: render_state plays scripted moves and refuses an illegal one, listing what is legal.
Tool calls
render_state (three scripted runs, then a png)
What came back
The Sailboat (cooldown 4) fired on its owner's 4th turn, straight after a ceasefire. A control run on the 2nd turn was refused, and only Kayak abilities were legal.
What changed
Proved: the ceasefire turn counted toward the Sailboat's cooldown.

7. Numbers, a tuning call and the final version

Prompts 7 to 11

Run 200 bot games and tell me the win split and how long games are compared with normal rules.
Players shouldn't have to click during a ceasefire. Just skip both turns automatically, but keep the cooldowns counting.
Play a couple of seeds again, including one where the other side moves first.
Run 500 bot games on the final version and give me the win split, game length and any errors.
Save a picture of the board just after a ceasefire, and save the mod as case-ceasefire.
What the agent did
Ran 200 bot games on version 2, then switched to ctx.skipNextTurn: when a player starts their 2nd, 5th or 8th turn, their next one is skipped, and a "ceasefire next round" message goes up once both sides are set.
Tool calls
simulate_n (200 games), save_mod, typecheck, play_once (seeds 2 and 7), render_state, simulate_n (500 games), render_state
What came back
Version 2 was fair (107 / 93) with no errors, but games ran 40.2 turns against 29.6, and the extra length was dead turns. On version 3, seed 2 gave the same moves and the same winner as before, and seed 7 (the other side moves first) skipped in pairs too. 500 games: 254 / 246, 28.7 turns played plus 11.2 skipped, against 29.2.
What changed
Version 3 is the final mod. The rule didn't change, only how it feels to play.

Mistakes and fixes

Turn number isn't round number

Combo ships take extra turn numbers: the Medium Galleon's double shot uses two. Working rounds out from ctx.turn let a ceasefire land in the middle of a combo, so one player held fire alone.

Fix: Count each player's own turns in onTurnStart, which runs once per player turn.

A one-word TypeScript trap

The mod's data started as holdFire: false, and TypeScript decided the flag's type was "always false", so setting it to true failed typecheck.

Fix: Write holdFire: false as boolean. A data object's starting values become its types.

A log too short to read

The first scripted test printed only 12 log lines and cut off the part that showed the bug.

Fix: Run it again with 60 lines. The bug was in the full log.

Fair isn't the same as fun

Version 2 was balanced, but games were 36% longer, and on a ceasefire turn a player had to pick a target and press fire, then watch the shot quietly disappear.

Fix: Skip the turn with ctx.skipNextTurn instead of cancelling the shot. Same rule, same winners on the same seeds, no dead clicks.

Results

500 bot games on the final version, seeds 1 to 500, against normal rules on the same fleets and seeds.

CeasefireNormal rules
Wins, seat 0 / seat 1254 / 246 (51% / 49%)250 / 250 (50% / 50%)
First mover won284 (57%)314 (63%)
Turns played per game28.729.2
Ceasefire turns (skipped instantly)11.20
Raft ability uses per game1.10.8
Sailboat ability uses per game1.51.3
Draws / errors0 / 00 / 0
  • Seat balance is within chance (±6.2 points at 500 games).
  • Played turns are about the same as normal rules (0.5 shorter, within the 0.6 noise). The ceasefire turns are skipped instantly.
  • The slow ships get more use, because every ceasefire hands them free charge.
  • The first player's edge fell from 63% to 57%. That's near the noise line, so treat it as a hint, not a result, until a bigger test.

Every version along the way:

VersionChangeResultKept?
v1Cancel shots, rounds worked out from ctx.turnCombos broke the countNo
v2Cancel shots, each player's turns counted107 / 93, 40.2 turns (normal 29.6), 200 gamesNo, dead turns
v3Skip the turn instead of cancelling the shot254 / 246, 28.7 turns played (normal 29.2), 500 gamesFinal

Screenshots

TODO: in-game screenshot of this game in the BattleTabs Arcade.

The mod tool's render_state draws the boards as 2D pictures. Dark red cells are a sunk ship.

Four 7x7 boards from the mod tool after the first ceasefire round in Ceasefire
After the first ceasefire (seed 2): the cancelled shot at (0,6) left no mark, and the Sailboat's 1x3 lands on seat 0's very next turn.
Four 7x7 boards from the mod tool just after the first ceasefire in the final version
The final version, seed 3, just after the first ceasefire: the Kayak, Galleon and Sailboat are ready again, and the Raft (cooldown 5) isn't yet.

The final mod

The whole mod, as saved by save_mod. Copy it into your own mods folder to start from it.

import { defineShipMod } from "@battletabs/mod-api";

// Ceasefire: every third round, both sides hold fire, but ability cooldowns keep counting down.
// A round is one turn for each seat. Each seat counts its own turns. When a seat starts its 2nd,
// 5th, 8th ... turn, its NEXT turn (the 3rd, 6th, 9th ...) is skipped. A skipped turn still ticks
// the seat's cooldowns, so abilities keep charging while nobody fires, and nobody has to click
// through a dead turn.
export default defineShipMod({
  id: "case-ceasefire",
  apiVersion: 1,
  classicVersion: "classic-2",
  rules: {
    pitch: "Every third round the guns fall silent, but your abilities keep charging.",
    bullets: [
      "Sink every enemy ship to win",
      "Every third round is a ceasefire: both sides skip their turn",
      "Ability cooldowns keep counting down during a ceasefire",
    ],
  },
  maxTurns: 200,
  fleet: ["kayak", "galleonmedium", "sailboat", "raft"],
  data: { turnsTaken: [0, 0], ceasefires: 0 },
  hooks: {
    onTurnStart(start, ctx) {
      const seat = start.seat;
      ctx.data.turnsTaken[seat] += 1;
      if ((ctx.data.turnsTaken[seat] + 1) % 3 === 0) {
        ctx.skipNextTurn(seat);
        ctx.data.turnsTaken[seat] += 1; // the skipped turn will not start, so count it now
        if (ctx.data.turnsTaken[0] === ctx.data.turnsTaken[1]) {
          ctx.data.ceasefires += 1;
          ctx.emit({ type: "show-message", audience: "all", template: "ceasefire-next-round" });
        }
      }
    },
  },
});

Try it yourself

Install the mod tools and connect your agent with the Arcade guide. Then paste the prompts above into your agent, all in one go or one at a time. To play against the bot yourself, ask your agent to open the play page.

Play Ceasefire in the Arcade

Previous case study: Blood Rush · Next case study: Glass Cannon · All case studies