Nine scanners for your Godot 4 project

Node scripts, one question each. No install, no dependencies, no network, and they never write to your project — every one of them only reads. Exit code is 0 when there is nothing to report and 1 when there is, so the same file works on your machine and as a CI gate.

Download

One zip, 53 KB, MIT. No email, no account, nothing to sign up for.

Contains the nine scripts, the six shared files they require, a README and both MIT licences. Every file is byte-identical to its source in the two public repos — the zip is a shortcut, not a different version. Needs Node 18 or newer; one of the nine also needs your own Godot editor binary.

What they printed against Godot's own demo projects

Claims about a scanner are cheap, so here is every one of them run today — 20 August 2026, Node 22.22.2, from a freshly extracted copy of the zip above — against code nobody here wrote: godotengine/godot-demo-projects pinned at 34fc995. The last line of output and the exit code, unedited:

scannerwhat it printedexit
scan_project_glyphs.js
whole repo
81 character occurrences across 5 files are outside the built-in font.1
scan_translation_glyphs.js
gui/translation CSV
20 distinct characters (ja) are outside the built-in font.1
find_frozen_translations.js
gui/translation
no frozen translations: 2 .gd/.cs file(s) scanned, no tr() result stored in an auto-translated property.0
check_plural_csv.js
whole repo
1 translation CSV file(s): no plural defect readable from the table.0
find_tile_seam_causes.js
2d/dynamic_tilemap_layers
4 cause(s) and 0 note(s) in project.godot plus 2 scene/resource file(s).1
find_tile_collision_gaps.js
whole repo
13 file(s) with a TileSet, 703 painted tile(s), 75 finding(s).1
predict_terrain_paint.js
2d/skeleton tileset.tres
all 9 cells have an exact tile in your set.0
verify_pot_generation.js
Godot 4.7-stable, --selftest
59/61 claims — selftest OK, the 2 seeded errors were both caught.0
find_y_sort_causes.js
whole repo, 533 scene/resource files
1 cause found.1

Three of the nine come back clean on this corpus, and that is the useful half of the table: a scanner that finds something everywhere is a scanner that has learned to shout. The 75 above are the opposite reminder: 5 of them are whole tilesets with no physics layer, in demos that move on a grid and never ask physics about a tile, and most of the other 70 are decoration sitting next to tiles that do collide. The scanner lists what a body will pass through; deciding which of those you meant is yours. The breakdown, file by file.

The nine, and the question each one answers

1. Characters your exported build cannot draw

The string is translated correctly and the player sees boxes. Godot's built-in font carries a fixed set of glyphs, and in the editor your machine quietly lends the rest — a font that is not in your export.

node docs/scan_project_glyphs.js /path/to/project
node docs/scan_translation_glyphs.js translations/text.csv

The first reads every scene and script in the project; the second reads one CSV and reports per locale. Both name the file, the line and the exact codepoint. The page with the measurements and an in-browser version.

2. Strings frozen in the language they were built in

The player switches language and half the UI follows. The half that does not is usually label.text = tr("KEY") — that stores the translated value where the engine expects the key, so there is nothing left to re-translate.

node docs/find_frozen_translations.js /path/to/project

Reads .gd and .cs, reports line and column, --json for CI. The 13 claims behind it, each asserted against Godot 4.7.

3. Plural forms that show one word for 1, 2 and 5

The CSV plural format on the first page of that search is the syntax of a pull request that was never merged: it imports without an error, writes the .translation files, and serves a single form for every n. What shipped in 4.6 is a ?plural column with the key column left empty on the continuation rows.

node docs/check_plural_csv.js /path/to/project

The finding that survives code review is a table that is right for every number you test with — Russian carrying two forms because English has two is correct at n=1, 2, 3 and 22 and shows the untranslated source plural at n=5 — so it names the smallest n that reproduces each one. Its per-language form counts are derived from the engine, not copied from gettext. 99 assertions across Godot 4.7, 4.4 and 4.2.

4. Translatable strings Generate POT leaves out

