Skip to content
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Agent Guidelines for Godot Automation Fork

Guidelines to avoid common mistakes when working on this codebase.

## C++ Include Requirements

**Always explicitly include headers for any types or functions you use.** Do not rely on transitive includes - they vary by platform and can cause CI failures on some builds but not others.

**Includes must be alphabetically sorted.** The project uses clang-format which enforces include ordering. Run `clang-format -style=file -i <file>` before committing to fix formatting.

Common includes that must be explicit:

| Function/Type | Required Include |
|---------------|------------------|
| `callable_mp_static` | `#include "core/object/callable_method_pointer.h"` |
| `callable_mp` | `#include "core/object/callable_method_pointer.h"` |
| `Object::CONNECT_ONE_SHOT` | `#include "core/object/object.h"` (use `Object::` qualifier) |
| `Ref<T>` | `#include "core/object/ref_counted.h"` |
| `Vector`, `List`, `HashMap` | `#include "core/templates/<container>.h"` |

**`callable_mp_static` syntax differs by function type:**
```cpp
// File-scope static functions: NO '&'
static void my_callback() { ... }
obj->connect("signal", callable_mp_static(my_callback));

// Class static methods: USE '&'
obj->connect("signal", callable_mp_static(&ClassName::static_method));
```

## Automation Protocol (remote_debugger.cpp)

When modifying automation commands in `core/debugger/remote_debugger.cpp`:

1. **Add `is_inside_tree()` guards** - Always check if the scene tree is initialized before accessing nodes:
```cpp
Node *root = tree->get_root();
ERR_FAIL_NULL(root);
if (!root->is_inside_tree()) {
// Send empty/error response
return;
}
```

2. **Scene changes are deferred** - `change_scene_to_file()` returns immediately but the scene isn't loaded yet. Connect to `scene_changed` signal if you need to respond after the scene is ready.

3. **Flush input events** - After injecting input events, call `DisplayServer::get_singleton()->process_events()` to ensure they reach the GUI system in headless mode.

## Testing Changes

The CI builds for multiple platforms (Linux, macOS, Windows). A local build passing does not guarantee CI will pass due to:
- Different include resolution
- Platform-specific code paths
- Compiler differences (GCC vs Clang vs MSVC)
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
58 changes: 52 additions & 6 deletions core/debugger/remote_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "core/input/input_event.h"
#include "core/io/resource_loader.h"
#include "core/math/expression.h"
#include "core/object/callable_method_pointer.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "scene/main/node.h"
Expand Down Expand Up @@ -1243,26 +1244,71 @@ void RemoteDebugger::_send_current_scene() {
EngineDebugger::get_singleton()->send_message("automation:current_scene", msg);
}

// Static callback for scene_changed signal - sends automation result after scene is fully loaded
static void _on_scene_changed_send_result() {
Array msg;
msg.push_back(true);
EngineDebugger::get_singleton()->send_message("automation:scene_result", msg);
}

void RemoteDebugger::_change_scene(const String &p_scene_path) {
SceneTree *tree = SceneTree::get_singleton();
ERR_FAIL_NULL(tree);

Node *root = tree->get_root();
ERR_FAIL_NULL(root);

// Guard: tree must be initialized before we can change scenes
if (!root->is_inside_tree()) {
Array msg;
msg.push_back(false);
EngineDebugger::get_singleton()->send_message("automation:scene_result", msg);
return;
}

Error err = tree->change_scene_to_file(p_scene_path);

Array msg;
msg.push_back(err == OK);
EngineDebugger::get_singleton()->send_message("automation:scene_result", msg);
if (err != OK) {
// Immediate failure (e.g., file not found) - send error response now
Array msg;
msg.push_back(false);
EngineDebugger::get_singleton()->send_message("automation:scene_result", msg);
return;
}

// Scene change is deferred - connect to scene_changed signal to send response
// when the scene is actually loaded and ready
tree->connect("scene_changed", callable_mp_static(_on_scene_changed_send_result), Object::CONNECT_ONE_SHOT);
}

void RemoteDebugger::_reload_scene() {
SceneTree *tree = SceneTree::get_singleton();
ERR_FAIL_NULL(tree);

Node *root = tree->get_root();
ERR_FAIL_NULL(root);

// Guard: tree must be initialized before we can reload scenes
if (!root->is_inside_tree()) {
Array msg;
msg.push_back(false);
EngineDebugger::get_singleton()->send_message("automation:scene_result", msg);
return;
}

Error err = tree->reload_current_scene();

Array msg;
msg.push_back(err == OK);
EngineDebugger::get_singleton()->send_message("automation:scene_result", msg);
if (err != OK) {
// Immediate failure (e.g., no current scene) - send error response now
Array msg;
msg.push_back(false);
EngineDebugger::get_singleton()->send_message("automation:scene_result", msg);
return;
}

// Scene reload is deferred - connect to scene_changed signal to send response
// when the scene is actually loaded and ready
tree->connect("scene_changed", callable_mp_static(_on_scene_changed_send_result), Object::CONNECT_ONE_SHOT);
}

void RemoteDebugger::_set_pause(const Variant &p_paused) {
Expand Down
Loading