The scene converter refuses any TileMap that carries a script — a script that extends TileMap cannot extend the Node2D the node becomes. Paste that .gd here and get the port call by call: the layer index turns into the layer node, each removed method gets its replacement named, and whatever needs a decision comes back as a question instead of a silent rewrite. Nothing is uploaded — this runs in your browser.
The example is 2d/navigation_astar/pathfind_astar.gd from Godot's own demo projects, branch 4.2 — unmodified, and one of the three scripted TileMap nodes the scene converter refuses.
A call like set_cell(0, …) can only be written out in full once something says what layer 0 became. Paste the .tscn that holds this TileMap and the names are read out of it by the same converter that would have rewritten the scene; or type them yourself, in layer order.
Without either, every call is still found and named — the ones that need the layer just say <the node for layer 0> instead of $Ground.
Only the rewrites marked rewritten above are in this text. Everything marked by hand is left exactly as you pasted it, because applying it would be a guess.
Godot 4.3 deprecated TileMap. The scene converter rewrites a .tscn: each TileMap becomes a Node2D of the same name, with one TileMapLayer child per layer. It refuses to touch a TileMap that carries a script, and that refusal is deliberate — a class that extends TileMap would be sitting on a node that is no longer one, and no text transform on the scene can decide what the script meant to do about it.
Run against Godot's own demo projects on branch 4.2, that is 3 of the 17 real TileMap nodes — roughly one in six. This page is what those three get instead of a dead end.
Almost all of it follows from the shape the converter emits. The layer stops being an argument and becomes a node:
extends TileMap extends Node2D
->
set_cell(0, pos, 1, Vector2i(2, 3)) $Ground.set_cell(pos, 1, Vector2i(2, 3))
That one sentence covers 20 of the 63 TileMap methods: same name, same behaviour, minus the leading layer argument (and, for seven getters, minus the trailing use_proxies). The wrapper keeps the old name, so $Level/TileMap anywhere else in your project still resolves.
The rest of the 63 fall into three piles:
| Kind | How many | What happens to the call |
|---|---|---|
| same | 12 | Identical signature on TileMapLayer. Only the receiver changes — from the map to one of its layer nodes. local_to_map(), get_used_rect(), clear(). |
| drop | 20 | Same method minus the layer argument, on the layer node. set_cell(), erase_cell(), get_cell_source_id(), set_cells_terrain_connect(). |
| gone | 27 | No method of that name exists on TileMapLayer. Most were layer bookkeeping and are now ordinary node operations: set_layer_modulate(1, c) is $Walls.modulate = c, get_layers_count() is counting children, add_layer() is adding a node. |
| enum | 4 | Same arity, but TileMap.VisibilityMode became TileMapLayer.DebugVisibilityMode — three values, all renamed, all still there. |
One property moved too: collision_animatable is use_kinematic_bodies, and it lives on each layer node.
It will not guess. A layer index that is not an integer literal, a call whose replacement is an assignment rather than a call, a bare TileMap in a type hint — those come back as findings for a human, never as a silent edit. TileMap is still a real class in 4.7, so none of them stops the script from compiling; they just stop being true about a node that is now a Node2D.
And it is deliberately quiet in a file that never proved it talks to a tilemap. Half these names are not TileMap's alone — clear() is on Array and Dictionary, get_used_rect() is on Image. A same-signature call is only reported once the file has shown its hand: extends TileMap, a method only TileMap ever had, or a layer index written as an integer.
Not from the online class reference. Every name, every argument count and every dropped argument is dumped out of ClassDB inside a real binary — Godot 4.2, 4.3, 4.4 and 4.7 stable — and the table is rebuilt from those dumps. The scanner has to embed the table, because it runs in this page with no engine anywhere near it, so a script re-derives it from the four engines and fails if one byte of the embedded copy has drifted.
That script checks three things, and only the first is about drift:
ClassDB dump; it must come out byte for byte like the copy this page loads.TileMapLayer in each engine that has one. The engine cannot say the advice is right; it can say it is not pointing at something imaginary.TileMap and prints what the script reads out of it. The converter and this scanner then port both, with no hand edits, and the newest engine runs the result and prints the same thing. A different digest is a failed port.The script is docs/verify_tilemap_script_api.sh; point it at your own binaries and it will tell you about your own version.
2d/navigation_astar is one of the three refusals. Its pathfind_astar.gd is 80 lines of A* pathfinding that reads the tilemap to find obstacles and writes back the start and end markers. Load it with the button above and you get 11 places to port.
Paste the script alone and 1 of the 11 is mechanical — the extends line — while the other 10 name the call and the shape of the fix but stop at <the node for layer 0>, because nothing has said what layer 0 is. Add the scene and all 11 become exact: that TileMap has one unnamed layer, the converter names it Layer0, and set_cell(0, _start_point, 0, …) is written out as $Layer0.set_cell(_start_point, 0, …).
The same numbers come out of the terminal, over a whole project at once:
node docs/scan_tilemap_script.js 2d/navigation_astar/ → 1 script, 11 places to port: 11 mechanical, 0 for a human
With --write it applies only the mechanical ones and leaves the rest alone; the exit code is 1 whenever anything still needs a human, so it drops straight into a build script.
TileMapLayer child and write extends TileMapLayer instead. Then the calls lose their receiver rather than gaining one — set_cell(0, pos, …) becomes plain set_cell(pos, …).var map: TileMap reference to this node are holding a Node2D now. They compile; they will fail on the first tile call.z_index or y-sort tricks, that is this page.None of the above is our product and none of it costs anything. Blobsmith is the pixel-art tool that turns 6 hand-drawn tiles into a full 47-tile blob sheet, and the Wirer (MIT) wires that sheet into a TileSet with the terrain bits already set. If you just ported a project and want a sheet you know is correct to test against, take the free 47-blob starter pack — 8 wired TileSets, MIT, no e-mail.
Convert the scene from TileMap to TileMapLayer · The byte layout of tile_map_data · Why a blob autotile is 47 tiles and not 256 · Terrain paints the wrong tile · Thin lines between tiles · Y-sorted tiles drawing in the wrong order · The free scanner bundle
This scanner also runs in your terminal, over a whole project at once — it is one of the MIT scripts in the free scanner bundle.