Skip to main content
multiplayer-movement 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. Movement in Summer multiplayer: owners move instantly, others glide, the authority checks each pose.

When your agent uses it

  • Moving players in a Summer multiplayer game: each player’s own character, other players drawn smoothly
  • Remote players stutter, jitter, teleport or rubberband in a Summer multiplayer game
  • Stopping speed hacks and teleport cheats on player movement; respawning or teleporting a player on purpose

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 2 of the multiplayer sequence. It continues the Courtyard game from multiplayer-project; read that first if network/game.gd does not exist. files.md has every file this step changes, complete, as it ran under Local Play.

How it works

Summer games use client-side movement:
  • The owner runs the whole character controller locally: input, walking, gravity, jumping, collision. Its camera follows instantly, at any latency. About 20 times a second it publishes its pose (position, facing) to an owner-written State group.
  • The authority runs no movement. It checks every pose with an acceptance policy: inside the level, no faster than the game allows, still the same player. A refused pose snaps the owner back to the last accepted one. The authority can also move a player on purpose (respawn, portal).
  • Everyone else receives each accepted pose and draws it 100 ms in the past, gliding between poses, so remote players move smoothly through jitter and lost packets.
Game rules on the authority (pickups, melee, triggers) read the latest accepted pose (multiplayer-state). The pose is bound to the player’s entity, which the Spawner creates for every joined player. The authority creates the pose group when that entity spawns, which is after the player’s network is ready and again after a reconnect. The group ends with the entity.

1. Declare the pose stream

Add a Motion State stream to network/composition.tres, before the Command stream’s sub-resources:
and list it in the [resource]:
Update load_steps in the first line to the number of sub-resources plus one. files.md has the complete file. Field type values are SummerNetworkField.TYPE_*: BOOL 0, INT 1, FLOAT 2, STRING 3, VECTOR2 5, VECTOR3 9, QUATERNION 15, COLOR 19, PACKED_BYTE_ARRAY 20. A pose Dictionary must have exactly the declared fields with exactly those types: no extras, no missing keys, no ints for floats. To send more (an animation state, a carried item), add a field to the schema and to every pose.

2. Shared rules

network/game.gd gains the stream id, the speed limit and the level bounds, after const QUEUE:
and the seat key, the spawn pose and the bounds check, after build_network:
MAX_SPEED must stay above the fastest legitimate speed, falling included. The authority enforces it; the client never reads it.

3. The authority checks every pose

authority/main.gd:
  • create_state_group(stream, key, initial, writer_session, entity) makes that Session the only writer, and ties the group’s life to the entity.
  • set_acceptance_policy runs synchronously on each pose. Return true to accept it unchanged and false to refuse it. It cannot edit a pose; to move a player, call reset() as teleport() does.
  • The distance allowance refills at MAX_SPEED and holds at most half a second of movement. Poses bunched together after a lag spike pass; teleports and speed hacks don’t. A flat per-pose distance check would refuse honest players after every hiccup and still let a cheat move fast in small steps.
  • A seat is the player’s place in the World until session_left. Pose groups are keyed by seat, so a stream needs at most MAX_PLAYERS keys.

4. The client: own player and everyone else

client/main.gd pairs each pose group with its entity. Either can arrive first. Add a waiting dictionary:
connect both signals in _ready() before joining:
and add the handlers at the end:
The client archetype from multiplayer-project already gives each player an owner scene and an observer scene. Give them scripts. client/player_owner.gd, your own player:
Attach it to client/player_owner.tscn (script = ExtResource(...) on the root, as in files.md). client/player_observer.gd, everyone else:
Attach it to client/player_observer.tscn the same way.
  • The owner ignores ordinary installs: with skip_writer_echo it is not sent its own accepted poses. A reset install is the authority moving it: spawn, teleport(), or a refused pose.
  • The owner publishes only changed poses, 20 times a second. The engine keeps only the newest pending pose per player, so a faster rate adds traffic, not smoothness.
  • The observer orders poses by when the authority accepted them (get_timing().state_authority_usec), so a burst of late packets still plays back evenly. A reset is a cut, not a slide.
  • Observer scenes have no collision. Other players are drawings on your screen; their owners decide where they are.

5. Test it under latency

Run Local Play with 2 players at 100 ms round trip, 10 ms jitter and 1 % loss (multiplayer-testing shows how):
  • Your own player responds instantly and never snaps back while moving normally.
  • The other window’s copy of your player moves smoothly and stops exactly where you stopped.
  • Jumping and falling look the same on both screens.
  • No SCRIPT ERROR, no cannot attach, and every process has "script_errors":0.
To check the anti-cheat, move the owner by 30 m in one frame from a test script. The authority refuses the pose, the owner snaps back, and the other client never sees the jump.

Common mistakes

Next

multiplayer-state: scores, doors, private inventories, and actions the authority checks against these poses.

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.

Files in this skill

  • 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-state: Authority-owned state in Summer multiplayer: shared and private State groups, Commands, Events.
  • multiplayer-testing: Test a Summer multiplayer game with Local Play: several players, bad network, bots, a passing run.

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