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.
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.
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:
| scanner | what it printed | exit |
|---|---|---|
scan_project_glyphs.js | 81 character occurrences across 5 files are outside the built-in font. | 1 |
scan_translation_glyphs.js | 20 distinct characters (ja) are outside the built-in font. | 1 |
find_frozen_translations.js | no frozen translations: 2 .gd/.cs file(s) scanned, no tr() result stored in an auto-translated property. | 0 |
check_plural_csv.js | 1 translation CSV file(s): no plural defect readable from the table. | 0 |
find_tile_seam_causes.js | 4 cause(s) and 0 note(s) in project.godot plus 2 scene/resource file(s). | 1 |
find_tile_collision_gaps.js | 13 file(s) with a TileSet, 703 painted tile(s), 75 finding(s). | 1 |
predict_terrain_paint.js | all 9 cells have an exact tile in your set. | 0 |
verify_pot_generation.js | 59/61 claims — selftest OK, the 2 seeded errors were both caught. | 0 |
find_y_sort_causes.js | 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 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.
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.
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.
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.
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.
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.
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.
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.
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).
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.