Check out our latest project ✨ OpenChapter.io: free ebooks the way its meant to be πŸ“–

godot-autosim

An asset by applesnort
The page banner background of a mountain and forest
godot-autosim hero image

Quick Information

0 ratings
godot-autosim icon image
applesnort
godot-autosim

Automated game simulation and balance testing framework for Godot 4.6+.Write a game adapter and a bot strategy. The framework plays your game thousands of times headlessly and gives you win rates, turn distributions, and custom metrics.Find balance problems with data instead of guesswork.Features: - Sync and async runners for any game architecture - Seed-based reproducibility for deterministic results - CLI runner for headless invocation and CI pipelines - JSON report export for external analysis - GUT assertion helpers for balance regression tests - Validated against 6 open-source games across 5 genresUsage: copy addons/godot_autosim/ into your project. No other dependencies.

Supported Engine Version
4.6
Version String
0.2.0
License Version
MIT
Support Level
community
Modified Date
8 hours ago
Git URL
Issue URL

godot-autosim

Automated game simulation and balance testing for Godot 4.6+.

Why

You're building a roguelike, card game, or strategy game. You tweaked enemy HP from 50 to 55. Did that break the difficulty curve? Did it make the boss unbeatable? Did it make block cards useless?

Without godot-autosim: play the game 20 times, guess, tweak, repeat.

With godot-autosim: run 1000 simulations in 5 seconds, get data:

Strategy Wins Win Rate Avg Turns
Aggressive 450/1000 45.0% 8.2
Defensive 380/1000 38.0% 11.4
Random 120/1000 12.0% 6.1

How It Works

You write two small scripts. The adapter teaches the framework how your game works β€” what actions are available, how to apply them, and when the game is over. The bot decides which action to take each turn. The framework handles the rest: it plays your game thousands of times, collects the results, and gives you a report with win rates, averages, distributions, and anything else you want to track.

Adapter (your game rules) + Bot (play strategy) β†’ Runner (1000 games) β†’ Report (win rates, metrics)

This runs headless β€” no rendering, no physics, no scene tree needed. A thousand games finish in seconds.

Quick Start

1. Install

Copy addons/godot_autosim/ into your project's addons/ folder.

2. Write an adapter

The adapter is a script that describes your game to the framework. You extend AutoSimGameAdapter and implement a handful of methods. Here's a card game example:

extends AutoSimGameAdapter

func create_initial_state() -> Variant:
    return {hp = 80, enemy_hp = 50, hand = [...]}

func get_roles() -> Array[String]:
    return ["player"]

func get_current_role(state: Variant) -> String:
    return "player"

func get_available_actions(state: Variant) -> Array:
    var actions = []
    for card in state.hand:
        actions.append({action = "play", card = card})
    actions.append({action = "end_turn"})
    return actions

func apply_action(state: Variant, action: Variant) -> Variant:
    match action.action:
        "play": play_card(state, action.card)
        "end_turn": resolve_enemy_turn(state)
    return state

func is_game_over(state: Variant) -> bool:
    return state.hp <= 0 or state.enemy_hp <= 0

func get_winner(state: Variant) -> String:
    return "player" if state.enemy_hp <= 0 else ""

func get_run_metrics(state: Variant) -> Dictionary:
    return {hp_remaining = state.hp, damage_taken = 80 - state.hp}

The framework calls these methods in a loop: get actions β†’ bot picks one β†’ apply it β†’ repeat until game over. You don't manage the loop yourself.

3. Write a bot

The bot picks an action from the list your adapter provides. Different bots let you test different play styles:

extends AutoSimBotStrategy

func choose_action(state: Variant, available_actions: Array) -> Variant:
    var best = null
    for action in available_actions:
        if action.action == "play" and action.card.damage > 0:
            if best == null or action.card.damage > best.card.damage:
                best = action
    return best if best else available_actions.back()

Write as many bots as you want β€” aggressive, defensive, random, greedy. Comparing them is how you find balance problems.

4. Run it

var config = AutoSimConfig.create(MyAdapter.new(), {"player": AggressiveBot.new()}, 1000)
var report = AutoSimRunner.run(config)
print(report.summary())  # "1000 runs | 72.3% win rate | avg 5.4 turns"

Or from the command line:

godot --headless --script addons/godot_autosim/cli/cli.gd -- \
  --adapter=res://my_adapter.gd \
  --strategy=res://aggressive_bot.gd \
  --iterations=1000 \
  --output=balance_report.json

Reading the Report

Once the runner finishes, you get a BalanceReport with everything you need to understand your game's balance:

report.win_rate("player")         # 0.723 β€” how often this strategy wins
report.avg("hp_remaining")        # 42.1 β€” mean across all runs
report.median("turns")            # 5.0 β€” middle value (less sensitive to outliers than avg)
report.stddev("damage_taken")     # 12.3 β€” how consistent the experience is
report.percentile("turns", 95)    # 12.0 β€” worst-case game length
report.distribution("turns", 5)   # histogram buckets for visualization

