Building Glass Cannon with an AI Agent

  • The prompts
  • How it was built
    • 1. Read before you build
    • 2. Pick the ships
    • 3. A test version that only counts
    • 4. Numbers for the reload half
    • 5. The closest rule that works
    • 6. Let the numbers argue
    • 7. Tune 3 to 4
  • Mistakes and fixes
    • The idea needed something the API can't see
    • Bots look different to your hooks
    • A list that could only hold false
    • Almost tuning on noise
    • The real problem was in another column
  • Results
  • Screenshots
  • The final mod
  • Try it yourself

Half of this idea took one line of code. The other half turned out to be impossible: a mod can't see a miss. So Glass Cannon became the closest rule that works: every ability reloads in 1 turn, but a long cold streak overheats your cannon and costs you an attack. The fleet is the Kayak, Sailboat, Raft and Whale.

The player's request:

Make a BattleTabs mod called Glass Cannon: every ability reloads in 1 turn, but if you miss, your next attack is lost.

It took 28 calls to the mod tool, three versions and one typecheck error. Here's how we found out early that half the idea couldn't be built, and what we built instead.

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 Glass Cannon: every ability reloads in 1 turn, but if you miss, your next attack is lost.
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-glass-cannon, 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 Glass Cannon: every ability reloads in 1 turn, but if you miss, your next attack is lost.
  2. Before you write it, read the ship-mods docs and the types. Can a hook tell whether a shot hit or missed?
  3. Pick 4 Arcade ships with a normal attack ability. Leave out anything that would be silly every turn, like the catamaran.
  4. Make a test version first: every ability reloads in 1 turn, and count what the attack hook sees in ctx.data. Play one game, then one where I script a normal attack and an ability.
  5. Run 200 bot games and tell me the win split and how long games are compared with normal.
  6. So misses can't be detected. What's the closest rule that works the same for me and the bot? Give me a few options and the trade-off of each.
  7. Go with the overheat: if you fire 3 shots in a row without sinking a ship, your next attack is lost, and sinking a ship resets it. Show a message when it happens.
  8. Fix the type error.
  9. Play games on seeds 1, 4 and 7 and show me how often the overheat fired.
  10. Run 200 bot games and tell me the win split.
  11. Seat 0 looks behind. Is that real? Run 500.
  12. Games are no faster than normal now. Make it more forgiving: 4 shots instead of 3. Run 500 games again and compare.
  13. Save it as case-glass-cannon and show me a picture of the board after each side fires the raft twice in a row.

How it was built

1. Read before you build

Prompts 1 and 2

Make a BattleTabs mod called Glass Cannon: every ability reloads in 1 turn, but if you miss, your next attack is lost.
Before you write it, read the ship-mods docs and the types. Can a hook tell whether a shot hit or missed?
What the agent did
Read the ship-mods guide, then the full type reference, before writing any code.
Tool calls
get_docs (ship-mods, then types)
What came back
cooldown: 1 makes the first half easy. But onAttack runs before the shot lands, and nothing reports the result afterwards. The only after-the-shot event is onSink, when a whole ship goes down. On a bot's turn, the hook only knows the attack's kind is "bot".
What changed
A mod can't see a miss. The agent decided to prove it with a test version before redesigning.

2. Pick the ships

Prompt 3

Pick 4 Arcade ships with a normal attack ability. Leave out anything that would be silly every turn, like the catamaran.
What the agent did
Listed the 22 Arcade ships.
Tool calls
list_ships (arcade_only)
What came back
The Kayak (2 tiles, 1x2 attack, cooldown 2), Sailboat (3 tiles, 1x3, cooldown 4), Raft (4 tiles, 2x2, cooldown 5), Whale (5 tiles, cooldown 3, attacks reveal adjacent tiles), the Catamaran (cooldown 8, a 7x1 line) and the passive Crab.
What changed
The fleet: Kayak, Sailboat, Raft and Whale, four plain shape attacks. The Catamaran was left out (a 7-cell line every turn would end games in a handful of shots), and so was the Crab, which has nothing to reload.

3. A test version that only counts

Prompt 4

