Skip to main content
tune-performance 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. Profile a slow game, find rendering, physics or script hotspots, and propose fixes with expected gains.

When your agent uses it

  • The game is slow, drops framerate, stutters, takes forever to start, or runs poorly on specific hardware
  • Fps drops or stutter when many units, projectiles, or particles spawn at once and the hotspot is unknown

Install

summer setup installs every skill for your agent. To add only this one:
Swap claude-code for your agent, such as codex or cursor. See Skills for every option.

The skill

Overview

Performance tuning without measurement is gambling. This skill enforces a measure-first loop: read diagnostics, identify the dominant cost (rendering / physics / scripting / startup), drill into the specific pattern, propose one fix, verify the metric moved. No shotgun optimization. Core principle: the engine’s diagnostics tell you which subsystem is bleeding. Don’t optimize a different subsystem.

Steps

1. Get the user’s symptom precisely

What’s slow? Pick the closest: framerate drops in scene X / startup is long / freezes for a moment / runs fine on my machine but bad on hardware Y.
Wait for the answer. It narrows where to look:

2. Take a baseline measurement

Don’t guess. Read the engine’s actual numbers. summer_get_diagnostics is NOT a profiler. It returns counts of console errors, debugger errors, debugger warnings and script errors — nothing else. It carries no FPS, no frame time, no draw calls, no body counts. Never quote a performance number you claim came from it. Preferred (Summer MCP): measure inside a verification probe. RunVerification spawns a hidden, disposable game instance with a real renderer, runs your GDScript probe, and dies without touching the user’s editor. Performance.get_monitor(...) inside that probe returns the real numbers.
Write the returned numbers down before fixing anything. Drive the probe to the slow state first (press() / key() to spawn the enemies, walk into the bad room) — measuring the main menu tells you nothing. Let it settle for at least a second before sampling. TIME_FPS is a per-second average, so a probe that reports at 0.5 s returns exactly 1.0 and looks like a catastrophic stall. An fps of exactly 1.0 almost always means you sampled too early, not that the game runs at 1 fps. Never profile with --headless. A headless instance has no renderer: RENDER_TOTAL_DRAW_CALLS_IN_FRAME and RENDER_TOTAL_OBJECTS_IN_FRAME read 0.0, and the FPS figure is an uncapped loop rate, not the game’s framerate. The verify instance is windowed-offscreen precisely so the numbers are real. Fallback (engine not running at all): ask the user to open the editor’s built-in monitors (Debugger → Monitors) or print Performance.get_monitor values in game, and paste the numbers. At minimum: FPS, frame time, draw calls, physics active objects.

3. Identify the dominant cost

The biggest number wins your attention. Don’t optimize the second-biggest — that’s a 5% win when 30% is sitting next to it. Decision tree:
  • Render time > 50% frame budget → rendering hotspot (Step 4a)
  • Physics time > 30% frame budget → physics hotspot (Step 4b)
  • Script time > 30% frame budget → GDScript hotspot (Step 4c)
  • Frame time spikes (jitter) but average is fine → GC / shader compile / async (Step 4d)
  • FPS is fine but startup is slow → import / autoload (Step 4e)

4a. Rendering hotspot

Common rendering costs, ordered by frequency: To inspect a suspect node:
Bakes: know which process can run them. LightmapGI.bake(from_node, image_data_path) IS script-bound on this build — run it from summer_run_script in the live editor (the GPU lightmapper needs a real renderer: desktop editors have one, cloud containers need xvfb + GL per running-in-the-cloud, and pure-headless returns BAKE_ERROR_NO_LIGHTMAPPER). The OccluderInstance3D bake remains editor-button only — say so rather than reporting it as applied. Navmesh baking, collision-shape generation from meshes, and ImporterMesh.generate_lods are all scriptable and remain yours to do.

4b. Physics hotspot

Top patterns:

4c. GDScript hotspot