This one runs your Godot binary headless and compares what the editor's POT generation extracted against what is really translatable in the project.

node docs/verify_pot_generation.js /path/to/godot --selftest

The --selftest above builds a throwaway project with known traps and seeds two errors on purpose, to prove the checker still catches them. What it misses and why.

5. Which of the four causes opens the line between your tiles

Tiles touch in the PNG and the running game draws a one-pixel seam. There are four different causes with four different fixes, and the answer repeated most often online — Rendering → Quality → 2D → Enable Pixel Snap — is a Godot 3 setting that does not exist in Godot 4.

node docs/find_tile_seam_causes.js /path/to/project

Each finding carries the id of a claim measured on a real engine. The 26 claims, and the same scanner running in the page.

6. Painted tiles with no collision polygon

The hole the player falls through, listed before a playtest finds it.

node docs/find_tile_collision_gaps.js /path/to/project

It says the honest thing when it finds nothing: every tile carrying a polygon is not the same as collision working — layer bits, collision_enabled and the body mask still have to line up. The six ways a body goes through a painted tile, all 26 claims measured on 4.7.

No terminal? Paste your .tres and run the same scanner in the browser — same bytes, plus the six causes as toggles.

7. What terrain painting will actually put down

From your TileSet alone, with no engine and no project: the mask each cell asks for, and the cells your set cannot answer. Godot never leaves such a cell empty — it places the tile that disagrees with the fewest peering bits, which is why a missing tile looks like a bug in the algorithm.

node docs/predict_terrain_paint.js tileset.tres --rect 8x8

The 24 claims, including the sweep showing the score never names one tile. Related: why a blob autotile is 47 tiles and not 256.

8. Why the y-sorted layer still draws tiles in the wrong order

The wall is taller than one tile, the player walks below it and is drawn behind it. Ticking Y Sort Enabled and dragging Texture Origin — the two steps every answer gives — move the picture and never the sort key. The scanner names the layer that has the flag off, the one whose parent is not y-sorted (both are needed, and with only one ticked the pixels do not move at all), the tile pushed with texture_origin instead of y_sort_origin, and the tile left at a z_index of its own, which outranks the whole arrangement silently.

node docs/find_y_sort_causes.js /path/to/project

Draw order is not a property you can print, so its 28 claims are measured by rendering each scene into a SubViewport and reading the contested pixel back — including the formula the sort key really uses, cell.y × tile_size.y + tile_size.y/2 + y_sort_origin. The 28 measurements, with the sort key as a slider you can move. Long form: why-y-sort-draws-the-wrong-order.md; the harness is docs/verify_y_sort.sh in the same folder and it needs xvfb-run, because the headless build has no pixel to read.

What these are not

They do not fix anything. Every one of them reads your files and prints what it found; not one writes a byte back. Nothing here phones home, and nothing here has a trial.

They are also not demos. These scripts are the whole thing — they are the tools we wrote to measure our own claims, and the numbers on the pages linked above come out of these exact files. The products they came from answer different questions: LocGuard is a Godot 4 editor dock and CLI that fails a build on the translation table itself — missing keys, empty translations, placeholder drift, unbalanced BBCode (blobsmith.itch.io/locguard, free CLI: locguard-lite). Blobsmith turns 6 hand-drawn tiles into a paint-ready 47-tile Godot 4 tileset (blobsmith.itch.io/blobsmith, free Lite: blobsmith-lite).

How this download stays honest

The zip is not assembled by hand. A build script copies each file from its repo path, and the site's own verification rebuilds the zip from those same sources and compares — if a script gains a rule and the zip is not rebuilt, the check fails instead of serving a stale download quietly.

That discipline earned its keep the day this page was written: extracting the zip into an empty folder and running every script from there is part of the check, and the first run failed. Three of the scripts require shared files that were sitting next to them in the repo and were not in the zip, and a fourth requires LocGuard's CLI core. A zip that is correct in the folder it was built from is exactly the kind of broken that a reader discovers and the author never does.

Every measured page these came from