Skip to content
This repository was archived by the owner on Oct 16, 2026. It is now read-only.

Connect a scene signal in the .tscn with no editor attached - #1468

Merged
saworbit merged 7 commits into
mainfrom
batch/signal-connect-offline
Oct 15, 2026
Merged

saworbit merged 7 commits into
mainfrom
batch/signal-connect-offline

Conversation

@saworbit

Copy link
Copy Markdown
Owner

signal_connect answered 503 with no editor attached, so an agent could not wire a scene's signals before anything opened it. Godot keeps those connections as [connection] lines in the .tscn. With no editor, the tool now takes scene_path, writes the line into that file atomically and reads it back. With an editor attached, scene_path is refused, because the live route connects in the edited scene.

What it checks first. The live route judges a connection from the running objects. The file route judges it from the project's files.

  • The emitter and the target resolve as they do live, and are written relative to the scene root, as the editor writes them.
  • The signal must be declared by the emitter's script, a script it extends or its engine class in the class reference. The method must be declared by the target's. A missing one gets the same 404 code as live.
  • When both sides are GDScript, the method must take the signal's arguments, or the call is 409 signal_target_arity_incompatible. An engine class's signals are known by name alone, so that check is skipped there and the answer says arity_checked: false.
  • A connection the file already has is 409.
  • A node inside an instanced scene, an instanced node, a built-in script and an inherited scene are refused with 422 connection_not_judged. What they declare is in another file or in none, so there is nothing here to check against. The remedy points at attaching an editor.

What I measured. On 4.5.1, 4.6.2 and 4.7.2 the editor writes no flags for 2 and flags=N for the others, with paths relative to the root. On load, a bad signal is dropped with an error, a bad method connects with no error, and a bad emitter is dropped with no error. Without the checks above, a typo writes a connection that never fires. Connections written through the server load connected on all three lines, with no ERROR lines.

The answer. execution_mode: "offline_fallback", undo_redo_registered: false and rollback: "none", plus written_to, connection, line_written and arity_checked. The success contract now has a live branch and an offline branch. A Python test runs the server with no editor and checks its answer against it.

tools/list grew by 152 bytes and went over both budgets. The flags text gave its reasons twice, so it is shorter now. Full is 210,351 of 210,400 and core is 96,822 of 96,900. The contract snapshots move only for this tool.

Tests: SceneConnections.Line, WritesWhatItCanJudge, RefusesWhatItCannotJudge, KeepsTheFileLayout and ToolOfflineRoute. Each fails with its part taken out: the signal check, the routing, the arity check, the instance check and the duplicate check. The full native suite, the Python suite, the docs validator and the test counts pass locally. The bridge did not change, and CI runs the live harness.

Fixes #1387

project_set_input_action answered 503 with no editor, so a new project's
input map could not be set before anything opened it, though
project_set_setting writes the same file offline. With no editor it now
writes input/<action> straight into project.godot, refuses an action that
is already there unless replace is set, as the live route does, and reads
the file back.

The event rules lived in the bridge alone. They are now one parser in
offline/input_actions, which the bridge builds its InputEvent from and the
offline route writes from, so the two cannot drift. Each event is written
with its class, its device and the properties the call set; Godot fills
the rest when it loads the file. Measured on 4.5.1, 4.6.2 and 4.7.2: once
an engine loads and saves the project, the action's text is byte for byte
what the live route saves for the same events.

The setting writers keep refusing input/, so a generic write cannot skip
the event checks; writeInputActionLiteral is the typed tool's own way in.
A dry run reads the action from the file.

Refs #1379
The refusal for an action that is already there carries retry_with in
the call itself, which the refusal scanner reads.

Refs #1379
signal_connect answered 503 with no editor, so a scene's signals could
not be wired before anything opened it. With no editor it now takes
scene_path and writes the [connection] line Godot itself writes into that
scene, after the last connection and before any [editable] section, under
the file's lock, and reads it back. With an editor attached scene_path is
refused, because the live route connects in the edited scene.

The engine says nothing about most bad connections: an emitter or target
the scene lacks is dropped, and a method the target lacks is connected and
fails only when the signal fires, measured on 4.5.1, 4.6.2 and 4.7.2. So
the write judges what it can and refuses the rest. The emitter's signal
and the target's method are found through each node's GDScript, each
script it extends by path or class_name, and the engine class the chain
ends on. A missing one is refused with the live route's code, a duplicate
and an arity the method cannot take likewise, and a node inside an
instanced scene, a built-in script or a step that does not resolve is
connection_not_judged. An engine class's signals are known by name alone,
so their arity is not checked and the answer says arity_checked: false.

Connections written this way through didi.exe load and connect on all
three lines with no ERROR line.

The Phase 7 success contract now holds the offline shape beside the live
one, and the request takes scene_path.

Refs #1387
…offline

# Conflicts:
#	CMakeLists.txt
#	src/mcp/tool_registry.cpp
scene_path and the new mode cost 152 bytes, which put both profiles over.
The flags text gave its reasons twice. It is shorter now, and the prose for
scene_path moves to the parameter table with the rest.

Full is 210351 of 210400 and core is 96822 of 96900.

The reference and the capability table now say what the file route checks.
A Python test runs the server with no editor and checks its answer against
the success contract. Three tests that used signal_connect as their example
of a live tool now expect both modes.
@github-actions github-actions Bot added documentation Improvements or additions to documentation build CMake, packaging, and the build contract tests Test suites and the contracts they assert mcp-protocol The MCP wire surface: tools, schemas, annotations labels Oct 15, 2026
@saworbit
saworbit enabled auto-merge (squash) October 15, 2026 22:06
@saworbit
saworbit merged commit 594ade6 into main Oct 15, 2026
29 checks passed
@saworbit
saworbit deleted the batch/signal-connect-offline branch October 15, 2026 22:36
saworbit added a commit that referenced this pull request Oct 16, 2026
* Say which tools write files with no editor attached

#1467, #1468 and #1469 gave project_set_input_action, signal_connect and
scene_create a file route. The capability matrix still said Phase 2 tools
never edit project.godot or a .tscn behind a disconnected editor, which
stopped being true at #382. The README, the site cards and the roadmap
named only the live route for the signal and wiring tools. The agent guide
now says what scene_create and signal_connect do with no editor.

* Record the change in the changelog
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

build CMake, packaging, and the build contract documentation Improvements or additions to documentation mcp-protocol The MCP wire surface: tools, schemas, annotations tests Test suites and the contracts they assert

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement]: signal_connect lacks offline fallback mode to connect signals in .tscn scene files

1 participant