High stddev means some runs are wildly different from others β€” a sign of feast-or-famine balance. If avg and median diverge, you've got outlier runs pulling the average.

Export the full data as JSON for external analysis:

report.save("balance_report.json")  # every run with all metrics

Balance Tests in CI

The real power is catching regressions automatically. If you use GUT (Godot Unit Test β€” the most popular testing framework for Godot, similar to Jest or pytest), the framework includes assertion helpers that turn balance data into pass/fail tests. If you don't use GUT, skip this section β€” everything above works without it.

extends GutTest

func test_boss_is_beatable_but_not_trivial():
    var config = AutoSimConfig.create(MyAdapter.new(), {"player": SmartBot.new()}, 500)
    var report = AutoSimRunner.run(config)

    # Win rate should be 40-70% β€” challenging but fair
    AutoSimAssertions.assert_win_rate_between(self, report, "player", 0.4, 0.7)

    # Fights should last 5-10 turns β€” long enough for decisions to matter
    AutoSimAssertions.assert_avg_between(self, report, "turns", 5.0, 10.0)

func test_no_dominant_strategy():
    var adapter = MyAdapter.new()
    var reports = {}
    for bot_name in ["aggressive", "defensive", "random"]:
        var bot = load("res://bots/%s_bot.gd" % bot_name).new()
        reports[bot_name] = AutoSimRunner.run(
            AutoSimConfig.create(adapter, {"player": bot}, 500))

    # No single strategy should win more than 95% β€” that means the others are pointless
    AutoSimAssertions.assert_no_dominant_strategy(self, reports, 0.95)

Now every PR that touches combat numbers runs these tests. You'll know immediately if a change broke the difficulty curve.

The full set of assertions:

  • assert_win_rate_between β€” win rate falls in a range (e.g., 40-70%)
  • assert_avg_between β€” average of any metric falls in a range
  • assert_median_between β€” same but for median (more robust to outliers)
  • assert_stddev_below β€” experience is consistent enough (low variance)
  • assert_no_dominant_strategy β€” no single strategy makes all others irrelevant

Async Games

If your game uses await in its turn logic (coroutines, signal-driven combat), use the async variants. The adapter interface is identical except apply_action becomes apply_action_async:

extends AutoSimAsyncGameAdapter

func apply_action_async(state: Variant, action: Variant) -> Variant:
    await state.play_card(action["card"])
    return state

The runner needs to be in the scene tree since it awaits each step:

var runner = AutoSimAsyncRunner.new()
add_child(runner)
var report = await runner.run(config)
runner.queue_free()

Two Adapter Approaches

Direct Adapter

If your game logic is separated from rendering β€” pure functions, no physics, no Timer nodes β€” your adapter calls the real game code directly. This gives exact fidelity:

func apply_action(state, action):
    state.play_card(action["card"])
    return state

Mathematical Model

If your game uses Area2D collision, NavigationAgent2D, AnimationPlayer, or Timer-based cooldowns, those systems crash without a scene tree. Instead of fighting the engine, extract the balance-relevant numbers and reimplement the core loop as pure math:

func _find_target(turret, enemies):
    for enemy in enemies:
        if turret["pos"].distance_to(enemy["pos"]) <= turret["range"]:
            return enemy
    return null

Why this works: Balance problems are about numbers β€” damage too high, cost too low, scaling too steep. The tower defense adapter below found a 144x cost-efficiency imbalance between turret types without ever loading a tilemap. The auto-battler adapter found that one equipped unit beats three unequipped without instantiating a single Area2D.

What it trades: Physics edge cases (projectile dodging, pathfinding quirks, collision overlaps). For balance testing, that's a good trade β€” you get hundreds of runs per second instead of fighting engine dependencies.

Your game's architecture Adapter approach
Logic separated from nodes (data-driven) Direct β€” call real game code
Game uses await / coroutines Direct with AutoSimAsyncGameAdapter + AutoSimAsyncRunner
Game uses physics/navigation/timers Mathematical model
C# simulation layer C# bridge class + GDScript adapter

Validated on Open-Source Games

We've tested godot-autosim against six games across different genres to verify the framework works beyond our own projects. Clone any of these and try the adapter yourself.

Built-in: Card Battle

Ships in examples/card_battle/. A minimal deckbuilder with attack/block/bash cards vs a repeating damage pattern. Three bot strategies included.

Strategy Win Rate Avg Turns Notes
Aggressive 100% 10.7 Fastest β€” plays damage cards first
Defensive 100% 33.4 3x slower β€” blocks before attacking
Random 100% 19.9 Even random wins β€” enemy is too weak
godot --headless --script addons/godot_autosim/cli/cli.gd -- \
  --adapter=res://examples/card_battle/card_battle_adapter.gd \
  --strategy=res://examples/card_battle/aggressive_bot.gd \
  --iterations=500

Tower Defense β€” quiver-dev/tower-defense-godot4

Tile-based tower defense with economy, waves, and multiple turret types. Mathematical model adapter.

