· 6 min read ·

Generating a Complete Game Is a Different Problem Than Generating Code

Source: hackernews

Most AI code generation tools work at the snippet level. Give Copilot a function signature and it fills in the body. Give Cursor a comment and it writes the surrounding code. The unit of output is a coherent chunk of code that can be evaluated in isolation: does it compile, do the tests pass, does the type checker accept it?

Godogen is doing something different. It takes a text prompt and produces a complete, playable Godot 4 project: GDScript source, a scene tree, 2D and 3D assets, the full thing. After a year of development across four major rewrites, the author identified three specific engineering bottlenecks that had to be solved before reliable output was possible. Reading those three problems together reveals something worth examining: why generating a complete game is not just a harder version of generating code, but a categorically different problem.

A Game Is Three Tightly Coupled Artifacts

A snippet of GDScript can be evaluated on its own terms. Does it compile? Does it reference APIs that exist? Are the types consistent? These questions have deterministic answers that a static checker or a syntax validator can answer without running anything.

A playable game is not a collection of scripts. It is a scene graph, a set of scripts attached to nodes in that graph, and assets referenced by both. The three artifacts must cohere in ways that no individual artifact can be evaluated against independently.

A CharacterBody2D node needs a collision shape attached as a child to participate in the physics simulation. The GDScript on that node uses move_and_slide() to move through the world. If the collision shape is absent or misattached in the scene tree, the character falls through the floor without any error in the script. The script is correct. The scene is malformed. The behavior is wrong.

func _physics_process(delta: float) -> void:
    velocity.y += gravity * delta
    move_and_slide()

This code is valid GDScript. It compiles. It runs. If the CollisionShape2D node is missing from the parent CharacterBody2D in the .tscn file, the character will pass through all geometry silently. No error is raised. The script did nothing wrong. The problem is in the relationship between the scene structure and the code, not in either artifact individually.

Godogen addresses the scene generation problem by writing GDScript that builds the node graph in memory and serializes it through Godot’s native ResourceSaver, rather than generating the .tscn format as text. This delegates format correctness to the engine. But it does not eliminate the coupling problem; it relocates it. The builder script must know which node types need which child nodes, which collision shape types are appropriate for which body types, and which script variables correspond to which scene tree paths.

Silent Failures and Distant Errors

The specific failure modes that Godogen’s author spent a year mapping share a common structure: the error surface is distant from the error cause.

Godot’s @onready annotation defers variable assignment until after the node enters the scene tree:

@onready var sprite: Sprite2D = $Sprite2D

During headless scene construction, the scene tree is never live. @onready is syntactically valid in a builder script. The compiler accepts it. The assignment never executes, leaving the variable null. The null reference error appears later, when the generated game runs and code attempts to use sprite. The error message points to a line in the generated game script, not to the builder script that left the variable uninitialized. Without knowing the execution model, the failure looks like a code bug rather than a generation phase problem.

The owner property requirement has the same character. Every node added programmatically during headless construction must have its owner set to the scene root:

var node = Sprite2D.new()
scene_root.add_child(node)
node.owner = scene_root  # omitting this line causes silent data loss

Omitting the owner assignment produces no error during construction. ResourceSaver.save() returns success. The file is written to disk. The node simply disappears when the file is reloaded. The failure is detected only by diffing the expected scene structure against the loaded one, which requires knowing what the expected structure should be.

These are not obscure edge cases. They are foundational mechanics of how Godot’s scene system works. The official documentation describes each feature accurately in isolation. What documentation does not say is what happens when you combine headless execution with @onready, or programmatic node construction with serialization, because those combinations are unusual enough that the documentation authors did not anticipate needing to explain them. Four rewrites represent discovering each of these combinations the hard way.

The Evaluation Oracle Problem

Unit tests for code libraries have a clear oracle: a function either returns the expected value or it does not. Integration tests for web applications have a clear oracle: an HTTP response either has the expected status code and body or it does not. Both have deterministic success criteria that a test harness can check automatically.

A game has no obvious oracle. “Does the game work” is partly answered by “does it run without crashing” and partly answered by questions that have no automated answer: do the controls feel responsive, do enemies navigate the world correctly, does the platforming physics have the right weight and momentum.

Godogen addresses the portion of the oracle that is answerable: running the project headlessly captures console errors and crash output, which catches null references, type mismatches, and missing nodes. Visual evaluation, via screenshots taken during headless execution, catches a different class of failure: scenes that load without errors but render incorrectly, scripts that execute without crashing but produce wrong behavior.

This is the third bottleneck the project author describes: a code-generation agent is biased toward trusting its own output. It generated the code, it can inspect the code, and its internal model of what the code does may not match what the code actually does at runtime against a real game engine. The only reliable signal comes from execution. Headless Godot runs the full scene tree, physics simulation, and scripting runtime without a display. Running godot --headless --path /project produces the runtime errors that static analysis cannot predict and that the generating model cannot anticipate from reading its own output.

The evaluation loop makes the pipeline expensive in a way that snippet generation is not. Each iteration requires launching a Godot process, waiting for it to execute, capturing its output, and feeding that output back into the generation prompt. But the loop is what makes the output reliable, and reliability was the point.

What This Means for AI-Assisted Game Development

The projects attempting to use LLMs for Godot or Unity code generation mostly work at the snippet level: you ask for a function, you get a function, you paste it into your project. That approach works well because function generation can be evaluated in isolation. The function either compiles or it does not. Copilot and similar tools are good at this.

Godogen is attempting something the snippet paradigm cannot address: generating a coherent system where the game logic, scene structure, and asset references all work together. The system produces errors at the junctions between its parts, and those errors are not visible to any component examined individually. Getting there required building a pipeline that understands the Godot class API well enough to know which 850 classes to load for a given game type, understands the execution phase model well enough to know which APIs are valid in which context, and runs the generated output against the real engine to catch the failures that only appear at runtime.

The year of development and four rewrites are not a story about LLMs being unreliable. They are a story about the gap between generating code and generating systems. For isolated snippets, the gap is small. For a complete game, the gap is the entire problem.

Was this interesting?