This week we spent time tracking down a particularly tricky bug that, at first glance, looked like a full game freeze.
About 25% of the time when launching a debug scene, the player would appear completely frozen. No movement, no animations, no menu input — nothing. Relaunching the game would often “fix” it, which made it feel like a race condition or initialization issue.
Those are some of the hardest bugs to track down.
The Initial Hypothesis
The first instinct was that input or player state was broken:
-
Input lock stuck?
-
Animation system not initializing?
-
Player state machine not advancing?
We added a large amount of debug output to verify:
-
Input state
-
Player velocity
-
Animation state
-
Menu lock state
-
Camera position
What we found was surprising.
The player was actually moving.
The Turning Point
Even in “frozen” cases, logs showed:
[MOVE INPUT] raw=1.0,0.0 …
pos=(723.0, 907.0) → (726.8, 907.0) → (729.3, 907.0)
So input worked. Movement worked.
But visually, nothing changed.
To isolate the issue further, we added a simple debug marker — a colored square on the player’s CanvasLayer:
-
Red when idle
-
Green when moving
In “frozen” runs:
-
Logs said the marker turned green
-
The screen still showed red
That was the key clue.
What Was Actually Happening
There were two map scenes loaded at the same time.
-
The new map (loaded by the transition system) was receiving input and updating correctly
-
The old map (from the debug launch) was still visible on screen
So the player wasn’t frozen at all.
We were simply looking at the wrong scene.
This explains everything:
-
Movement logs updated → new scene
-
Screen didn’t change → old scene still visible
-
Happens randomly → timing-dependent scene initialization
-
Restart fixes it → removes stale scene
Root Cause
When launching a map directly for debugging:
-
Godot loads the scene normally
-
Our SceneTransitionManager also loads the same map
-
The original debug scene was not always removed cleanly
-
Occasionally, it stayed visually “on top”
So we ended up with:
Root
├── MapNode (debug launch) ← still visible
└── MapNode (transitioned) ← actually running the game
The Fix
We added a cleanup step to the scene transition system:
func removeexisting_map_nodes() -> void:
for child in get_tree().root.get_children():
if child.name == “MapNode” and !child.is_queued_for_deletion():
child.queue_free()
And ensured this runs before adding the new scene.
This guarantees that only one active map exists at any time.
Why This Was Hard to Debug
This bug had several characteristics that made it deceptive:
-
It looked like a game freeze, but wasn’t
-
Input and logic were functioning correctly
-
It only happened some of the time
-
Logs and visuals contradicted each other
-
The root cause lived in scene lifecycle, not gameplay code
Without the debug marker and careful logging, it would have been very easy to chase the wrong system.
Takeaway
This is a good example of a broader lesson:
When debugging, always verify whether the problem is logic, input, or presentation.
In this case, all gameplay systems were correct — the issue was purely visual due to duplicate scenes.
Current Status
After implementing the fix, we tested multiple launches in a row with no issues:
-
No duplicate maps
-
Player movement consistent
-
Menu behavior normal
-
Debug markers match logs
Everything is now behaving as expected.
This kind of work isn’t flashy, but it’s extremely important. Bugs like this can undermine confidence in the entire system if left unresolved.
The good news is that tracking it down also made the scene transition system more robust, which will help prevent similar issues later.
Thanks for sticking with us — more updates soon!
Share