Skip to main content
multiplayer-testing 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. Test a Summer multiplayer game with Local Play: several players, bad network, bots, a passing run.

When your agent uses it

  • Running or testing a Summer multiplayer game locally with several players (Local Play)
  • Testing multiplayer under latency, jitter or packet loss, or with bots
  • A multiplayer run says ok but nothing networked works, or deciding whether a multiplayer change is done

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

Step 4 of the multiplayer sequence (multiplayer explains the model). Local Play runs a Summer multiplayer game on one machine. It starts the World’s authority scene headless and N clients, and each client joins through the game’s own Summer.client.join. No account, server or extra tools are needed. It can also make the network bad on purpose, which is where multiplayer bugs show.

1. Start Local Play

Pick whichever the session has. Summer MCP tools (they set project settings, then play):
Editor: Debug > Local Multiplayer, pick the player count, press Play. The client windows tile side by side. Terminal (<summer> is the Summer editor executable). Import once after adding scripts so their classes register:

2. Make the network bad

Loopback has no latency, so it hides every timing bug. Always run once with these settings; they apply to every client:
Or put them in project.godot while testing:
Ranges: round trip 0–2000 ms, jitter 0–1000 ms, loss 0–50 %. Each client prints NETWORK_EMULATION round_trip_ms=100 ... when it applies. Try 200 ms too: that is a player on another continent.

3. Read the run

Every output line is prefixed by its process: [authority], [player-1], [player-2]. The last line is the summary:
A run passes only when all of these hold:
  • "ok":true, and every process has "script_errors":0.
  • The game’s own ready lines appear: the authority’s, and one per client after its join Command (COURTYARD_AUTHORITY_READY, COURTYARD_CLIENT_JOINED in the Courtyard example). Print one in your game too.
  • No [SummerNetworkSpawner] cannot attach warning on any process.
  • No SCRIPT ERROR lines.
"ok":true alone only proves that clients got seats. If the composition is invalid, the Spawner logs cannot attach as a warning and nothing networked works, yet every client still counts as joined. The smoke flag (--summer-local-play-smoke) proves the same thing and no more. With MCP, summer_get_diagnostics reports runtime errors from the debugger. Read it after every play; summer_get_console alone misses them.

4. Drive the game with bots

To test gameplay without hands, give the game a bot mode. Arguments after -- reach every process, and {client} becomes 1, 2, … for players and 0 for the authority:
Put the bot in client/bot.gd. It presses the same input actions a player does, so it tests the real controls:
Add it from client/main.gd only when the argument is there, after the player has joined:
Then check the lines it prints. The Courtyard bot walks player 1 to a coin and collects it; player 2 watches:
  • [player-1] BOT_MOVED and [player-2] BOT_SAW report the same position (the observer is up to 100 ms behind while moving, then equal).
  • [player-1] BOT_COLLECT {"ok":true...} and both players print the new score.
Keep bot code in client/: files under test/ or other debug folders are left out of the players’ pack, so the shipped game could not load them. Headless clients have no real window. Guard calls that need one (mouse capture, window size) with DisplayServer.get_name() != "headless".

5. What Local Play cannot show

  • Hosted-only services answer with an error code locally: parties, friends, World chat, the store, client leaderboards and analytics. Their skills list the codes. Make the UI hide or explain them; never crash on them.
  • Matchmaking’s accept prompt. Local Play seats players directly: no proposal, one World per queue.
  • A player leaving for good. A client that quits or calls Summer.client.leave() only disconnects. session_disconnected fires and their seat is held. session_left arrives when the World ends; hosted, it arrives when Summer ends the Session.
  • Which scene the hosted server starts. Local Play starts the authority scene by name. Summer starts the exported server.pck with no scene, so a server preset without the summer_authority feature still passes here and then runs the title screen hosted, where every join fails with world_start_timeout. Run the bundle’s server.pck with no scene (multiplayer-publish, step 4d).
  • A wrong Game ID. Local Play has no hosted identity, so Summer.initialize("some-id") passes here and fails hosted with game_identity_mismatch. Call Summer.initialize() with no ID; only a real online join after publishing proves it.
  • Real devices, real accounts, real distances. Publish to staging for that (multiplayer-publish).
Before a version reaches players, run release-readiness: it covers these gaps on the .zip you upload.

Checklist for any multiplayer change

  • Ran Local Play with at least 2 players, and once with 100 ms / 10 ms / 1 %.
  • Every process: no SCRIPT ERROR, no cannot attach, script_errors 0.
  • The game’s own ready lines appear for the authority and every client.
  • Your own player responds instantly and never snaps while moving normally.
  • Other players move smoothly and end where their owners are.
  • Shared state matches on every client; private state reaches only its owner.
  • A refused Command changes nothing and shows its reason.

Common mistakes

Next

multiplayer-publish: export the summer.games bundle and upload it as a Build.

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.
  • multiplayer: Start here for Summer multiplayer: how authority, clients, Worlds and Sessions fit, and what comes next.
  • multiplayer-project: Set up a Summer multiplayer project: entry scenes, World and queue files, joining, a first Local Play.
  • multiplayer-movement: Movement in Summer multiplayer: owners move instantly, others glide, the authority checks each pose.
  • multiplayer-state: Authority-owned state in Summer multiplayer: shared and private State groups, Commands, Events.
  • multiplayer-publish: Ship a hosted Summer multiplayer game: the bundle, server preset, pre-upload checks, a new Build.
  • release-readiness: Before releasing a Summer game version, prove players can play it: server, online join, progress, files.
  • summer-friends: Summer friends in a game: a consented friends list, profile and chat screens, direct messages. Hosted only.
  • summer-match-results: End a Summer match: report each player’s win, loss or draw, show results on clients, then close the World.
  • summer-matchmaking: Get players into matches with Summer matchmaking: queues, teams, accept prompts, join progress and failures.
  • summer-parties: Let a Summer party play together: the leader presses Play, members follow into the same match. Hosted only.
  • summer-player-data: Save a player’s progress so it follows them to every match: authority load and commit, autosave, safe retries.
  • summer-store: Sell Summer items for Sparks in a multiplayer game: shop, checkout, owned items, authority entitlement checks.
  • summer-world-chat: Add match chat to a Summer game: one World channel shared by everyone in the match. Hosted only.

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