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.
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.
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?Pick 4 Arcade ships with a normal attack ability. Leave out anything that would be silly every turn, like the catamaran.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.Run 200 bot games and tell me the win split and how long games are compared with normal.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.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.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.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?get_docs (ship-mods, then types)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".Prompt 3
Pick 4 Arcade ships with a normal attack ability. Leave out anything that would be silly every turn, like the catamaran.list_ships (arcade_only)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.ctx.data.save_mod, typecheck, play_once (seed 1, then seed 2 with 3 scripted moves){"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}.Prompt 5
Run 200 bot games and tell me the win split and how long games are compared with normal.simulate_n (200 games)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.save_mod, typecheck, save_mod, typecheckjammed: [false, false], so TypeScript decided that list could only ever hold false.Data type and passed it to defineShipMod<Data>. Typecheck passed.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.play_once (seeds 1, 4 and 7), simulate_n (200 games), render_state, simulate_n (500 games)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.save_mod, typecheck, simulate_n (500 games), play_once (seeds 1, 4 and 7), then a final save_mod, typecheck, simulate_n and render_statesimulate_n gave the same result as before."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.
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.
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.
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.
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).
500 bot games on the final version, seeds 1 to 500, against normal rules on the same fleets and seeds.
| Glass Cannon | Normal rules | |
|---|---|---|
| Wins, seat 0 / seat 1 | 260 / 240 (52% / 48%) | 256 / 244 (51% / 49%) |
| First mover won | 280 (56%) | 284 (57%) |
| Average turns played | 27.5 | 29.8 |
| Draws / errors | 0 / 0 | 0 / 0 |
Every version along the way:
| Version | Change | Result | Kept? |
|---|---|---|---|
| v1 | Reload only, every cooldown 1 | 47% / 53%, 25.7 turns (normal 29.9), 200 games | Test version |
| v2 | Overheat after 3 shots without a sink | 47% / 53%, 29.1 turns (normal 29.8), 500 games | No, no faster than normal |
| v3 | Overheat after 4 shots | 52% / 48%, 27.5 turns (normal 29.8), 500 games | Final |
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.


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;
},
},
});
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.