Per-frame work in _process is the #1 GDScript cost. Read the script, look for:
Fixes:
  • Cache the group lookup in _ready() if the group composition is stable.
  • Move per-frame work to a timer at 10 Hz (_on_timer_timeout), not 60 Hz.
  • Replace distance_to with distance_squared_to in comparisons (no sqrt).
  • Move a truly per-frame hot loop to C# or a GDExtension.

4d. Frame time spikes (jitter)

If FPS is good but frame time spikes:
  • Shader compilation: warm shaders up during a loading screen by rendering each material once off-screen.
  • GC / object allocation: var x := [] inside _process allocates each frame. Pre-allocate.
  • Async resource load: ensure ResourceLoader.load_threaded_request is finished before referencing.
Check summer_get_console for shader-compile warnings.

4e. Startup time

Look for:
  • Long autoload _ready() work (move to call_deferred or background thread).
  • Asset import on first run (cache .import files in source control).
  • preload() of huge scenes at script-parse time — switch to load() on demand.

5. Propose ONE fix with a metric expectation

Don’t bundle five fixes. Pick the highest-leverage one.
The dominant cost is rendering at 14.2 ms / frame (85% of budget). The biggest source is 240 individual MeshInstance3D for foliage. Replacing them with one MultiMeshInstance3D should drop render time to ~3 ms (estimate: 5–7 ms). May I refactor ./World/Foliage to use MultiMeshInstance3D?
Format the proposal as: (a) what the dominant cost is, (b) the specific source, (c) the fix, (d) the expected metric movement, (e) the ask.

6. Apply, re-measure, report

After the user approves:
Re-running the identical probe is the whole point: same instance type, same warm-up, same sample point, so the delta is attributable to the fix and not to how the run was taken. Compare: was the metric movement at least 50% of what was promised? If yes, ship it. If no, the hypothesis was wrong — go back to step 3 with the new numbers.
Before: 14.2 ms render / 22 fps. After: 4.1 ms render / 58 fps. Shipped.

Common mistakes

Anti-patterns from the field

  • “Just disable VSync to see real FPS.” Disabling VSync helps you measure, but performance work targets perceived smoothness (16.7 ms, locked). Re-enable before shipping.
  • Premature C++ rewrite. A GDScript hotpath at 5% of frame time isn’t the bottleneck. Profile first.
  • “It’s slow on user machines, must be the GPU.” Often it’s resolution scale + shadow quality + GI settings. Add a quality preset switcher first.
  • Threading gameplay code. The scene tree isn’t thread-safe. Threading is for asset load, network, AI compute — not _physics_process.

Collaborative protocol

This skill makes scene/resource/code changes. Always ask before each fix is applied. See summer_docs_read id reference/collaborative-protocol.

Want a working starter?

No template — this is a workflow. Performance tuning is project-specific by definition.

See also

  • summer_docs_read id reference/mcp-tools-reference — full MCP tool list
  • summer_docs_read id reference/godot-version — renderer API churn notes (Compositor, RenderSceneBuffers)
  • summer_docs_read id reference/collaborative-protocol — “May I write” pattern
  • summer_docs_read id reference/gd-style — GDScript conventions (avoid bare types, use :=)
  • debug — bug triage (related but different)
  • diagnosing-perf-regressions — when it got slow rather than always was
  • 3d-lighting — light setup and bake decisions

From a shell

No Summer MCP tools in this session? Run each one as a command; when summer --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.
  • running-in-the-cloud: Run Summer Engine on a Linux box with no display: install, launch headless, xvfb, sign in without a browser.
  • scene-scripting: Build scenes, levels, HUDs and wiring with one editor GDScript (summer_run_script) instead of call chains.
  • debug: Disciplined bug, crash and error loop: script errors, console, debugger, hypothesis, fix, verify.
  • 3d-lighting: Light a 3D scene: sun, omni and spot lights, WorldEnvironment, sky, shadows and ambient.
  • diagnosing-perf-regressions: Find why frame rate, frame time or load time got worse since a known-good state.

Source: library/skills/tune-performance/SKILL.md, rendered from summer-engine@4.3.0 on npm.