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

Write a new scene file with no editor attached - #1469

Merged
saworbit merged 11 commits into
mainfrom
batch/scene-create-offline
Oct 15, 2026
Merged

saworbit merged 11 commits into
mainfrom
batch/scene-create-offline

Conversation

@saworbit

Copy link
Copy Markdown
Owner

scene_create answered 503 with no editor attached, so an agent could not lay out a project's scenes before anything opened it. With no editor, the tool now writes the .tscn itself: the gd_scene header with a fresh uid, and one [node] line for the root. It reads the file back, and it opens nothing. With an editor attached nothing changes.

What it checks first. The same things the editor's route checks, in the same words where it can.

  • root_type is checked against the class reference the way the live route checks it against ClassDB. It must be a class, it must inherit Node, and it must not be abstract. A script's class_name is refused on both routes, because neither ClassDB nor the reference has it. The answer's type_check names the reference and says whether it is the Godot line the project was last saved by.
  • root_name is refused for an empty name and for a path separator, as live. Then it gets the rename Node.set_name makes, . : @ " % to _. A loaded file keeps whatever name it holds, so without this the offline file would hold a name the live route never writes. root_name_requested is in the answer when the two differ.
  • An existing file is 409 already_exists unless overwrite is set, as live. A replaced scene keeps its uid, as it does when the editor saves over it.

What I measured. For every Node class the engine can build, on 4.5.1, 4.6.2 and 4.7.2, I saved a root-only scene with ResourceSaver and compared it with this two-line file. Both load to the same stored state. The engine writes more on some lines: unique_id from 4.6, layout_mode and anchors_preset for a Control root, format for a TileMap. None of it changes what loads, and the editor adds it when it next saves. Then I wrote 26 scenes through the server and checked them against the engine's own files. Every type loaded as itself and every name came back right. The editor's scan registered each uid and printed no ERROR or WARNING lines.

The name line matches the engine's byte for byte, with one exception I chose. The engine writes \a and \v in a name, and its own parser reads those back as the letters, so the engine's file loses the character. This writes them as they are, and they load back.

Also corrected: the Phase 2 intro in the reference said every Phase 2 tool is live only, and the guide said project_set_setting is the only project writer with no session. Both stopped being true before this, and five Phase 2 tools now have a file route.

tools/list grew by 46 bytes for the new mode and the tool description, which no longer says the tool always opens the scene. Full is 210,397 of 210,400 and core is 96,868 of 96,900. That leaves almost no room in full, so the next change that adds to it will need to shrink something. The contract snapshots move only for this tool.

Tests: NewScene.TextAndNames, WritesAScene, RefusesWhatTheEditorRefuses and ToolOfflineRoute. Each fails with its part taken out: the Node check, the abstract check, the existing file check, the rename, the escapes and the routing. 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 #1380

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.
scene_create answered 503 with no editor, so a project's scenes could not
be laid out before anything opened it. Now it writes the .tscn itself: the
gd_scene header with a fresh uid and one root node line.

root_type is checked against the class reference the way the editor checks
it against ClassDB. It must be a class, it must inherit Node, and it must
not be abstract. root_name gets the rename Node.set_name makes, because a
loaded file keeps whatever name it holds. An existing file needs overwrite,
and a replaced scene keeps its uid.

Measured on 4.5.1, 4.6.2 and 4.7.2 for every Node class the engine can
build: this file loads to the same stored state as the scene the engine
saves for that root. The node line matches the engine's byte for byte,
except that \a and \v go in as they are, because the engine's parser reads
its own escapes for them back as letters.

Fixes #1380
The reference says what the file route writes and checks, and the
capability table gives scene_create its own row. The Phase 2 intro said
every Phase 2 tool is live only, and five now have a file route. The guide
said project_set_setting is the only project writer with no session, which
stopped being true when project_set_input_action got one.

tools/list is 210397 of 210400 full and 96868 of 96900 core.
…fline

# Conflicts:
#	CHANGELOG.md
#	CMakeLists.txt
#	README.md
#	docs/TEST_INVENTORY.md
#	src/mcp/refusal_remedies.cpp
#	src/mcp/tool_registry.cpp
@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:43
@saworbit
saworbit merged commit b13612c into main Oct 15, 2026
29 checks passed
@saworbit
saworbit deleted the batch/scene-create-offline branch October 15, 2026 23:10
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]: scene_create lacks offline fallback mode to scaffold initial scene files

1 participant