Strategy Win Rate Kills Finding
Gatling-first 100% 190 Objective untouched
Mixed 100% 190 Objective untouched
Missile-first 0% 42 Objective destroyed

Balance issue found: 144x DPS/cost imbalance β€” gatling (180 DPS, 250g) vs missile (4 DPS, 800g). Any strategy that doesn't prioritize gatlings loses.

Auto-Battler β€” guladam/godot_autobattler_course

Singleplayer auto-battler with units, abilities, traits, and items. Mathematical model adapter.

Matchup Result
3 Bjorn + 2 Robin vs 5 Zombie 100% player win
2 Robin vs 5 Zombie 0% player win
2 Bjorn (tier 2) vs 5 Bjorn (tier 1) 7.4% player win
1 Bjorn (sword+gloves) vs 3 Zombie 100% player win

Balance issue found: Items swing outcomes from 0% to 100% β€” one equipped unit beats three unequipped.

Deckbuilder β€” DesirePathGames/Slay-The-Robot

Full roguelike deckbuilder with 20+ cards, energy system, and status effects. Mathematical model adapter.

Strategy Win Rate Avg Turns HP Remaining
Smart 100% 5.6 47.0
Random 99.7% β€” β€”

Balance issue found: Basic Attack at 25 damage is wildly overtuned (75 DPS/turn vs 20-40 HP enemies). Even random play wins 99.7%.

3D Turn-Based Combat β€” Cute-Fame-Studio/3D-TurnBasedCombat

3D RPG with CharacterBody3D battlers, elemental damage, skills, and multi-unit parties. Mathematical model adapter.

Matchup Result Finding
Warriors vs Mages (1v1) 100% warrior Mages too squishy (80 HP / 4 def)
Balanced (W+M+H) vs 3x Warriors 98.5% balanced Healers enable infinite sustain
Fire Mages vs Water Mages 100% fire Element wheel inverted from intuition
Party vs Dragon Boss 100% party Healer sustain trivializes boss fights

Balance issues found: Healers are overpowered (50 HP heal, 15 SP cost, 7 SP/turn regen). Element wheel function contradicts its own comments β€” fire beats water, not vice versa.

Roguelike β€” statico/godot-roguelike-example

Turn-based roguelike with d20 combat, BSP dungeon generation, and monster scaling. Mathematical model adapter.

Strategy Avg Floors Kills Turns Survived
Smart 1.5 2.3 267
Random 0.0 0.5 686

Finding: Smart bot clears floors but dies faster (aggressive engagement). Neither bot completes the full 20-floor dungeon β€” the d20 combat system is genuinely lethal at depth with monster strength scaling 1.25x per floor.

Roadmap

  • Godot 4.5 support β€” currently requires 4.6+. Backporting to 4.5 would cover the majority of active Godot projects.
  • Multi-role games β€” the framework supports multiple roles already, but needs a proper 2-player example (PvP matchup testing, asymmetric balance).
  • Report visualization β€” HTML or Godot-native dashboard for viewing distributions, win rate trends across parameter sweeps.
  • Parameter sweep runner β€” vary a game parameter (enemy HP, card cost, spawn rate) across a range and plot win rate as a curve. Find the sweet spot without manual iteration.
  • Adapter generator β€” CLI tool or editor plugin that scaffolds an adapter from your game's existing scripts.

Requirements

  • Godot 4.6+

License

MIT

Automated game simulation and balance testing framework for Godot 4.6+.

Write a game adapter and a bot strategy. The framework plays your game thousands of times headlessly and gives you win rates, turn distributions, and custom metrics.

Find balance problems with data instead of guesswork.

Features:
- Sync and async runners for any game architecture
- Seed-based reproducibility for deterministic results
- CLI runner for headless invocation and CI pipelines
- JSON report export for external analysis
- GUT assertion helpers for balance regression tests
- Validated against 6 open-source games across 5 genres

Usage: copy addons/godot_autosim/ into your project. No other dependencies.

Reviews

0 ratings

Your Rating

Headline must be at least 3 characters but not more than 50
Review must be at least 5 characters but not more than 500
Please sign in to add a review

Quick Information

0 ratings
godot-autosim icon image
applesnort
godot-autosim

Automated game simulation and balance testing framework for Godot 4.6+.Write a game adapter and a bot strategy. The framework plays your game thousands of times headlessly and gives you win rates, turn distributions, and custom metrics.Find balance problems with data instead of guesswork.Features: - Sync and async runners for any game architecture - Seed-based reproducibility for deterministic results - CLI runner for headless invocation and CI pipelines - JSON report export for external analysis - GUT assertion helpers for balance regression tests - Validated against 6 open-source games across 5 genresUsage: copy addons/godot_autosim/ into your project. No other dependencies.

Supported Engine Version
4.6
Version String
0.2.0
License Version
MIT
Support Level
community
Modified Date
8 hours ago
Git URL
Issue URL

Open Source

Released under the AGPLv3 license

Plug and Play

Browse assets directly from Godot

Community Driven

Created by developers for developers