Building First to Three with an AI Agent

  • The prompts
  • How it was built
    • 1. Read the guide
    • 2. Pick the fleet
    • 3. Typecheck, and the first mistake
    • 4. Watch some games
    • 5. Is it fair? Is it too long?
    • 6. Make it feel like a race
    • 7. Double-check the balance
  • Mistakes and fixes
    • A guessed API shape
    • A ship that never sank anything
    • Proving the rule fired
  • Results
  • Screenshots
  • The final mod
  • Try it yourself

Normal BattleTabs ends when one fleet is gone, so the last few turns are often a slow hunt for the final, best-hidden ship. First to Three turns it into a race: the first captain to sink three enemy ships wins. The fleet is the Kayak, Sailboat, Raft and Mini Sub.

The player's request:

Make a BattleTabs mod called First to Three: it's a race, the first player to sink 3 enemy ships wins.

We built it from scratch with an AI agent and the public mod tool, starting from the tool's own docs and no game code. It took 18 calls to the mod tool, two versions and one typecheck error, and no balance tuning.

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 First to Three: it's a race, the first player to sink 3 enemy ships wins.
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-first-to-three, 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 First to Three: it's a race, the first player to sink 3 enemy ships wins.
  2. Use only ships I could publish to the Arcade. Keep it simple: a Kayak, a Sailboat, a Raft and a Mini Sub.
  3. Typecheck it and fix any errors.
  4. Play one game on seeds 1, 2 and 3 and show me who won, how many turns it took and how many ships each side sank.
  5. Run 200 bot games and tell me the win split and how long games last compared with normal BattleTabs.
  6. When someone is one ship away from winning, warn both players. Add it to the rules card too.
  7. Run 500 games to double-check the balance.
  8. Show me a picture of the board when someone has sunk 2 ships.
  9. Save it as case-first-to-three.

How it was built

1. Read the guide

Prompt 1

Make a BattleTabs mod called First to Three: it's a race, the first player to sink 3 enemy ships wins.
What the agent did
Read the ship-mods guide.
Tool calls
get_docs (ship-mods)
What came back
A ship mod keeps the real Battleship rules: a 7x7 board and 4 ships each. There's an onSink hook, and ctx has end(outcome). The guide never shows what an outcome looks like.
What changed
No new game needed: one onSink hook that counts each player's sinks and ends the match at three.

2. Pick the fleet

Prompt 2

Use only ships I could publish to the Arcade. Keep it simple: a Kayak, a Sailboat, a Raft and a Mini Sub.
What the agent did
Checked which ships can be published.
Tool calls
list_ships (arcade_only)
What came back
22 of the 35 ships are allowed in the Arcade. Shop and promo ships work locally but are refused at publish.
What changed
The fleet: Kayak (2 tiles), Sailboat (3), Raft (4) and Mini Sub (2, sonar). All Arcade-safe.

3. Typecheck, and the first mistake

Prompt 3

Typecheck it and fix any errors.
What the agent did
The first draft ended the game with a guess: ctx.end({ winner: sink.by }).
Tool calls
save_mod, typecheck, get_docs (types), save_mod, typecheck
What came back
"'winner' does not exist in type 'Outcome'." The type reference showed the real shape: Outcome = { winners: Seat[] } | { draw: true }. It's a list because the same API also serves team games.
What changed
ctx.end({ winners: [sink.by] }), plus a "three ships sunk" message and an endedByRace counter. Typecheck passed, including the publish text rules.

4. Watch some games

Prompt 4

Play one game on seeds 1, 2 and 3 and show me who won, how many turns it took and how many ships each side sank.
What the agent did
Played three bot games.
Tool calls
play_once (seeds 1, 2 and 3), render_state
What came back
Games ended in 17, 27 and 22 turns. Each time the winner had sunk exactly 3 and the loser still had a ship afloat, and endedByRace was 1.
What changed
Nothing. The counter proved the race rule ended every game, not the normal "sink everything" rule.

5. Is it fair? Is it too long?

Prompt 5

Run 200 bot games and tell me the win split and how long games last compared with normal BattleTabs.
What the agent did
Played the mod and normal rules on the same seeds.
Tool calls
simulate_n (200 games)
What came back
93 / 107, identical to normal BattleTabs on those seeds. 19.5 turns against 27.9, about 30% shorter. No draws, no errors.
What changed
Nothing needed tuning. That's a result too: the rule is fair and cuts out the slow ending, which is what a race should do.

