Can Fugu Max write working Godot 4 code? 13 of 15 on the first try
By Eduardo Orellana · Updated
Fugu Max passed 13 of 15 Godot 4 tasks on the first try and 14 of 15 after one fix with the test's output, each run headless on Godot 4.7.2. It missed: cast a physics ray, wait for a signal or a timeout.
AppImage, x86_64. Ubuntu 22.04, Debian 12, Fedora 39 or newer.
Intel or AMD Chromebook, with Linux turned on.
Every model measured on the same Godot 4 tasks, each run headless on Godot 4.7.2 at a fixed 60 frames per second.
Measured by Flockbay: the same tasks, the same checks, one fix allowed with the test output.
| Model | First try | After one fix | Agent | Run on | Read from |
|---|---|---|---|---|---|
| Claude Opus 5.5 | 15 of 15 | 15 of 15 | Claude Code | 2026-10-06 | Claude Opus 5.5 results |
| Claude Sonnet 5.5 | 15 of 15 | 15 of 15 | Claude Code | 2026-10-06 | Claude Sonnet 5.5 results |
| GPT-6.1 Sol | 15 of 15 | 15 of 15 | Codex | 2026-10-06 | GPT-6.1 Sol results |
| DeepSeek V4.1 Flash | 15 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | DeepSeek V4.1 Flash results |
| Ember-1 | 15 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | Ember-1 results |
| Gemini 3.1 Pro Preview | 15 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | Gemini 3.1 Pro Preview results |
| Muse Wonder | 15 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | Muse Wonder results |
| Kimi K3 | 15 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | Kimi K3 results |
| GPT-5.6 Luna | 15 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | GPT-5.6 Luna results |
| GPT-6 Luna | 15 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | GPT-6 Luna results |
| Hy4 preview | 15 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | Hy4 preview results |
| Pareto 26.10 | 15 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | Pareto 26.10 results |
| Claude Haiku 5.5 | 14 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | Claude Haiku 5.5 results |
| MiMo-V2.6-Pro-UltraSpeed | 14 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | MiMo-V2.6-Pro-UltraSpeed results |
| GLM 5.2 | 14 of 15 | 15 of 15 | Hosted, by API | 2026-10-18 | GLM 5.2 results |
| DeepSeek V4 Pro 0813 | 14 of 15 | 14 of 15 | Hosted, by API | 2026-10-18 | DeepSeek V4 Pro 0813 results |
| GPT-5.4 | 14 of 15 | 14 of 15 | Hosted, by API | 2026-10-18 | GPT-5.4 results |
| MiniMax M3 | 13 of 15 | 14 of 15 | Hosted, by API | 2026-10-18 | MiniMax M3 results |
| Qwen3.7 Max | 13 of 15 | 14 of 15 | Hosted, by API | 2026-10-18 | Qwen3.7 Max results |
| Fugu Max | 13 of 15 | 14 of 15 | Hosted, by API | 2026-10-18 | Fugu Max results |
| Beam 1.0 501B A23B | 12 of 15 | 14 of 15 | Hosted, by API | 2026-10-18 | Beam 1.0 501B A23B results |
| Mistral Large 4 | 12 of 15 | 13 of 15 | Hosted, by API | 2026-10-18 | Mistral Large 4 results |
| Claude Haiku 4.5 | 11 of 15 | 14 of 15 | Claude Code | 2026-10-06 | Claude Haiku 4.5 results |
| Nemotron 3.5 Super VL | 11 of 15 | 14 of 15 | Hosted, by API | 2026-10-18 | Nemotron 3.5 Super VL results |
| LongCat 2.0 | 11 of 15 | 13 of 15 | Hosted, by API | 2026-10-18 | LongCat 2.0 results |
| Grok 4.3 | 11 of 15 | 13 of 15 | Hosted, by API | 2026-10-18 | Grok 4.3 results |
| Laguna S 2.1 | 9 of 15 | 12 of 15 | Hosted, by API | 2026-10-18 | Laguna S 2.1 results |
| Nova 2 Lite | 2 of 15 | 4 of 15 | Hosted, by API | 2026-10-18 | Nova 2 Lite results |
How the test works
Each task asks for one GDScript file with a stated API, in a fresh Godot project. Fugu Max was asked through its API, the way Flockbay reaches its hosted models: the task in one request, no tools, and the whole file back in a code block. A check scene then loaded the file and drove it on Godot 4.7.2, headless, at a fixed 60 frames per second: it pressed input actions, stepped physics, waited on timers and signals, and compared the results.
When a task failed, the model got the check's output once and could fix its file. Every task touches an API that changed between Godot 3 and Godot 4, or was added since: move_and_slide and is_on_floor, FileAccess and JSON, signals, tweens, Timer nodes, PackedScene, TileMapLayer, ray queries, AStarGrid2D, AnimationLibrary, and lambdas, which capture local variables by value.
The checks themselves are tested first: each passes on a reference answer and fails on an empty script. Run on 2026-10-18.
Fugu Max in Flockbay
Fugu Max is a hosted model in the Flockbay app's model menu, on the Indie Developer plan and above. It uses the plan's hosted usage about 21.4 times as fast as Auto.
It takes images, so Flockbay can show it screenshots of your game, and it has an effort setting in the composer, from quick answers to longer reasoning.
Move a CharacterBody2D with an input action: pass
The task: Write res://player.gd for Godot 4: a script for a CharacterBody2D that moves right at 200 pixels per second while the input action "ui_right" is held, and stands still otherwise.
Passed on the first try.
extends CharacterBody2D
const SPEED := 200.0
func _physics_process(_delta: float) -> void:
if Input.is_action_pressed("ui_right"):
velocity = Vector2(SPEED, 0.0)
else:
velocity = Vector2.ZERO
move_and_slide()
Fall with gravity and land on the floor: pass
The task: Write res://ball.gd for Godot 4: a script for a CharacterBody2D that falls with a gravity of 980 pixels per second squared and stops when it lands on the floor. The scene gives it its collision shape.
Passed on the first try.
extends CharacterBody2D
# Ball: falls under gravity and stops when it lands on the floor.
# The scene provides the collision shape.
const GRAVITY := 980.0 # pixels per second squared
var _landed := false
func _physics_process(delta: float) -> void:
if _landed:
return
# Apply gravity to build up downward speed.
velocity.y += GRAVITY * delta
move_and_slide()
# Stop once we're resting on the floor.
if is_on_floor():
velocity = Vector2.ZERO
_landed = true
A score label: pass
The task: Write res://score_label.gd for Godot 4: a script for a Label that shows "Score: 0" when it enters the scene, and has a method add(points: int) that adds to the score and updates the text to "Score: <total>".
Passed on the first try.
extends Label
var score: int = 0
func _ready() -> void:
_update_text()
func add(points: int) -> void:
score += points
_update_text()
func _update_text() -> void:
text = "Score: %d" % score
Save and load a dictionary as JSON: pass
The task: Write res://save_game.gd for Godot 4: a script extending RefCounted with two static functions: save_data(data: Dictionary, path: String) -> void writes the dictionary to the file as JSON, and load_data(path: String) -> Dictionary reads it back.
Passed on the first try.
# save_game.gd
# A small utility for saving and loading dictionaries as JSON files.
# Usage:
# SaveGame.save_data({"gold": 100}, "user://save.json")
# var data: Dictionary = SaveGame.load_data("user://save.json")
extends RefCounted
class_name SaveGame
## Writes the given dictionary to `path` as a JSON file.
static func save_data(data: Dictionary, path: String) -> void:
var file := FileAccess.open(path, FileAccess.WRITE)
if file == null:
push_error("SaveGame: could not open '%s' for writing (error %d)." % [path, FileAccess.get_open_error()])
return
var json_text := JSON.stringify(data, "\t")
file.store_string(json_text)
file.close()
## Reads a dictionary previously written by `save_data`.
## Returns an empty Dictionary if the file is missing or invalid.
static func load_data(path: String) -> Dictionary:
if not FileAccess.file_exists(path):
push_warning("SaveGame: no save file at '%s'." % path)
return {}
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
push_error("SaveGame: could not open '%s' for reading (error %d)." % [path, FileAccess.get_open_error()])
return {}
var json_text := file.get_as_text()
file.close()
var parsed = JSON.parse_string(json_text)
if typeof(parsed) != TYPE_DICTIONARY:
push_error("SaveGame: file at '%s' does not contain a valid JSON object." % path)
return {}
return parsed
Health with signals: pass
The task: Write res://health.gd for Godot 4: a script extending Node with health starting at 100, a signal health_changed(old_value: int, new_value: int), a signal died, and a method take_damage(amount: int) that lowers health without going below 0, emits health_changed when health changes, and emits died once, when health reaches 0.
Passed on the first try.
extends Node
## Health component for Godot 4.
##
## Attach to any node to give it damage handling.
signal health_changed(old_value: int, new_value: int)
signal died
var health: int = 100
var _has_died: bool = false
func take_damage(amount: int) -> void:
if amount <= 0 or health <= 0:
return
var old_health := health
health = maxi(health - amount, 0)
if health != old_health:
health_changed.emit(old_health, health)
if health == 0 and not _has_died:
_has_died = true
died.emit()
Fade out with a tween: pass
The task: Write res://fader.gd for Godot 4: a script extending Node2D with a method fade_out(duration: float) that fades the node's modulate alpha from 1 to 0 over duration seconds with a tween, and a signal faded emitted when the fade ends.
Passed on the first try.
extends Node2D
## Fades this node's modulate alpha from 1 to 0 over a duration,
## then emits the "faded" signal.
signal faded
var _tween: Tween
func fade_out(duration: float) -> void:
if _tween != null and _tween.is_valid():
_tween.kill()
modulate.a = 1.0
_tween = create_tween()
_tween.tween_property(self, "modulate:a", 0.0, duration)
_tween.finished.connect(_on_fade_finished)
func _on_fade_finished() -> void:
faded.emit()
Spawn on a timer, then stop: pass
The task: Write res://spawner.gd for Godot 4: a script extending Node that, once in the scene, adds a new child Node2D every 0.25 seconds, using a Timer, and stops after 4 children. It keeps the number spawned in a variable spawned_count.
Passed on the first try.
extends Node
## Spawns a new Node2D child every 0.25 seconds, stopping after 4 children.
const SPAWN_INTERVAL := 0.25
const MAX_SPAWNS := 4
var spawned_count: int = 0
var _timer: Timer
func _ready() -> void:
_timer = Timer.new()
_timer.wait_time = SPAWN_INTERVAL
_timer.one_shot = false
_timer.autostart = false
add_child(_timer)
_timer.timeout.connect(_on_timer_timeout)
_timer.start()
func _on_timer_timeout() -> void:
if spawned_count >= MAX_SPAWNS:
_timer.stop()
return
var child := Node2D.new()
add_child(child)
spawned_count += 1
if spawned_count >= MAX_SPAWNS:
_timer.stop()
A door with states: pass
The task: Write res://door.gd for Godot 4: a script extending Node for a door with the states CLOSED, OPENING and OPEN. It starts CLOSED. A method interact() starts opening a closed door; 0.5 seconds of game time later it is OPEN. A method get_state_name() -> String returns "CLOSED", "OPENING" or "OPEN".
Passed on the first try.
extends Node
enum State { CLOSED, OPENING, OPEN }
var _state: State = State.CLOSED
func interact() -> void:
if _state == State.CLOSED:
_state = State.OPENING
await get_tree().create_timer(0.5).timeout
_state = State.OPEN
func get_state_name() -> String:
match _state:
State.CLOSED:
return "CLOSED"
State.OPENING:
return "OPENING"
State.OPEN:
return "OPEN"
return "CLOSED"
An inventory with counts: pass
The task: Write res://inventory.gd for Godot 4: a script extending RefCounted with add_item(item_name: String, count: int), remove_item(item_name: String, count: int) -> bool that removes only when there are enough and returns whether it did, and get_count(item_name: String) -> int that is 0 for an item never added.
Passed on the first try.
extends RefCounted
## A simple inventory that tracks item counts by name.
var _items: Dictionary = {}
## Adds the given count of an item to the inventory.
func add_item(item_name: String, count: int) -> void:
if count < 0:
push_warning("Inventory: tried to add a negative count (%d) for '%s'." % [count, item_name])
return
if count == 0:
return
if _items.has(item_name):
_items[item_name] += count
else:
_items[item_name] = count
## Removes the given count of an item, but only if there are enough.
## Returns true if the removal happened, otherwise false.
func remove_item(item_name: String, count: int) -> bool:
if count < 0:
push_warning("Inventory: tried to remove a negative count (%d) for '%s'." % [count, item_name])
return false
if count == 0:
return true
if get_count(item_name) < count:
return false
_items[item_name] -= count
if _items[item_name] <= 0:
_items.erase(item_name)
return true
## Returns how many of an item the inventory holds; 0 if it was never added.
func get_count(item_name: String) -> int:
return _items.get(item_name, 0)
Load a level scene from a path: pass
The task: Write res://level_loader.gd for Godot 4: a script extending Node with a method load_level(path: String) -> Node that loads the scene file at path, creates an instance of it, adds it as a child, and returns it.
Passed on the first try.
extends Node
## Loads the scene at [param path], instantiates it, adds the instance as a
## child of this node and returns it. Returns null if the scene can't be loaded.
func load_level(path: String) -> Node:
var packed_scene := ResourceLoader.load(path) as PackedScene
if packed_scene == null:
push_error("LevelLoader: could not load a PackedScene from '%s'." % path)
return null
var level := packed_scene.instantiate()
add_child(level)
return level
Paint cells on a TileMapLayer: pass
The task: Write res://grid_painter.gd for Godot 4.7: a script extending TileMapLayer with paint_row(y: int, from_x: int, to_x: int) that sets the cells from (from_x, y) to (to_x, y) inclusive to tile source 0, atlas coordinates (0, 0), and is_painted(cell: Vector2i) -> bool that says whether a cell has a tile.
Passed on the first try.
extends TileMapLayer
## GridPainter: paints straight rows of tiles onto a TileMapLayer.
## Paints every cell in the row `y` from `from_x` to `to_x` (inclusive) with
## the tile from source 0 at atlas coordinates (0, 0).
func paint_row(y: int, from_x: int, to_x: int) -> void:
var start_x := mini(from_x, to_x)
var end_x := maxi(from_x, to_x)
for x in range(start_x, end_x + 1):
set_cell(Vector2i(x, y), 0, Vector2i(0, 0))
## Returns true if the cell `cell` currently has a tile assigned to it.
func is_painted(cell: Vector2i) -> bool:
return get_cell_source_id(cell) != -1
Cast a physics ray: pass after one fix
The task: Write res://line_of_sight.gd for Godot 4: a script extending Node2D with first_hit(from: Vector2, to: Vector2) -> Vector2 that casts a physics ray through the 2D world from `from` to `to` and returns the point where it first hits a collider, or `to` when it hits nothing. It is called from physics processing.
Failed on the first try (SCRIPT ERROR: Parse Error: Function "get_rid()" not found in base self.), passed after one fix.
What Godot 4.7.2 printed on the first try:
Godot Engine v4.7.2.stable.mono.official.ed1daf0bf - https://godotengine.org
SCRIPT ERROR: Parse Error: Function "get_rid()" not found in base self.
at: GDScript::reload (res://line_of_sight.gd:13)
GDScript backtrace (most recent call first):
[0] _ready (res://bench_check.gd:4)
ERROR: Failed to load script "res://line_of_sight.gd" with error "Parse error".
at: load (modules/gdscript/gdscript_resource_format.cpp:46)
GDScript backtrace (most recent call first):
[0] _ready (res://bench_check.gd:4)
SCRIPT ERROR: Invalid call. Nonexistent function 'first_hit' in base 'Node2D'.
at: _ready (res://bench_check.gd:21)
GDScript backtrace (most recent call first):
[0] _ready (res://bench_check.gd:21)A path on a grid around walls: pass
The task: Write res://path_finder.gd for Godot 4: a script extending RefCounted with find_path(size: Vector2i, walls: Array[Vector2i], from: Vector2i, to: Vector2i) -> Array[Vector2i] that returns the shortest path of grid cells from `from` to `to`, both included, on a grid of `size` cells, never entering a wall cell and moving only up, down, left or right.
Passed on the first try.
class_name PathFinder
extends RefCounted
## Finds the shortest path of grid cells between two cells on a rectangular grid,
## avoiding wall cells and moving only up, down, left or right.
const NEIGHBOR_OFFSETS: Array[Vector2i] = [
Vector2i.UP,
Vector2i.DOWN,
Vector2i.LEFT,
Vector2i.RIGHT,
]
## Returns the shortest path from `from` to `to` (both included) on a grid of
## `size` cells, never entering a wall cell, moving in 4 directions.
## Returns an empty array if no path exists or the arguments are invalid.
func find_path(
size: Vector2i,
walls: Array[Vector2i],
from: Vector2i,
to: Vector2i
) -> Array[Vector2i]:
var path: Array[Vector2i] = []
# Validate arguments: grid size, endpoints inside the grid,
# and endpoints not inside walls.
if size.x <= 0 or size.y <= 0:
return path
if not _is_inside(from, size) or not _is_inside(to, size):
return path
if _is_wall(from, walls) or _is_wall(to, walls):
return path
if from == to:
path.append(from)
return path
var wall_set := {}
for cell in walls:
wall_set[cell] = true
# Breadth-first search, which guarantees the shortest path
# on an unweighted grid.
var queue: Array[Vector2i] = [from]
var queue_index := 0
var came_from: Dictionary = {} # Vector2i -> Vector2i (previous cell)
came_from[from] = from # Sentinel: marks `from` as visited.
while queue_index < queue.size():
var cell: Vector2i = queue[queue_index]
queue_index += 1
for offset in NEIGHBOR_OFFSETS:
var neighbor: Vector2i = cell + offset
if not _is_inside(neighbor, size):
continue
if wall_set.has(neighbor):
continue
if came_from.has(neighbor):
continue
came_from[neighbor] = cell
if neighbor == to:
return _reconstruct_path(came_from, from, to)
queue.append(neighbor)
# `to` was never reached.
return path
func _is_inside(cell: Vector2i, size: Vector2i) -> bool:
return cell.x >= 0 and cell.x < size.x and cell.y >= 0 and cell.y < size.y
func _is_wall(cell: Vector2i, walls: Array[Vector2i]) -> bool:
for wall in walls:
if wall == cell:
return true
return false
func _reconstruct_path(
came_from: Dictionary,
from: Vector2i,
to: Vector2i
) -> Array[Vector2i]:
var path: Array[Vector2i] = []
var cell := to
while cell != from:
path.append(cell)
cell = came_from[cell]
path.append(from)
path.reverse()
return path
Build and play an animation in code: pass
The task: Write res://blinker.gd for Godot 4: a script extending Node2D that, when it enters the scene, creates an AnimationPlayer child and a looping animation named "blink", 1 second long, that keeps the node visible for the first half of each second and hidden for the second half, and plays it.
Passed on the first try.
extends Node2D
func _ready() -> void:
var player := AnimationPlayer.new()
add_child(player)
var anim := Animation.new()
anim.length = 1.0
anim.loop_mode = Animation.LOOP_LINEAR
var track_idx := anim.add_track(Animation.TYPE_VALUE)
anim.track_set_path(track_idx, ":visible")
anim.track_insert_key(track_idx, 0.0, true)
anim.track_insert_key(track_idx, 0.5, false)
var library := AnimationLibrary.new()
library.add_animation("blink", anim)
player.add_animation_library("", library)
player.play("blink")
Wait for a signal or a timeout: fail
The task: Write res://waiter.gd for Godot 4: a script extending Node with wait_for(sig: Signal, seconds: float) -> bool, a coroutine that waits until the signal is emitted or `seconds` of game time pass, whichever comes first, and returns true if the signal came first. The signal has no arguments.
Failed on the first try (the check did not finish within 10 s of game time (600 frames)) and after one fix (the check did not finish within 10 s of game time (600 frames)).
What Godot 4.7.2 printed on the first try:
Godot Engine v4.7.2.stable.mono.official.ed1daf0bf - https://godotengine.orgQuestions
- Is Fugu Max good at Godot?
- On these 15 tasks it passed 13 on the first try and 14 after one fix. Each task is small and checked by running it, so the score says whether its Godot 4 code works, not how well it designs a game.
Related
- Beam 1.0 501B A23B on the same tasks
- Claude Haiku 4.5 on the same tasks
- DeepSeek V4 Pro 0813 on the same tasks
- GPT-5.4 on the same tasks
- MiniMax M3 on the same tasks
- Nemotron 3.5 Super VL on the same tasks
- Qwen3.7 Max on the same tasks
- Claude Haiku 5.5 on the same tasks
- Every verified Godot 4 answer
- Can GLM 5.2 write working Godot 4 code? 14 of 15 on the first try
- Can Ember-1 write working Godot 4 code? 15 of 15 on the first try
- Can Kimi K3 write working Godot 4 code? 15 of 15 on the first try
- Can Claude Opus 5.5 write working Godot 4 code? 15 of 15 on the first try
- Can Claude Sonnet 5.5 write working Godot 4 code? 15 of 15 on the first try
Flockbay runs your own agent, Claude or ChatGPT on the official Godot 4.7.2, and gives it Godot's errors the way this test does.
Download Flockbay free