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 createopens what it writes. The new scene becomes the edited scene and the result reports it asactive_scene, so the followingnode addlands where you expect. Pass--open=falseto 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 assertget_script() != nullafter instancing, because a script-less instance silently swallows property writes. PackedScene.pack()only keeps nodes whoseowneris the root. Build a scene programmatically and forget to setowner, and the children vanish on save.- Out-of-tree Controls never refresh their cached minimum size, so a
sizewrite clamps to the saved minimum. Add the Control to the tree before sizing it. bake_meshfrom 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.screenshotneeds a windowed editor (it returns-32000under--headless).runtime.screenshotworks 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_breakedwith the break reason instead of guessing at a missing autoload. This includes a hard error inruntime.eval. Recover withscene stopthenscene 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: untypedArrayelements areVariant, so annotate the type (var center: Vector3 = ...). AVector2igrid 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_encryptedand 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.