esc
navigate openSearch by Pagefind

Live-engine gotchas

Things that only surface when you drive a real 4.7 editor and game, and how to work around each.

Some engine behaviors only show up when you drive a live editor and a running game. Each of these was hit in practice; knowing them up front saves a confusing “why did nothing happen” loop.

Discovery, not memory

Training memory is confidently wrong about specific 4.7 APIs. Lead with engine search and engine class-info on the running build, every time.

Editor-time surprises

  • scene create opens what it writes. The new scene becomes the edited scene and the result reports it as active_scene, so the following node add lands where you expect. Pass --open=false to write a batch of files without moving the editor, and open one explicitly when you get to it.
  • The editor’s resource cache survives rescans. A script that loads a just-edited scene or script gets the stale copy. Load with CACHE_MODE_REPLACE_DEEP, and assert get_script() != null after instancing, because a script-less instance silently swallows property writes.
  • PackedScene.pack() only keeps nodes whose owner is the root. Build a scene programmatically and forget to set owner, and the children vanish on save.
  • Out-of-tree Controls never refresh their cached minimum size, so a size write clamps to the saved minimum. Add the Control to the tree before sizing it.
  • bake_mesh from MeshInstance geometry yields an empty navmesh under a headless editor (zero polygons, even synchronously). Bake in a windowed editor, or set the navmesh from colliders.

Headless traps

Screenshots and error capture behave differently without a window.

  • editor.screenshot needs a windowed editor (it returns -32000 under --headless). runtime.screenshot works headless, because the game is a separate windowed process.
  • A real script error in the played game freezes it under a headless editor. The remote debugger breaks with no UI to resume, so every runtime command afterward times out; the timeout says so, reporting debugger_breaked with the break reason instead of guessing at a missing autoload. This includes a hard error in runtime.eval. Recover with scene stop then scene play. push_error, push_warning, and shader errors are captured fine; a standalone or windowed run just logs the error and keeps serving IPC.

Value coercion

  • Pass packed-array properties as a JSON array of the element literal, for example '["Vector2(-64,-64)", "Vector2(64,-64)"]'. Each element is coerced to the packed type engine-side.
  • var x := <Array-element arithmetic> fails to compile in GDScript: untyped Array elements are Variant, so annotate the type (var center: Vector3 = ...). A Vector2i grid stores (x, z) as (x, y); it has no .z.

Encryption expectations

Two places where Godot’s encryption does less than its name suggests. The shipping and save-system guides carry the full recipes; these are the expectations to correct up front.

  • PCK encryption on a web export protects nothing meaningful. The browser fetches the pck and the wasm holds the key, so the export mechanism works and the protection is decorative. Encrypt on web only because the same key already covers other platforms.
  • An encrypted save is not a tamper-proof save. FileAccess.open_encrypted and its passphrase variant stop a text editor, and the key ships in your binary, so they do not stop a debugger. Detecting changes takes a stored HMAC compared on load (Crypto.hmac_digest); anything that must survive a hostile player belongs on a server.
Built with the help of godot-mcp. MIT licensed.