verifying-scenes is a skill from the Summer skill library: a guide your AI agent reads when the task matches, so it builds the good version on the first try. Prove scene work landed: snapshot, diff, screenshots, runtime reads, 3D audits, shot sheets and zooms.
When your agent uses it
- After any mutation batch, asset import, or lighting change, before claiming it worked
- During a playtest, to read live runtime state (spawned nodes, actual stats) without stopping the game
- Deciding what counts as evidence for a visual or structural claim
- Taking before/after screenshots from the same saved camera viewpoint, or an annotated screenshot whose numbered labels map to node paths
- “make this environment beautiful” / reviewing a level like an artist: hero views, a shot sheet after each change, debug views of the weakest shot, zooms into problems
- Finding good camera views automatically (establishing, player eye level, low-angle hero, detail, down a lane)
- After each build stage of a 3D scene and before calling it done - run the scene audit, then frame every error and look item up close
- Open band ends and seams on a facade, a gap in a band, a duct missing its elbow - the audit’s exposed_edge, depth_step and open_fixture_end checks
- Accepting the audit’s look items you judged fine so later audits stop repeating them
Install
summer setup installs every skill for your agent. To add only this one:
claude-code for your agent, such as codex or cursor. See Skills for every option.
The skill
Two signals, two jobs
- Structured state (
summer_world_snapshot,summer_snapshot_diff,summer_get_scene_tree, runtime reads) proves facts: paths, classes, transforms, world AABBs, counts, what changed. - Pixels (
summer_screenshot) prove appearance: composition, scale-to-the-eye, lighting, “does it read as a forest”.
The before/after discipline
Around every mutation batch (script, scene tools, import):- BEFORE:
summer_world_snapshot— keepsnapshot_id. First time in a session, also screenshot so you know the starting state. - Mutate.
- AFTER:
summer_snapshot_diff from_id:<id>(omitto_id— the engine snapshots now). Check the receipt against your INTENT:added= exactly what you meant to add — no more, no less.removed= empty unless you deleted on purpose. A node you created appearing here (or missing fromaddedafter a save) is the ownership bug — seescene-scripting.changed= only nodes you touched. Unexpected entries mean your script had side effects.
- AFTER:
summer_screenshot— and LOOK at it. - Fix before stacking more work on a broken base. An empty diff after a “successful” mutation is a red flag, not a success.
unknown_snapshot means the baseline expired — take a fresh one and redo the pair.
Physical plausibility — check AABBs
summer_world_snapshot carries a world AABB per 3D visual. After placing or importing anything:
- Nothing clips that shouldn’t (compare AABBs of neighbors).
- Nothing floats above or sinks into its support.
- Sizes are real-world plausible: door ≈ 2 units, person ≈ 1.7, car ≈ 4.5. An AABB of 40 on a “chair” is an import-scale bug — pass
target_sizetosummer_instantiate_sceneand re-check.
Audit the scene after each build stage
A screenshot only shows what its camera points at. Problems behind a wall, under a prop or across the street slip past it: a door insert narrower than its frame (you see the street through the gap), a bench turned 90 degrees against the wall, a floor tile with holes over an underlay.summer_scene_audit walks every node in one read-only call and lists where to look.
- After each build stage (a facade, a street, a dressing pass): save, then
summer_scene_audit scenePath:"res://...". It reads the SAVED file in a private offscreen copy, so the open tab never becomes unsaved and nothing is saved. Useroot:"Block3"to audit only what you just built (the rest still counts as surroundings) andchecks:[...]to rerun one check after a fix. - Read the page, not just the counts. Issues come sorted
error,warn,look, each with a node path, a world position, a reason, the evidence numbers (sizes, gaps, angles, the ray that reproduces it) and the next tool. The result is at most 5 KB; follownext_offsetwithoffsetfor the next page, or narrow withmin_severity:"warn".- A check with
partialin its counts ran out of editor time (budget_ms, default 3000) and covered only that share. It is not clean: rerun it alone (checks:["floor_gap"]) or with a largerbudget_msbefore you call that check clean.
- A check with
- Frame every error and look item up close before calling the scene done.
render:"sheet"returns one image of the page’s first 6 issues framed from their open side (tiles labelled#n); otherwisesummer_frame_nodeson the path andsummer_zoominto it. A look item (orientation, a facing question) is never an answer: decide the facing yourself from the asset (summer_inspect_asset). - Classify each item as real or a false positive in your notes. Fix the real ones with the placement tools, then audit again: the same check must come back clean (or the remaining items must be ones you have looked at and accepted).
- Accept the look items you judged fine, once. Pass
accept:[{key, reason}]with each item’skeyfrom the page (a warn can be accepted too, an error never). The audit writes them tores://.summer/audit-accept.json; later audits count them (counts.<check>.accepted) but hide them. An accepted item comes back, markedaccept_stale, when its evidence changes materially (its severity rises or its measured size moves by over 25%), so a fix that made it worse is not hidden.show_accepted:truelists them again.
through_hole: rays pass the wall and reach the far side of the building.floor_gap: what a down ray hits FIRST.- “falls to the void” or “holes in its own mesh”: the floor really is open.
- “covers the floor”: an underlay plane sits above the floor’s own drain or dip. Lower the underlay; the tile is not holed.
- “bare strip … between <tile> and <wall>”: the tile row stops short of the wall.
- Areas are measured (a 4 cm seam is a fraction of a square metre).
floating/sunken: props 2 cm above or 3 cm into their support. Sunken names the surface the prop is buried in, seen from above.interpenetration: over 3 cm, with every piece it cuts (up to 3). Clear all of them, not only the first.exposed_edge: open outline edges of walls, bands, piers and corner blocks that nothing covers within 4-7 mm, seen from walkable space, with the reveal behind them: a 2-60 cm step (an open band end, a module out of line), a seam (the next sheet within 4 cm) or a gap to the next sheet (within 35 cm). A warning on band pieces (and on walls whendepth_stepagrees), a look item on walls. Repeats group per piece type. An open band end at an outside corner: add the corner piece for that band, or wrap one band past the corner by its depth.open_fixture_end: an open end of a run piece (a mesh with two or more open rims of one size: a pipe, duct or gutter section, an elbow, a tee) that nothing joins within 2.5 cm, seen from walkable space: a missing elbow, coupler, section or end piece. A piece with one rim, or rims of different sizes (a cap, a funnel, an outlet), is open by design.depth_step(look): ray rows across each facade at its band levels and every 1.25 m: band recesses and missing band runs, seams, modules standing proud, holes with something behind them. A hole next to athrough_holeconfirms it (that issue becomes an error). Runs a door-like opening covers are skipped.orientation: a long prop near a wall that does not run parallel to it. It never says which way to face.uv_stretch: stretched or collapsed texture an instance shows. A face that inserts normally cover is a warning where it shows.duplicate: the same scene at the same transform.z_fight: coplanar overlapping faces anywhere, including two surfaces of one mesh.- “Coplanar” means closer than twice the 24-bit depth step at the view distance, for the main camera’s near and far.
evshows the gap, the tolerance and the viewpoint. ev.normalis the shared plane’s normal andev.nudgethe axis to move along, in the world and in the piece’s own frame. A window insert can share its head or a jamb with its host, not its front: nudge alongnudge.local, not along the facade normal.- A pair whose material may make it intentional comes back as a look item with the reason:
render_priority, a depth or normal offset, a see-through (alpha) material such as a decal or overlay, or no depth test. Check that it actually renders on top.
- “Coplanar” means closer than twice the 24-bit depth step at the view distance, for the main camera’s near and far.
lights: more lights on a mesh than the renderer’s per-object limit, and hard spot rims.transform: NaN, mirrored, non-uniform scale, far out of bounds, and pieces left at the origin: only an identity LOCAL transform under an identity parent that touches nothing and is not one of a row of siblings. Several pieces sharing that identity transform are a warning each; one alone is a look item. A module whose corner is the world origin is not flagged.resource.budget_ms: the gap detectors run last on the time the other checks leave. On a large scene the default 3000 ms may coverexposed_edge/depth_steponly partly (band pieces and band rows first). Before calling a facade done, rerunchecks:["exposed_edge","depth_step"](or withbudget_ms:6000) when they showpartial.
Choosing the right screenshot
Read the confession warnings in every capture (no camera, no light, synthetic camera, project mismatch, “engine predates camera framing”). They are part of the result.
Stable viewpoints (newer engines)
A preset framing re-fits the scene bounds on every capture, so a before/after pair drifts whenever anything moves. For comparisons that line up, fix the pose:- Bookmark once, reuse forever.
summer_camera_bookmark action:"save" name:"hero"(omitposition/look_atto capture the current editor 3D viewport camera, or pass both as"Vector3(x, y, z)"literals). Then every capture issummer_screenshot target:"scene" framing:"bookmark" bookmark_name:"hero"— same pose, real WorldEnvironment, project-persisted (res://.summer/camera_bookmarks.json), so it survives sessions and machines.action:"list"/"delete"manage them. - One-off pose:
framing:"free"withcamera_position+camera_look_at(+fov). - Name what you see: add
marks:true(cap withmax_marks) and the caption listslabel -> node pathfor the numbered tags drawn over the largest visible 3D nodes. Cite the label AND the path in your claim: “label 3 (Props/Crate_02) floats above the floor” — then fix it by that exact path. Every labelled node gets an occlusion test from the rendered camera; a label noted(hidden behind <path>)sits over whatever is in front of its node, so never cite it. 2D scenes come backmarks_unsupported, not annotated. - Read the confession. An engine that predates these framings echoes the preset it fell back to, and the caption says the frame is NOT pose-stable;
marks:trueon such a build draws nothing. Do not compare, and do not read labels, across that warning.
Environment review: see it like a player and an artist
For “make this place beautiful”, one screenshot is not enough evidence. Preview tools; every image comes back inline (one grid per call), rendered from the SAVED scene with its REAL WorldEnvironment and lights, and nothing in the scene changes. Save the scene before each look.The review loop
- Bookmark the hero views once:
summer_frame_shotfor each shot type that matters (an establishing wide, the player’s eye at spawn, a corridor per street or lane, a low-angle hero of the landmark, a detail of the best prop). Read the score breakdown and the rejection counts; a pose a wall blocks never ranks, and neither does a camera behind or inside a one-sided wall (behind_surface,inside_volume: the renderer does not draw a wall’s back, so that image would look through it). The top 3 are three different views (one per side while the score allows); the light term prefers side or front-side key light, the edge term penalises sky or void below the horizon. - After each change (save first): one
summer_shot_sheetof all hero bookmarks,compare_previous:truewhen judging a change. Each bookmark keeps exactly ONE previous render inres://.summer/shots/<bookmark>.jpg: the compare baseline. Acompare_previous:truerender replaces it after comparing; plain sheets, debug views and screenshots in between leave it alone (passupdate_previous:trueto reset it on purpose). - Take the weakest shot to
summer_debug_views: lighting shows dead-dark areas and missing pools, unshaded shows flat texture and value clashes, wireframe shows floating or duplicated pieces. - Zoom into each problem with
summer_zoombefore fixing it, and again after. Cite the mark or region in the claim. - Find new views with
summer_frame_shotwhenever the layout changes; bookmark the winners so the sheet stays pose-stable.
Beauty rubric (judge every sheet against it)
- Clear focal point: one subject reads first; the eye has somewhere to go.
- Value contrast near versus far: darker, richer foreground; lighter, hazier distance. A flat-grey frame fails.
- Light direction and pools: a readable key direction, and pools of light where the player should look (doors, lamps, paths); lighting view shows them.
- Dressing density at bases and corners: where walls meet the ground, at corners and doorways, props and grime break the hard line. Empty bases read as unfinished.
- No empty frame areas: no void below the horizon, no blank wall or sky filling a third of the frame without purpose (the frame_shot
voidandclearterms flag these). - A level horizon: no accidental roll; the horizon off the dead center for wide shots.
- Foreground framing: something soft in front (foliage, a fence, a pipe, a doorframe) for depth, never covering the subject.
save_to copies; JPEG at most 1024 px, at most 20 MB under res://.summer/shots/ (oldest evicted first); every file there is safe to delete.
Runtime reads during playtests
The edited scene is not the running game. While the game runs:summer_get_runtime_tree— what ACTUALLY spawned (enemies, projectiles, autoloads, pooled nodes). Runtime paths often differ from edited-scene paths.summer_inspect_runtime_node path:"/root/..."— one node’s live properties: actual stats, actual position, actual flags.
game_not_running means exactly that: summer_play, then re-run. For input-driven proof, climb to a RunVerification probe (see the playbook’s rawOpsViaBatch).
Waiting for engine moments
When the next step depends on the engine reaching a moment — the game booting aftersummer_play, a long import finishing, a save landing — wait for the event instead of sleeping and re-polling:
summer_recent_eventsfirst: note itsnext_seq. Events are delivered live from a cursor, so one taken BEFORE the trigger is the only way not to miss a moment that arrives immediately.- Trigger (
summer_play, the import, the save). summer_wait_for_event since:<next_seq> kinds:["play.started"]— or["import.completed"],["scene.saved"],["op.applied","op.failed"]withmatch:{requestId}. During a playtest, wait onscript.errorto catch runtime script errors as they fire.
timed_out: true means no matching event arrived — not that the thing did not happen. Verify with summer_is_running / diagnostics, and never claim an event you did not receive. Engines without the events channel return engine_lacks_events; fall back to the reads above.
Honest-claim rules
- Claim only what a diff, frame, or diagnostics call proved, and cite it: “the diff shows 40 trees added; the camera-framing screenshot shows them lit on the terrain.”
- NEVER describe an image you did not receive. A failed capture is a result — report it and climb down (scene → viewport) or ask the user.
- Preset-framing scene renders are static (t=0), synthetic-camera, flat-environment: no claims about animation, particles, lighting, or mood from them.
- Pass structured failures (
failure_reason,terminalState) through verbatim — never soften them into “it didn’t work”. - After the result is verified, record the outcome with
summer_call{"op":"docs.feedback"}(worked / worked with fixes / wrong / outdated / incomplete + a short note). Optional and fire-and-forget; if telemetry is off, move on.
Red Flags — STOP
Related skills:
scene-scripting carries the mutation loop and ctx API this discipline wraps; playtesting-a-feature and verification-before-completion carry the broader done-claiming rules.
From a shell
No Summer MCP tools in this session? Run each one as a command; whensummer --version is missing or below 4.0, use npx -y summer-engine@latest in place of summer. Fields are flags in kebab-case (--scene-path res://main.tscn) or one --input '<json>', and summer ops describe <op> prints the exact input. The summer-cli skill has the rest.
Related skills
scene-scripting: Build scenes, levels, HUDs and wiring with one editor GDScript (summer_run_script) instead of call chains.playtesting-a-feature: Before calling a gameplay feature done, run the game and walk through it. Static checks do not count.verification-before-completion: Run verification and read its output before claiming work done, fixed or passing: evidence first.
Source:
library/skills/verifying-scenes/SKILL.md, rendered from summer-engine@4.3.0 on npm.