Make a test version first: every ability reloads in 1 turn, and count what the attack hook sees in ctx.data. Play one game, then one where I script a normal attack and an ability.
What the agent did
Set every cooldown to 1 and counted each kind of attack in ctx.data.
Tool calls
save_mod, typecheck, play_once (seed 1, then seed 2 with 3 scripted moves)
What came back
The bot game gave {"attacks":0,"abilities":0,"bots":27,"sinks":6}: all 27 attacks said "bot". The game with scripted moves gave {"attacks":2,"abilities":1,"bots":28,"sinks":7}.
What changed
A rule like "punish ability shots" would hit people and never the bot, and the bot simulator would never show it. Even for a person, nothing says hit or miss.

4. Numbers for the reload half

Prompt 5

Run 200 bot games and tell me the win split and how long games are compared with normal.
What the agent did
Ran the reload-only version against normal rules.
Tool calls
simulate_n (200 games)
What came back
94 / 106 (47% / 53%), no draws or errors, and 25.7 turns against 29.9. The bots still used each ability only about 2.5 times a game.
What changed
The reload half works and is fair. Now for the other half.

5. The closest rule that works

Prompts 6 to 8

So misses can't be detected. What's the closest rule that works the same for me and the bot? Give me a few options and the trade-off of each.
Go with the overheat: if you fire 3 shots in a row without sinking a ship, your next attack is lost, and sinking a ship resets it. Show a message when it happens.
Fix the type error.
What the agent did
Weighed three rules. "No sink, lose your next turn": almost every shot fails to sink, so games would just get twice as long. A random backfire: fair, but luck, not skill. An overheat: fire 3 shots in a row without sinking a ship and your next attack is lost; a sink resets it. The overheat won.
Tool calls
save_mod, typecheck, save_mod, typecheck
What came back
The first overheat version failed typecheck: "Type 'true' is not assignable to type 'false'." The data started as jammed: [false, false], so TypeScript decided that list could only ever hold false.
What changed
Declared the data's shape with a Data type and passed it to defineShipMod<Data>. Typecheck passed.

6. Let the numbers argue

Prompts 9 to 11

Play games on seeds 1, 4 and 7 and show me how often the overheat fired.
Run 200 bot games and tell me the win split.
Seat 0 looks behind. Is that real? Run 500.
What the agent did
Played three games to see the overheat fire, then ran 200 and 500 bot games.
Tool calls
play_once (seeds 1, 4 and 7), simulate_n (200 games), render_state, simulate_n (500 games)
What came back
Each side lost 1 or 2 attacks a game to overheating. At 200 games, seat 0 won only 44% (87 / 113), but the tool said ±9.8 points is noise at 200. At 500 games: 234 / 266 (47% / 53%), and 29.1 turns against 29.8.
What changed
The split was fine. The real problem was in the length column: the overheat cancelled the faster reloads, so the glass cannon played at normal pace.

7. Tune 3 to 4

Prompts 12 and 13

Games are no faster than normal now. Make it more forgiving: 4 shots instead of 3. Run 500 games again and compare.
Save it as case-glass-cannon and show me a picture of the board after each side fires the raft twice in a row.
What the agent did
Changed the limit from 3 shots to 4, and the rules card with it.
Tool calls
save_mod, typecheck, simulate_n (500 games), play_once (seeds 1, 4 and 7), then a final save_mod, typecheck, simulate_n and render_state
What came back
500 games: 260 / 240 (52% / 48%), 27.5 turns against 29.8, no errors. Each side still lost 1 or 2 attacks a game. The final save only added comments, and simulate_n gave the same result as before.
What changed
Kept 4. Single games swing more than normal, which is the glass-cannon feel.

Mistakes and fixes

The idea needed something the API can't see

"If you miss" sounded simple, but onAttack runs before the shot lands and nothing reports a miss afterwards.

Fix: Read the types before you design. Then pick the closest signal you can see (here, sinks) and say the trade-off in the rules text.

Bots look different to your hooks

On a bot's turn, the attack's kind is just "bot". A rule that punishes ability shots would bind people and never the bot, and simulate_n, which plays bots only, would hide it.

Fix: Probe with a throwaway version that only counts what the hook sees.

A list that could only hold false

jammed: [false, false] made TypeScript decide the list could only ever hold false, so setting it to true failed typecheck.

Fix: If your data holds a flag or a list that will change, declare its type.

Almost tuning on noise

A 44% win rate for seat 0 at 200 games looked like a problem. It was inside the noise band and went away at 500 games.

Fix: Run 500 games before you tune a win split.

The real problem was in another column

