Building Blood Rush with an AI Agent

  • The prompts
  • How it was built
    • 1. Read the guide, find the hook
    • 2. Pick the ships
    • 3. The first version
    • 4. The counter disagrees
    • 5. Fix the winning shot
    • 6. Prove the fix
    • 7. Is it fair?
  • Mistakes and fixes
    • The winning shot gave a bonus too
    • One word decides which mod you build
    • Testing instead of guessing
    • A first-mover swing that wasn't real
  • Results
  • Screenshots
  • The final mod
  • Try it yourself

Blood Rush is normal BattleTabs with one hook: sink an enemy ship and you shoot again straight away. Both players get the same fleet: Kayak, Sailboat, Raft and Sea Turtle.

The player's request:

Make a BattleTabs mod called Blood Rush: when you sink an enemy ship, you get a bonus shot.

It took 17 calls to the mod tool and two versions. Here's every step, including the bug we caught along the way: a counter in the mod's data spotted it when the game result never would have.

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 Blood Rush: when you sink an enemy ship, you get a bonus shot.
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-blood-rush, 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 Blood Rush: when you sink an enemy ship, you get a bonus shot.
  2. Use only ships I could publish to the Arcade, and pick ones that sink things often.
  3. Check it compiles, then play one bot game and show me the log around a sink.
  4. The counter says 7 bonus shots but I only see 6 skipped turns. Is the winning shot giving a bonus too? Skip it when the game's already over.
  5. Play a few more seeds and check the counter matches the skipped turns every time.
  6. Run 200 bot games and tell me the win split, how long games last, and if anything errored.
  7. First mover went up a bit. Run 500 games and tell me if that's real or noise.
  8. Show me a picture of the board right after someone gets a bonus shot.
  9. Save it as case-blood-rush.

How it was built

1. Read the guide, find the hook

Prompt 1

Make a BattleTabs mod called Blood Rush: when you sink an enemy ship, you get a bonus shot.
What the agent did
Read the ship-mods guide.
Tool calls
get_docs (ship-mods)
What came back
An onSink hook runs whenever a ship goes down, and ctx.extraTurn(seat) lets a player go again. The guide spells out this exact rule: "Sinker gets another shot: extraTurn(sink.by)." It works by skipping the other player's next turn.
What changed
The whole rule is one hook. One trap noted: sink.by is the player who sank the ship, and sink.owner is the player who lost it.

2. Pick the ships

Prompt 2

Use only ships I could publish to the Arcade, and pick ones that sink things often.
What the agent did
Listed the ships that can be published to the Arcade.
Tool calls
list_ships (arcade_only)
What came back
35 live ships, 22 allowed in the Arcade. Among them: the Kayak (a 1x2 attack, cooldown 2), Sailboat (1x3, cooldown 4), Raft (2x2, cooldown 5) and Sea Turtle (cooldown 2; successful attacks reveal the tiles next to them and cut its cooldown by 1).
What changed
The fleet: Kayak, Sailboat, Raft and Sea Turtle. All straightforward attackers, so sinks happen often and the rule shows up a lot.

3. The first version

Prompt 3

Check it compiles, then play one bot game and show me the log around a sink.
What the agent did
Copied the Second Wind example, which uses sink.owner, flipped it to sink.by, and added a bonusShots counter to see how often the hook fires.
Tool calls
get_docs (examples/second-wind), save_mod, typecheck
What came back
"No type errors, and a match starts." Clean the first time.
What changed
Version 1 saved as case-blood-rush.

4. The counter disagrees

Prompt 3

Check it compiles, then play one bot game and show me the log around a sink.
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 29 turns plus 6 skipped" and {"bonusShots":7}. Seven bonus shots, but only six skipped turns. The seventh was the winning sink: the hook gave a bonus shot and showed the blood rush message after the game was already over.
What changed
The result was right, so nothing else would have flagged it. The counter did.

5. Fix the winning shot

Prompt 4

The counter says 7 bonus shots but I only see 6 skipped turns. Is the winning shot giving a bonus too? Skip it when the game's already over.
What the agent did
Looked up the fleet type, then skipped the bonus when the loser has no ships left.
Tool calls
get_docs (types), render_state, save_mod, typecheck
What came back
Each fleet has a shipsLeft count. The docs didn't say whether it updates before onSink runs, so the agent didn't guess: it added a second counter, finalSinks.
What changed
Version 2 checks ctx.fleets[sink.owner].shipsLeft === 0 and returns early. Typecheck was clean.

6. Prove the fix

Prompt 5

