diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..eda4d0c36f33 --- /dev/null +++ b/AGENTS.md @@ -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 ` 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` | `#include "core/object/ref_counted.h"` | +| `Vector`, `List`, `HashMap` | `#include "core/templates/.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) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000000..43c994c2d361 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index b852c4affb0a..005ec7c33c58 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -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" @@ -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) {