6. Make it feel like a race

Prompt 6

When someone is one ship away from winning, warn both players. Add it to the rules card too.
What the agent did
When a captain sinks their second ship, both players now see "one ship from victory". A third rules bullet explains it.
Tool calls
save_mod, typecheck, play_once (seed 4)
What came back
Seat 0 won after 19 turns, with {"sunk":[3,2],"matchPoints":2,"endedByRace":1}. The warning fired on turn 16 and the win came on turn 19. matchPoints was 2 because both players reached match point, one warning each.
What changed
Bots don't feel tension, but people do. Version 2 is the final mod.

7. Double-check the balance

Prompts 7 to 9

Run 500 games to double-check the balance.
Show me a picture of the board when someone has sunk 2 ships.
Save it as case-first-to-three.
What the agent did
Ran 500 bot games, then saved two pictures of seed 4: the start, and the moment seat 0 has sunk two ships.
Tool calls
simulate_n (500 games), render_state (twice)
What came back
248 / 252, 19.9 turns against 28.0, no draws or errors.
What changed
Nothing. The warning changes nothing the bots measure, so the numbers match version 1.

Mistakes and fixes

A guessed API shape

The guide doesn't show what ctx.end takes, so the first draft guessed { winner: sink.by }. Typecheck caught it before any game ran.

Fix: When a guess fails, read the types instead of guessing again. get_docs (types) is one call.

A ship that never sank anything

The Mini Sub's sonar sank nothing in 500 bot games, in either mode. That comes from the normal ship, not from this mod.

Fix: Compare against normal play before you blame your own rule. The ship was left alone.

Proving the rule fired

A finished game doesn't tell you which rule ended it.

Fix: Put a counter in ctx.data for every hook path. endedByRace: 1 proved the race rule ended the game, and matchPoints: 2 showed the warning fired once per player.

Results

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

First to ThreeNormal rules
Wins, seat 0 / seat 1248 / 252256 / 244
First mover won268268
Average turns played19.928.0
Draws / errors0 / 00 / 0
  • Seat balance is within chance (noise about 6.2 points at 500 games).
  • Games are 8.1 turns shorter, well above the noise of about 0.7.
  • All 500 games played out differently from normal rules.

Every version along the way:

VersionChangeResultKept?
v1First to sink 3 ships wins93 / 107, 19.5 turns (normal 27.9), 200 gamesYes
v2Adds the match-point warning248 / 252, 19.9 turns (normal 28.0), 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 showing both First to Three fleets at the start of a game
The first version, seed 1: both fleets placed.
Two 7x7 boards from the mod tool showing what seat 0 sees at the start of a game
The final version, seed 4: what seat 0 sees at the start.
Two 7x7 boards from the mod tool after seat 0 has sunk two enemy ships
Seed 4 after 9 moves: seat 0 has sunk the Sailboat and the Kayak (dark red) and is one ship from winning. This is the moment the warning fires.

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";

// First to Three: a race. The first captain to sink 3 enemy ships wins,
// so you never have to hunt down the last, best-hidden ship.
export default defineShipMod({
  id: "case-first-to-three",
  apiVersion: 1,
  classicVersion: "classic-2",
  rules: {
    pitch: "A race: the first captain to sink 3 enemy ships wins.",
    bullets: [
      "Sink 3 enemy ships to win",
      "You do not need to sink the whole fleet",
      "Both captains are warned when someone is one ship away",
    ],
  },
  maxTurns: 200,
  fleet: ["kayak", "sailboat", "raft", "subsmall"],
  ships: [],
  // sunk[seat] = enemy ships that seat has sunk. matchPoints counts the warning toast.
  data: { sunk: [0, 0], matchPoints: 0, endedByRace: 0 },
  hooks: {
    onSink(sink, ctx) {
      ctx.data.sunk[sink.by] += 1;
      const count = ctx.data.sunk[sink.by];
      if (count >= 3) {
        ctx.data.endedByRace += 1;
        ctx.emit({ type: "show-message", audience: "all", template: "three-ships-sunk" });
        ctx.end({ winners: [sink.by] });
      } else if (count === 2) {
        ctx.data.matchPoints += 1;
        ctx.emit({ type: "show-message", audience: "all", template: "one-ship-from-victory" });
      }
    },
  },
});

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 First to Three in the Arcade

Previous case study: Glass Cannon ยท All case studies