Play a few more seeds and check the counter matches the skipped turns every time.
What the agent did
Played four games on version 2.
Tool calls
play_once (seeds 1 to 4)
What came back
Bonus shots of 6, 5, 6 and 6 matched the skipped turns exactly, and finalSinks was 1 every time. Seed 1 gave the same game and the same winner as before.
What changed
Proved that shipsLeft is already updated when onSink runs. The fix changed the bookkeeping, not the play.

7. Is it fair?

Prompts 6 to 9

Run 200 bot games and tell me the win split, how long games last, and if anything errored.
First mover went up a bit. Run 500 games and tell me if that's real or noise.
Show me a picture of the board right after someone gets a bonus shot.
Save it as case-blood-rush.
What the agent did
Ran 200 bot games, then 500 for a tighter read, and saved a picture of the board after a bonus shot.
Tool calls
simulate_n (200, then 500 games), render_state
What came back
At 200 games: 104 / 96, no draws, no errors, but the first mover won 114 against 109 under normal rules, which could be a snowball sign. At 500 games: 258 / 242, first mover 56% against 54%, 27.7 turns against 28.5, and about 5.5 bonus shots a game.
What changed
Nothing. The split is fair and the first-mover bump is inside the noise, so no tuning was done.

Mistakes and fixes

The winning shot gave a bonus too

The first version gave a bonus shot, and showed the blood rush message, on the sink that won the game. The game result was right, so only the counter (7 bonus shots against 6 skipped turns) showed it.

Fix: Skip the bonus when the loser has no ships left. Count what your hook does, and compare it with the game log.

One word decides which mod you build

sink.by sank the ship, sink.owner lost it. The example the agent copied, Second Wind, uses owner. Copy it without flipping that word and you build the opposite game.

Fix: Flip owner to by when you copy the example.

Testing instead of guessing

The docs didn't say whether a fleet's shipsLeft updates before onSink runs.

Fix: A second counter, finalSinks, and four test games settled it, instead of an assumption.

A first-mover swing that wasn't real

At 200 games the first mover's wins rose from 109 to 114, which looked like it might snowball.

Fix: 500 games showed a 2-point gap, inside the noise. Don't tune without a problem, and run 500 games before reading anything into a small swing.

Results

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

Blood RushNormal rules
Wins, seat 0 / seat 1258 / 242 (52% / 48%)256 / 244 (51% / 49%)
First mover won282 (56%)272 (54%)
Average turns played27.728.5
Bonus shots per game5.5–
Draws / errors0 / 00 / 0
  • Seat balance is within chance (noise about 6 points at 500 games).
  • Games are 0.8 turns shorter, which is more than chance (noise 0.5): a sink hands you a free shot, so the hunt ends a bit sooner.
  • All 500 games played out differently from normal rules. With about five bonus shots a game, you feel the rule every few turns.
  • Bots don't hunt smartly after a sink, so a skilled player may get more out of a bonus shot than the bots do. That needs people playing it, which this build didn't test.

Every version along the way:

VersionChangeResultKept?
v1onSink gives the sinker an extra turn7 bonus shots, 6 skipped turns on seed 1No, bonus on the winning shot
v2No bonus when the loser has no ships left52% / 48%, 27.7 turns (normal 28.5), 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 right after seat 1 sinks the Kayak and gets a bonus shot
The first version, seed 1, after 6 turns: seat 1 sank the Kayak (dark red) on turn 4, and it's seat 1's turn again.
Two 7x7 boards from the mod tool showing seat 1's view after its sink
The final version, from the sinking player's side: the sunk Kayak on the enemy board, and it's their turn again.

The final mod

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

// Blood Rush: sink an enemy ship and you get a bonus shot.
import { defineShipMod } from "@battletabs/mod-api";

export default defineShipMod({
  id: "case-blood-rush",
  apiVersion: 1,
  classicVersion: "classic-2",
  rules: {
    pitch: "Sink a ship and the blood rush gives you a bonus shot.",
    bullets: ["Sink every enemy ship to win", "Sink an enemy ship and you shoot again straight away"],
  },
  maxTurns: 200,
  fleet: ["kayak", "sailboat", "raft", "turtle"],
  data: { bonusShots: 0, finalSinks: 0 },
  hooks: {
    onSink(sink, ctx) {
      // The last ship ends the game: no bonus shot, no toast.
      if (ctx.fleets[sink.owner].shipsLeft === 0) {
        ctx.data.finalSinks += 1;
        return;
      }
      ctx.data.bonusShots += 1;
      ctx.extraTurn(sink.by);
      ctx.emit({ type: "show-message", audience: "all", template: "blood-rush" });
    },
  },
});

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 Blood Rush in the Arcade

Previous case study: Quickfire · Next case study: Ceasefire · All case studies