With the overheat at 3 shots, games took 29.1 turns against 29.8 for normal rules. The penalty cancelled the speed-up, so a "glass cannon" played at normal pace.

Fix: One number, 3 to 4, brought the speed back (27.5 turns).

Results

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

Glass CannonNormal rules
Wins, seat 0 / seat 1260 / 240 (52% / 48%)256 / 244 (51% / 49%)
First mover won280 (56%)284 (57%)
Average turns played27.529.8
Draws / errors0 / 00 / 0
  • Seat balance is within chance (±6.2 points at 500 games).
  • Games are 2.3 turns shorter, which is more than chance (noise about 0.6).
  • All 500 games played out differently from normal rules.
  • Each side loses 1 or 2 attacks a game to overheating. On seeds 1, 4 and 7, seat 0 / seat 1 lost 2 / 2, 1 / 2 and 2 / 1.
  • Ability uses per ship per game (Glass Cannon / normal): Kayak 2.9 / 3.6, Sailboat 2.6 / 1.7, Raft 2.4 / 1.3, Whale 2.6 / 2.1.

Every version along the way:

VersionChangeResultKept?
v1Reload only, every cooldown 147% / 53%, 25.7 turns (normal 29.9), 200 gamesTest version
v2Overheat after 3 shots without a sink47% / 53%, 29.1 turns (normal 29.8), 500 gamesNo, no faster than normal
v3Overheat after 4 shots52% / 48%, 27.5 turns (normal 29.8), 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 the Glass Cannon fleets at the start of a game
The starting boards (overheat-at-3 version, seed 2).
Four 7x7 boards from the mod tool after each side fires the Raft on two of its turns in a row
The final version, seed 4: each side fires the Raft on turn 1 and again on turn 3, back-to-back turns of its own. Under normal rules the Raft waits 5 turns.

The final mod

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

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

// Glass Cannon
// Player's idea: "every ability reloads in 1 turn, but if you miss, your next attack is lost."
// The mod API cannot see a hit or a miss (onAttack runs before the shot resolves, and only
// sinks are reported). So a "miss" here is a shot that did not sink a ship, with a grace
// window: fire DRY_LIMIT shots in a row without a sink and your next attack is lost.
// The rule reads the same for people and bots, because it only uses the seat and sinks.
const DRY_LIMIT = 4;

type Data = {
  dry: number[]; // shots fired since this seat last sank a ship
  jammed: boolean[]; // this seat's next attack is lost
  lost: number[]; // attacks lost to overheating, per seat (a counter for play_once)
  sinks: number;
};

export default defineShipMod<Data>({
  id: "case-glass-cannon",
  apiVersion: 1,
  classicVersion: "classic-2",
  rules: {
    pitch: "Every ability reloads in 1 turn, but go cold and your cannon overheats.",
    bullets: [
      "Sink every enemy ship to win",
      "Every ship's ability reloads in 1 turn",
      "Fire 4 shots in a row without sinking a ship and your next attack is lost",
      "Sinking a ship cools your cannon down",
    ],
  },
  maxTurns: 200,
  fleet: ["kayak", "sailboat", "raft", "whale"],
  ships: [
    classic.ship("kayak", { cooldown: 1 }),
    classic.ship("sailboat", { cooldown: 1 }),
    classic.ship("raft", { cooldown: 1 }),
    classic.ship("whale", { cooldown: 1 }),
  ],
  data: { dry: [0, 0], jammed: [false, false], lost: [0, 0], sinks: 0 },
  hooks: {
    onTurnStart(turn, ctx) {
      if (ctx.data.dry[turn.seat] >= DRY_LIMIT) {
        ctx.data.jammed[turn.seat] = true;
        ctx.data.dry[turn.seat] = 0;
      }
    },
    onAttack(attack, ctx) {
      const seat = attack.seat;
      if (ctx.data.jammed[seat]) {
        ctx.data.jammed[seat] = false;
        ctx.data.lost[seat] += 1;
        ctx.emit({ type: "show-message", audience: "all", template: "cannon-overheated" });
        return "cancel";
      }
      ctx.data.dry[seat] += 1;
    },
    onSink(sink, ctx) {
      ctx.data.sinks += 1;
      ctx.data.dry[sink.by] = 0;
    },
  },
});

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 Glass Cannon in the Arcade (listed there as Overheat)

Previous case study: Ceasefire · Next case study: First to Three · All case studies