Validator
Use when confirming a generated Godot game opens, runs, and has a working core loop. Runs headless checks, records manifest.validation, and advances status to validated/playable or failed.From its SKILL.md
npx -y skills add qmertesdorf/GameForge --skill validatorAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
21.3 KB, ~5.4k tokens by cl100k_base, as published. Nobody here has run it
validator
Confirm a generated game opens, runs without script errors, and (via human playtest) has a working core loop. Make every failure legible — attribute it to a specific skill gap (POC success criterion #5).
Inputs
manifests/<id>.jsonwith a populatedbuildblock (status = "generated").- The project on disk at
games/<id>/.
Outputs
- A populated
manifest.validationblock. status = "validated"(programmatic checks pass), then"playable"(human playtest passes), or"failed"with legibleissues.
Method 1 — Programmatic (automated now)
-
Run the project headless and capture output + exit code:
godot --headless --path games/<id>/ --quit-after 120PASS when: exit code is 0 AND the output contains no
SCRIPT ERROR, noERROR:, and no "Failed to load" lines. (A clean run of ~120 frames means the scene tree loaded and_processran without crashing.) -
Record results:
node tools/manifest.mjs merge <id> "{\"validation\": {\"opens_in_editor\": true, \"runs\": true, \"issues\": []}}"- On failure, set
runs: falseand put each error line inissuesverbatim, then:
STOP and report which skill is responsible (almost alwaysnode tools/manifest.mjs set-status <id> failedbuilder) and the precise error.
- On failure, set
-
On a clean run, do not advance yet — proceed to Method 1.5. The build is structurally sound, but "runs clean" is not "plays correctly"; the logic gate decides whether it reaches
validated.
Method 1.5 — Logic self-test (automated; REQUIRED if games/<id>/selftest.gd exists)
Headless error-checking cannot catch logic bugs in logic-heavy genres (match-3, hybrids): a mis-detected match, broken gravity, or an offense action that never damages the threat all run clean and only fail a human (POC runs 002–004). builder now emits games/<id>/selftest.gd for such genres — run it:
godot --headless --path games/<id>/ --script res://selftest.gd
- PASS = exit code 0 AND output contains
SELFTEST OK. Then advance:
(node tools/manifest.mjs merge <id> "{\"validation\": {\"core_loop_functional\": true}}" node tools/manifest.mjs set-status <id> validatedcore_loop_functionalis now backed by an assertion, not just a hopeful human — the human playtest in Method 2 confirms feel, the self-test confirms logic.) - FAIL =
SELFTEST FAIL: <reason>or non-zero exit. Record the reason verbatim inissues, setcore_loop_functional: false,set-status <id> failed, and STOP — attribute it tobuilderwith the precise assertion that failed (e.g. "builder: a 3-in-a-row swap did not clear any cells"). This is a POC success: a logic bug was caught automatically. - No
selftest.gdfor a logic-heavy genre is itself abuilderfinding — note it ("shipped no automated proof its loop works"), then advance tovalidatedon the clean run and lean harder on Method 2. For a genuinely trivial arcade loop, absence is fine.
Method 1.6 — Turn-based logic self-test (automated; REQUIRED for turn-based genres)
Method 1 (clean headless run) and a real-time _process self-test cannot exercise a turn-based engine — nothing advances without a scripted turn, so a deckbuilder/tactics title can run "clean" for 120 frames while its combat math is wrong. For turn-based genres, builder emits a selftest.gd that drives scripted turns through the rules engine with a fixed RNG seed (see builder's "Turn-based / scripted-turn genres"). Run it exactly like Method 1.5:
godot --headless --path games/<id>/ --script res://selftest.gd
- PASS = exit 0 AND output contains
SELFTEST OK. The scripted turn proved the full turn cycle: the opening state was populated, a core action spent its resource + landed its effect, a setup action established a state, a payoff action exploited that state for its bonus branch (not the base one), the opponent acted and durational state ticked onend_turn, a win/lose transition resolved, the post-encounter progression advanced, and the persistence milestone wroteuser://save.json. Advance:node tools/manifest.mjs merge <id> "{\"validation\": {\"core_loop_functional\": true}}" node tools/manifest.mjs set-status <id> validated - FAIL =
SELFTEST FAIL: <reason>or non-zero exit. Record the reason verbatim inissues, setcore_loop_functional: false,set-status <id> failed, and STOP — attribute it tobuilderwith the precise assertion that failed (e.g. "builder: a payoff action dealt only its base effect against an established state — the bonus branch never fired"). Catching a turn-based math bug headlessly is a POC success.
Determinism is mandatory: the seed is fixed in selftest.gd, so a flaky self-test is itself a builder finding (an unseeded RNG path in the engine). The human playtest (Method 2) still gates playable — the self-test proves the rules are correct, the human confirms it feels like a game worth replaying.
Method 1.7 — Interaction self-test (automated; REQUIRED if games/<id>/uitest.gd exists)
Methods 1.5/1.6 prove the rules (they drive the engine directly) and the visual audit proves the pixels; neither can see whether a tap on a control actually reaches its handler. That seam — mouse-filter shadowing, a phase mutation that never emits its rebuild event, a dead button, an overlapping hit-rect — is invisible to both, and an unscripted human playtest has no coverage guarantee (shopkeep-0001 reached playable with its sell tap completely dead and a stale screen after Next Day). builder emits games/<id>/uitest.gd for tap/click-driven games: a headless SceneTree script that boots Main.tscn, pushes real InputEventMouseButton clicks through the full core loop, and asserts engine state after every click. Run it:
godot --headless --path games/<id>/ --script res://uitest.gd
- PASS = exit code 0 AND output contains
UITEST OK. Record it:
Advance tonode tools/manifest.mjs merge <id> "{\"validation\": {\"interaction_functional\": true}}"validatedonly when this AND the applicable logic self-test (1.5/1.6) both pass. - FAIL =
UITEST FAIL: <n> checks failedor non-zero exit. Record each failingUITEST FAIL:check line verbatim inissues, setinteraction_functional: false,set-status <id> failed, and STOP. Attribute it tobuilderon a fresh build, or to whichever skill last reworked the view (asset / deepen / visual-audit fix pass) on a re-validation — with the precise check (e.g. "asset: shelf_tap_sells — full-screen container with MOUSE_FILTER_PASS swallows shelf taps"). - No
uitest.gdfor a tap/click-driven game is itself abuilderfinding — note it ("shipped no automated proof its controls receive input"), then proceed on the other gates and lean harder on Method 2.
Method 1.8 — Balance / playability audit (automated; REQUIRED if games/<id>/playtest.gd exists)
Methods 1.5–1.7 prove the rules, the math, and that taps land — none can see whether the assembled loop is winnable. A game can pass every one of them and be physically unplayable (diver-0001 shipped 100% unwinnable — the crush line sat shallower than the nearest treasure, so a player could never bank anything — with all logic/UI gates green). The playtest-audit skill emits games/<id>/playtest.gd: a headless competent-player bot that drives the real game loop (real spawns/collision/resource math) and asserts the game is winnable, fair, and progressable. Run it:
godot --headless --path games/<id>/ --script res://playtest.gd
- PASS = exit 0 AND output contains
PLAYTEST OK. The bot reports balance metrics (earnings/clear, min resource margin, objective fill rate) alongside the verdict — note any difficulty it only barely cleared for the human playtest (Method 2). - FAIL =
PLAYTEST FAIL: <reason>or non-zero exit. Record the reason verbatim,set-status <id> failed, and STOP. Attribute it to the skill that owns the tuning —builderon a fresh build,deepenon a re-validation after a depth/tuning pass (e.g. "deepen: crush depth shallower than the first commission zone — unwinnable"). The fix is tuning (spawn geometry, gate depths, costs, the ramp), NOT weakeningselftest. - No
playtest.gdfor a game with a win/economy/progression loop is itself abuilder/deepenfinding — note it ("shipped no automated proof the game is winnable"), then lean harder on Method 2. A trivially-endless arcade toy with no economy may legitimately skip it — say so.
Method 2 — Human playtest (manual now)
-
Ask the owner to open the project in the Godot editor and play for ~60 seconds, confirming the core loop from
concept.core_loop(e.g. tap → jump, score climbs, game-over → restart works). -
On confirmation:
node tools/manifest.mjs merge <id> "{\"validation\": {\"core_loop_functional\": true}}" node tools/manifest.mjs set-status <id> playableIf the loop is broken, record the specific failure in
issues, set the loop boolean false, and attribute it to a skill (e.g. "builder did not wire restart on tap after game over"). Do NOT advance toplayable.
Toward full automation — what's built vs. what remains
Method 1.5 above is the first half of this hook, now live for logic-heavy genres: builder emits selftest.gd, the validator runs it, and SELFTEST OK backs core_loop_functional with an assertion instead of a hope. What it does NOT yet do is replace the human playtest — the self-test proves the loop's logic is correct, but playable still requires a human to confirm it feels right (juice, fairness, that a blend actually coheres). The remaining future step is to grow selftest.gd coverage (and add feel heuristics) until status can reach playable in CI with no human in the loop. Until then: self-test gates validated, human gates playable.
Method 3 — Re-skin re-validation (playable → styled, after the asset skill)
When asset has re-skinned a playable title — via the svg or the raster method — re-run the same gates on the rewired game and advance to the terminal styled status on success. The gates are method-agnostic:
- Headless import + run clean —
godot --headless --path games/<id>/ --quit-after 120, exit 0 with noSCRIPT ERROR/ERROR:/ "Failed to load". Proves the textures (.svgor.png) imported and the rewired scene runs. (Confirm theassetskill ran--importfirst, orload("res://art/...")returns null.) selftest.gdstillSELFTEST OK(if the title has one) — proves the swap changed only visuals, not logic. 2b.uitest.gdstillUITEST OK(if the title has one) — the re-skin rewires exactly the layer where interaction breaks, and the frozen-logic rule + selftest cannot see it (they prove the engine, not that taps still reach it). Exit 0 +UITEST OK, run as in Method 1.7; on failure attribute toassetwith the failing check.- Human A/B playtest — the owner confirms the re-skin (a) looks more designed than the primitive original, (b) reads as one coherent visual system rather than mismatched assets, and (c) plays identically.
- Cross-modal cohesion (when ≥2 modalities are present — e.g. the title also carries an
audio_pass, or at M2 astore_pass): confirm the visuals, audio, and (at M2) the icon read as one themed world — the same premise/tone/setting fromconcept.theme— not three independent interpretations. On failure, attribute it to aconcept.themegap (the anchor was too vague to align the modalities) or to a skill that ignored the theme (e.g. "audio: chose a chiptune mood for a cozy-storybook theme — ignoredconcept.theme.tone") — a specific, fixable prose cause, exactly like the within-modality cohesion finding above.
- Cross-modal cohesion (when ≥2 modalities are present — e.g. the title also carries an
Raster-only additional checks (when asset_pass.method == "raster"):
- Mobile-density sanity — each
recipes[].master_resolutionis a high-res power-of-two master (downscaled to footprint, never upscaled) and eachimport_settingsenables mipmaps. A sprite that is blurry/aliased at the footprint, or generated below its on-screen size, is anassetfinding (wrong master/import), not a validator pass. - IP-safety A/B — the owner explicitly confirms nothing resembles trademarked/copyrighted characters, logos, or celebrity likeness. If anything does, it is a hard fail (app-store + legal risk): record it, attribute it to
asset(weaknegativeprompt / non-generic prompt), and do not advance.
On all gates passing:
node tools/manifest.mjs set-status <id> styled
node tools/manifest.mjs validate <id>
On failure, record the specific issue in validation.issues, attribute it to a skill, and do not advance — the game stays playable. Examples: "asset: left the primitive obstacle drawing under the sprite — double-draw"; "asset: sprites individually fine but don't cohere — prompt_scaffold/style gap"; "asset: hero master generated at 256² — blurry on xxxhdpi, wrong master_resolution"; "comfy.mjs: ComfyUI unreachable — infra, re-run after starting the server". The fix is a specific asset/comfy.mjs prose or recipe change.
Method 4 — Audio pass (PNG-independent; for scored games)
When a game carries an audio_pass, confirm the audio is real and wired:
- Files exist & import. Every
audio_pass.recipes[].namehas a committed file atgames/<id>/audio/<name>.<format>. Open the project headless (& "<godot-exe>" --path games/<id>/ --quit-after 2) and confirm Godot imports the audio without errors in the log (no "Error importing" / failed.import). - Players reference valid streams. Each
audio_pass.events[].nodeexists as anAudioStreamPlayerin the scene and itsstreampoints at a real imported clip; the music player's stream hasloop = truewhen its recipe setloop:true. - SFX fire on events — gated, like Method 1.5. If
games/<id>/selftest.gdexists and carries audio assertions, drive each mappedsignal/event through it and assert the correspondingAudioStreamPlayer.play()was invoked (e.g. spy by checkingplayingor a wired counter). Otherwise (no selftest, or a selftest with no audio coverage — neitherbuildernoraudiois required to add play()-spy assertions), confirm SFX wiring by inspection (item 2 already verified each player references a real stream) and move on. Don't block on a self-test no skill was told to write. - Mobile sanity. File sizes are reasonable for mobile (SFX ≪ 1 MB; music a few MB WAV — the pipeline emits uncompressed WAV today, ~5 MB / 30 s stereo; OGG is a future size optimization), formats are
wav/ogg, sample rate ≤ 48 kHz. - IP-safety. Confirm no recipe prompt names an artist or copyrighted track; music negative prompt excludes vocals unless intended.
- Cross-modal cohesion (when the title also has an
asset_pass). Confirm the audio and the visuals read as one themed world — the same premise/tone/setting fromconcept.theme. A cozy-storybook look with an aggressive arcade soundtrack is a failure: attribute it to aconcept.themegap or to theaudio/assetskill that ignored the theme, and record it. (Cohesion is a human judgment call, like every aesthetic gate — not automatable.) This is the same cross-modal question as Method 3's cohesion sub-bullet — if the visual pass already confirmed it, record the verdict once rather than re-litigating. Like the rest of Method 4, it is an advisory finding recorded invalidation.issues, not a hard gate that blocks the visual pass.
Record results in manifest.validation.issues as needed. Audio validation does not block the visual pass and vice-versa.
Method 5 — Packaging gate (scored → packaged, after the packager skill)
When packager has produced a store_pass, assert the title is genuinely store-ready — headlessly and without the Android SDK — then advance to the terminal packaged status. The CI-checkable assertions run through tools/package.mjs verify (pure file + dimension + parse checks; no GPU, no SDK):
node tools/package.mjs verify <id>
- Both polish passes present + A/B-confirmed. A game is store-ready only with both a confirmed visual
asset_passand a confirmedaudio_pass(spec §2). The gate keys off the presence of both pass blocks — the source of truth theasset/audioskills designate — not the lossystatusstring (which holds onlystyledorscoredat once). The A/B confirmation itself is the human gate that advanced the title throughstyled(visual) andscored(audio): the canonical incoming status isscored, having passed throughstyled. If either block is absent, or the owner has not A/B-confirmed both visual and audio, do not advance — record "packager ran before both identities were owner-confirmed." - Every icon at exact px. Each
iconSizeTable()entry exists at its exact pixel dimensions (read straight from each PNG's IHDR bypackage.mjs, no engine). A missing or wrong-sized icon is apackager/package.mjsfinding. - Atlas covers every member. The atlas sheet exists and its map (
store/atlas.json) has one placement per member sprite (sprite_countmatches). 3b. Splash at canonical size (if recorded). Whenstore_pass.splashis present,store/splash.pngexists at the canonical boot-splash dimensions for the title's orientation (splashSize(build.orientation)→ 1080×1920 portrait, 1920×1080 landscape, read from the PNG's IHDR). Splash is optional, so a themeless/splashless title still passes; a wrong-sized splash is apackage.mjsfinding. The boot_splash aesthetic is part of item 7's owner A/B. Screenshots likewise follow orientation — portrait720×1280, landscape1280×720. - Size budget passes.
store_pass.size_budget.passis true (total committed store bytes ≤ budget). On failure, attribute it to oversized masters or too many assets — a specificpackagerchoice. - Export preset parses.
games/<id>/export_presets.cfgexists and parses as a valid Godot Android preset (parsePresetCfg→preset.0.platform == "Android"). - Regression guard. The game still imports + runs headless clean —
godot --headless --path games/<id>/ --quit-after 120, exit 0 with noSCRIPT ERROR/ERROR:/"Failed to load" (packaging must not have broken the game). 6b. Build artifact (toolchain-guarded; CI-skipped). Whenstore_pass.build_artifactis recorded,package.mjs verifyalready checks its shape (format/build_type/path) headlessly with no SDK. When the Android toolchain is present (ANDROID_HOME/ANDROID_SDK_ROOTset), additionally runnode tools/package.mjs verify-build <id>to assert the real file exists and is a well-formed APK/AAB (ZIP magicPK\x03\x04, non-trivial size). When the toolchain is absent, this command skips cleanly (printsskipped:true, exit 0) — the build artifact is git-ignored and never present on a clean checkout, so CI is unaffected. A recorded-but-broken artifact is apackager/package.mjsfinding. - Cross-modal cohesion A/B (human). The owner confirms the visuals, audio, and the store icon/splash/screenshots read as one themed world — the same premise/tone/setting from
concept.theme— not four independent interpretations. (This is the M2 cohesion check the theming precursor explicitly deferred to here.) On failure, attribute it to aconcept.themegap (anchor too vague) or to the skill that ignored the theme (e.g. "packager: chose a hard-neon icon for a cozy-storybook theme — ignoredconcept.theme.tone") — a specific, fixable prose cause.
On all gates passing:
node tools/manifest.mjs set-status <id> packaged
node tools/manifest.mjs validate <id>
On failure, record the specific issue in validation.issues, attribute it to a skill (packager / package.mjs), and do not advance — the game stays scored. The icon/splash aesthetic A/B (item 7's aesthetic verdict) and the real APK build are explicitly the owner gate and the §8 Android-toolchain feasibility gate — not asserted here. The end-to-end … → packaged proof needs a scored game (owner-gated) plus the APK gate; the foundation exercises the CI-checkable assertions against the current substrate without claiming packaged (spec §9). Build-toolchain proof ≠ packaged. Proving the build toolchain on a game — recording build_artifact and passing verify-build — does not by itself advance status to packaged. The packaged gate still requires both owner A/B confirmations (styled visual + scored audio) and the item-7 cross-modal cohesion A/B; a game whose build is proven but whose A/Bs are still pending stays at its current status. The build seam proves shippability of the pipeline, not polish of the game.
Notes
- Some Godot CLI flags vary slightly by 4.x point release; if
--quit-afteris unavailable, fall back to--headless --path games/<id>/ --quitafter confirming--importsucceeds. Verify against the pinned version. - Legibility is the product. "It didn't work" is a POC failure; "builder doesn't scaffold touch input" is a POC success.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.