summer-engine 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,266 @@
1
+ ---
2
+ name: design-level
3
+ description: Use when designing a single level — layout, pacing, encounters, secrets, reward gating. Outputs a level design doc and a concrete node-tree skeleton for `summer_create_scene`. Trigger on "design a level", "design level 1", "tutorial level", "boss arena", "design the encounter", "make a level layout".
4
+ license: MIT
5
+ compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
+ category: level-design
7
+ user-invocable: true
8
+ allowed-tools: Read Grep Glob Write Edit summer_get_scene_tree summer_create_scene summer_open_scene summer_add_node summer_set_prop summer_save_scene summer_get_script_errors
9
+ paths: ["**/*.tscn", "**/*.gd", ".summer/**", "levels/**"]
10
+ ---
11
+
12
+ # /design-level — Design One Level
13
+
14
+ ## Overview
15
+
16
+ A level is a controlled sequence of player experiences in physical space. Layout, pacing, encounters, secrets, and reward gating must work together — a "fun layout" with a flat pacing curve still feels boring. This skill walks the six design beats of a single level and drops a node-tree skeleton ready for `summer_create_scene`.
17
+
18
+ **Core principle:** A level has a *pacing curve* and a *teaching arc*. If you can't draw the tension graph and name what the player learned by the end, the level isn't designed yet.
19
+
20
+ ## Steps
21
+
22
+ ### 1. Pin the level type
23
+
24
+ Ask exactly one question:
25
+
26
+ > What kind of level? Pick one: tutorial / hub / combat encounter / boss / exploration / puzzle / stealth / platforming-challenge / mixed.
27
+
28
+ Each type has different beats. Don't blend types in v1 — a "boss room with stealth and puzzles" is three levels poorly squeezed into one.
29
+
30
+ ### 2. Read context
31
+
32
+ If `.summer/GameSoul.md` exists, read it. The level must serve the brief's core loop and the three mechanics.
33
+
34
+ ```
35
+ Read .summer/GameSoul.md
36
+ ```
37
+
38
+ If `.summer/mechanics/` exists, list the mechanics. Quote them back:
39
+
40
+ > Brief says mechanics are: dash, parry, double-jump. This level should *exercise* at least one and ideally combine two.
41
+
42
+ ### 3. Define the teaching / experience goal
43
+
44
+ Ask plain:
45
+
46
+ > What does the player know by the end of this level that they didn't at the start?
47
+
48
+ Tutorial-level answer: "How to dash." Combat-encounter answer: "That parrying converts damage to mana, and that big enemies need parry, not dodge." Exploration answer: "That this region's secrets are hidden behind double-jumpable gaps."
49
+
50
+ If the user can't answer, the level has no purpose. Say it:
51
+
52
+ > If there's no answer to that question, the level is filler. Either give it a teaching goal, give it a *failure-state* goal (the player must die here to progress), or merge it into the next level.
53
+
54
+ ### 4. Sketch the pacing curve
55
+
56
+ Draw it as text. Five beats, intensity 0-10:
57
+
58
+ ```
59
+ Intro Build Peak Release Hook
60
+ 2 → 5 → 8 → 3 → 6
61
+ ```
62
+
63
+ For a 5-minute encounter level:
64
+
65
+ | Beat | Duration | Intensity | Player feels | Designer plants |
66
+ |---|---|---|---|---|
67
+ | Intro | 30s | 2 | Calm, oriented | Visual landmark for the goal direction |
68
+ | Build | 90s | 5 | Tension rising, small pressure | First weak enemy / first puzzle clue |
69
+ | Peak | 60s | 8 | Stakes high, mistakes hurt | Main encounter / hardest fight |
70
+ | Release | 60s | 3 | Catharsis, loot | Reward + breath room |
71
+ | Hook | 30s | 6 | Curiosity for next | Visible-but-locked secret or path |
72
+
73
+ Show the curve. Ask:
74
+
75
+ > Pacing above. Does the peak land where you want, or does the level need a second wave?
76
+
77
+ ### 5. List encounters and secrets
78
+
79
+ Encounters = explicit player tests (combat, puzzle, platforming). Secrets = optional rewards that respect the pacing curve.
80
+
81
+ State them flat. For a combat encounter level:
82
+
83
+ ```
84
+ Encounters (in player-traversal order):
85
+ 1. E1 — Tutorial enemy (1 weak grunt) at 0:30, in the open atrium. Teaches the basic attack.
86
+ 2. E2 — Forced parry (mid-tier enemy with telegraphed wind-up) at 1:30, in a corridor with no dodge room. Teaches parry.
87
+ 3. E3 — Mixed wave (2 grunts + 1 parry-required) at 3:00, in the peak arena. Tests both.
88
+ 4. E4 — Mini-boss at 4:00 in the chamber. Combines.
89
+
90
+ Secrets (3 total — one per beat after intro):
91
+ - S1 (build beat): Behind a breakable wall in the corridor, holds 50 currency. Teaches "walls can break".
92
+ - S2 (peak beat): Visible from the arena, accessible only via parrying off an enemy. Rewards mastery.
93
+ - S3 (hook beat): Locked door, key in a future level. Plants curiosity for next level.
94
+ ```
95
+
96
+ ### 6. Define reward gating
97
+
98
+ What's behind which gate? Levels feel cheap when they hand out rewards without respecting the difficulty curve.
99
+
100
+ | Reward | Gate | Why |
101
+ |---|---|---|
102
+ | Heal pickup | After E2 | Player just learned parry; reward + safety net before the peak |
103
+ | New ability unlock | After mini-boss | Mastery-pinned; player demonstrates parry before getting upgrade |
104
+ | Currency cache | Behind S1 (breakable wall) | Optional; rewards observation |
105
+ | Story note | At the calm intro | Free; orients the player to the level's narrative |
106
+
107
+ Ask:
108
+
109
+ > Reward gating above. Anything missing or mis-placed?
110
+
111
+ ### 7. Define the layout primitives
112
+
113
+ Every level is built from a small vocabulary of spaces. Name them. Most levels need 4-6 distinct spaces, no more.
114
+
115
+ For a combat-encounter level:
116
+
117
+ ```
118
+ Spaces (node-tree skeleton):
119
+ World (Node3D)
120
+ ├── Geometry (Node3D)
121
+ │ ├── IntroAtrium (Node3D) # open, calm, well-lit
122
+ │ ├── Corridor (Node3D) # tight, forces parry
123
+ │ ├── PeakArena (Node3D) # large, multiple cover, exit visible
124
+ │ └── BossChamber (Node3D) # contained, single exit, dramatic lighting
125
+ ├── Spawns (Node3D)
126
+ │ ├── PlayerSpawn (Node3D) # marker
127
+ │ ├── E1_Spawn (Node3D)
128
+ │ ├── E2_Spawn (Node3D)
129
+ │ ├── E3_Wave_Spawns (Node3D)
130
+ │ └── MiniBoss_Spawn (Node3D)
131
+ ├── Triggers (Node3D)
132
+ │ ├── E2_GateTrigger (Area3D) # locks door behind player on entry
133
+ │ ├── PeakWaveTrigger (Area3D)
134
+ │ └── BossDoorTrigger (Area3D)
135
+ ├── Pickups (Node3D)
136
+ │ ├── HealAfterE2 (Node3D)
137
+ │ └── CurrencyS1 (Node3D)
138
+ ├── Secrets (Node3D)
139
+ │ ├── S1_BreakableWall (StaticBody3D)
140
+ │ ├── S2_ParryLedge (StaticBody3D)
141
+ │ └── S3_LockedDoor (StaticBody3D)
142
+ └── Lighting (Node3D) # WorldEnvironment + DirectionalLight3D
143
+ ```
144
+
145
+ Show the skeleton. Ask:
146
+
147
+ > Tree above. May I create the level scene `levels/<level-name>.tscn` with this skeleton?
148
+
149
+ ### 8. Scaffold via MCP
150
+
151
+ **Preferred (Summer MCP):**
152
+
153
+ ```
154
+ summer_create_scene(path="res://levels/level_01.tscn", root_type="Node3D", root_name="World")
155
+ summer_add_node(parent="./World", type="Node3D", name="Geometry")
156
+ summer_add_node(parent="./World/Geometry", type="Node3D", name="IntroAtrium")
157
+ summer_add_node(parent="./World/Geometry", type="Node3D", name="Corridor")
158
+ summer_add_node(parent="./World/Geometry", type="Node3D", name="PeakArena")
159
+ summer_add_node(parent="./World/Geometry", type="Node3D", name="BossChamber")
160
+ summer_add_node(parent="./World", type="Node3D", name="Spawns")
161
+ summer_add_node(parent="./World/Spawns", type="Node3D", name="PlayerSpawn")
162
+ summer_add_node(parent="./World/Spawns", type="Node3D", name="E1_Spawn")
163
+ # ... and so on
164
+ summer_add_node(parent="./World", type="Node3D", name="Triggers")
165
+ summer_add_node(parent="./World/Triggers", type="Area3D", name="E2_GateTrigger")
166
+ # ... etc.
167
+ summer_add_node(parent="./World", type="Node3D", name="Lighting")
168
+ summer_add_node(parent="./World/Lighting", type="WorldEnvironment", name="WorldEnvironment")
169
+ summer_add_node(parent="./World/Lighting", type="DirectionalLight3D", name="Sun")
170
+ summer_save_scene
171
+ ```
172
+
173
+ This is a **skeleton, not playable geometry**. Markers and Area3Ds, no meshes, no enemy scripts. Implementation in subsequent sessions / templates.
174
+
175
+ **Fallback (no MCP):** Print the `.tscn` file contents for the user to save manually.
176
+
177
+ ### 9. Save the level design doc
178
+
179
+ Always write the design doc — it's the artifact that drives implementation. Path: `.summer/levels/<level-name>.md`.
180
+
181
+ ```markdown
182
+ # Level: <Name>
183
+
184
+ **Type:** combat encounter
185
+ **Duration target:** 5 min
186
+ **Teaching goal:** Player learns parry and that parrying mid-tier enemies is required, not optional.
187
+
188
+ **Pacing curve:** 2 → 5 → 8 → 3 → 6
189
+ **Beats:** Intro (30s) | Build (90s) | Peak (60s) | Release (60s) | Hook (30s)
190
+
191
+ ## Encounters
192
+ - E1 — Tutorial grunt @ 0:30 (IntroAtrium)
193
+ - E2 — Forced parry @ 1:30 (Corridor)
194
+ - E3 — Mixed wave @ 3:00 (PeakArena)
195
+ - E4 — Mini-boss @ 4:00 (BossChamber)
196
+
197
+ ## Secrets
198
+ - S1 — Breakable wall (Corridor) → 50 currency
199
+ - S2 — Parry-ledge (PeakArena) → cosmetic
200
+ - S3 — Locked door (BossChamber) → next level
201
+
202
+ ## Reward Gating
203
+ - Heal pickup after E2
204
+ - Ability unlock after mini-boss
205
+
206
+ ## Spaces
207
+ 1. IntroAtrium — open, calm
208
+ 2. Corridor — tight, forces parry
209
+ 3. PeakArena — large with cover
210
+ 4. BossChamber — contained, dramatic
211
+
212
+ ## Open questions
213
+ <list anything the user couldn't answer>
214
+ ```
215
+
216
+ Ask:
217
+
218
+ > May I create `.summer/levels/<level-name>.md` with this design doc?
219
+
220
+ ### 10. Route to the next step
221
+
222
+ End with:
223
+
224
+ > Skeleton + design doc saved. Next:
225
+ > - `/summer:design-mechanic` to design any mechanics this level requires that aren't designed yet.
226
+ > - `/summer:design-npc` to design the enemies (E1-E4) in detail.
227
+ > - `/summer:art-direction` if the visual style isn't pinned yet.
228
+ > - Or open `levels/<level-name>.tscn` and start blocking out geometry.
229
+
230
+ ## Common mistakes
231
+
232
+ | Mistake | Fix |
233
+ |---|---|
234
+ | No teaching goal | Every level teaches something or fails. State it explicitly. |
235
+ | Flat pacing curve | If intensity stays at 5 the whole time, you have a hallway, not a level. |
236
+ | Too many spaces | 4-6 distinct spaces max. More fragments the player's mental map. |
237
+ | Encounters not tied to mechanics | E2 should *require* parry, not just allow it. Forced use teaches; optional use doesn't. |
238
+ | Secrets ignore the pacing curve | Don't put a major secret in the calm intro — players aren't curious yet. |
239
+ | Reward gating too generous | Free heals before the peak deflate the peak. Gate them after the test. |
240
+ | Blending level types | Stealth + puzzle + boss in one level = three half-baked levels. Pick one. |
241
+ | Building geometry before the skeleton | Skeleton-first lets you iterate on layout cheaply. Geometry comes after the design is locked. |
242
+ | Calling SetResourceProperty on inline sub_resources | Silent fail. See `_shared/mcp-tools-reference.md` § Trap. |
243
+
244
+ ## Collaborative protocol
245
+
246
+ This skill creates a new scene file (`.tscn`) and a design doc (`.md`). Group writes per phase. Always ask before `summer_create_scene`. See `_shared/collaborative-protocol.md`.
247
+
248
+ ## Want a working starter?
249
+
250
+ This is a workflow that produces a skeleton scene. For runnable level examples, see:
251
+
252
+ - `template-id: template-3d-fps` (https://github.com/SummerEngine/FPS-template-Summer-Engine) ships a sample arena.
253
+ - `template-id: template-3d-platformer` (TBD) for jump-based level samples.
254
+
255
+ The skeleton this skill produces is meant to be filled in via `/summer:design-mechanic`, gridmap blockout, or a template merge.
256
+
257
+ ## See also
258
+
259
+ - `_shared/collaborative-protocol.md`
260
+ - `_shared/mcp-tools-reference.md`
261
+ - `_shared/godot-version.md` — Godot 4.5 API notes
262
+ - `scene-and-project/brainstorm-game/SKILL.md` — produces `.summer/GameSoul.md`
263
+ - `gameplay-mechanics/design-mechanic/SKILL.md` — designs the verbs the level exercises
264
+ - `ai-and-npcs/design-npc/SKILL.md` — designs the enemies the encounters reference
265
+ - `rendering-and-lighting/3d-lighting/SKILL.md` — sets the lighting in `WorldEnvironment` + lights
266
+ - `rendering-and-lighting/art-direction/SKILL.md` — defines the visual style the level expresses
@@ -0,0 +1,328 @@
1
+ ---
2
+ name: setup-multiplayer
3
+ description: Use when the user wants to add multiplayer to an existing single-player game — co-op LAN, co-op online, competitive PvP, or lobbies/matchmaking. Strong opinion that you start with Godot's high-level MultiplayerAPI plus MultiplayerSpawner and MultiplayerSynchronizer; only roll custom networking with a justified reason. Walks peer authority, RPC patterns, replication graph, and irreversible architecture decisions. Trigger on "multiplayer", "co-op", "PvP", "online", "netcode", "rollback", "MultiplayerAPI", "host migration", "matchmaking".
4
+ license: MIT
5
+ compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
+ category: multiplayer-and-networking
7
+ user-invocable: true
8
+ allowed-tools: Read Grep Edit Write summer_get_scene_tree summer_inspect_node summer_add_node summer_set_prop summer_connect_signal summer_save_scene summer_get_script_errors summer_project_setting summer_play summer_stop
9
+ paths: ["**/*.gd", "**/*.tscn", "**/*.tres", "project.godot"]
10
+ ---
11
+
12
+ # /setup-multiplayer — Add Multiplayer Without Repainting the Project
13
+
14
+ ## Overview
15
+
16
+ Multiplayer is a one-way door. Pick the wrong authority model and refactoring later is brutal. This skill enforces an opinionated path: **start with Godot's high-level MultiplayerAPI + MultiplayerSpawner + MultiplayerSynchronizer + ENet**. Don't roll custom networking unless you have a measured reason. Make the irreversible decisions up front, in writing.
17
+
18
+ **Core principle:** the architecture decision (peer authority vs server authority) is harder than the code. Lock it before touching a single RPC.
19
+
20
+ ## Steps
21
+
22
+ ### 1. Ask the four blocking questions
23
+
24
+ Before any code:
25
+
26
+ > 1. What kind of multiplayer? co-op LAN / co-op online / competitive PvP / lobbies + matchmaking
27
+ > 2. How many players? 2 / 2–4 / 8+
28
+ > 3. Who runs the simulation? host (one of the players) / dedicated server / authoritative peer-to-peer
29
+ > 4. What's the network you're targeting? same Wi-Fi / public internet with ~80ms RTT / mobile
30
+
31
+ Wait. Each answer flips an irreversible knob.
32
+
33
+ ### 2. Pick the authority model — irreversible decision
34
+
35
+ **Lock it now.** Refactoring authority is a project-killer.
36
+
37
+ | Model | Best for | Pros | Cons |
38
+ |---|---|---|---|
39
+ | **Host authority** (one player runs the sim) | Co-op (2–4 players), casual PvP, friends-only | Cheapest. No server bill. Host migration possible. | Host advantage (no lag for them). Cheating possible. |
40
+ | **Dedicated server authority** | Competitive PvP, matchmaking, > 4 players | Fair. Anti-cheat possible. Stable. | Server bill. Need a deploy story. Higher complexity. |
41
+ | **Peer-to-peer with shared authority** | Lock-step strategy / RTS, fighting games (rollback) | Symmetric latency. No "host advantage". | Determinism is a project on its own. Hard to debug. |
42
+
43
+ **Default recommendation:** host authority for co-op (2–4 players), dedicated server for everything else. If user says "competitive PvP" + "lobbies": dedicated.
44
+
45
+ State the choice explicitly:
46
+
47
+ > Going with **host authority** for 2–4 player co-op. The first player to start a game is the host. Their scene tree is the source of truth; clients receive replicated state and send input RPCs. Host migration is hard with this model — if the host quits, the game ends. Confirm?
48
+
49
+ ### 3. Pick the transport
50
+
51
+ Almost always: **ENetMultiplayerPeer**. It's UDP with reliability layers, ships with Godot, and just works for < 32 peers.
52
+
53
+ When to switch:
54
+ - **WebSocketMultiplayerPeer**: if you need the game to run in the browser (HTML5 export). ENet doesn't work in browsers.
55
+ - **WebRTCMultiplayerPeer**: if you need browser AND P2P (no central server). Higher complexity.
56
+ - **Custom transport**: never, unless you've shipped multiplayer before.
57
+
58
+ ### 4. Pick what to replicate — the replication graph
59
+
60
+ Replicate the *minimum*. Every replicated field is bandwidth + bug surface.
61
+
62
+ For a typical co-op shooter:
63
+
64
+ | Object | Replicated state | Replication frequency | Authority |
65
+ |---|---|---|---|
66
+ | Player position / rotation | x, y, z, yaw | 20 Hz | Owner peer |
67
+ | Player health | int | On change | Server/host |
68
+ | Player input intent | move_dir, jump, fire | 30 Hz | Owner peer (sends to host) |
69
+ | Enemy state | position, hp, FSM state | 10 Hz | Host only |
70
+ | World items (pickups) | spawn / despawn events | On change | Host only |
71
+ | UI / menus | nothing | nothing | Local only |
72
+ | Particles, decorative VFX | nothing | nothing | Local only (each peer plays its own) |
73
+
74
+ **Don't replicate:**
75
+ - Local UI state.
76
+ - Cosmetic-only VFX (each peer simulates locally).
77
+ - Camera / view position (each peer has their own camera).
78
+ - Audio (use 3D audio with replicated positions, not replicate the AudioStreamPlayer).
79
+
80
+ ### 5. Read the scene to find what changes
81
+
82
+ ```
83
+ summer_get_scene_tree
84
+ summer_inspect_node "./World/Player"
85
+ ```
86
+
87
+ Note: the existing single-player Player needs to become a per-peer Player. The path is to spawn one Player per connected peer.
88
+
89
+ ### 6. Propose the change set
90
+
91
+ > I'm about to:
92
+ > - Add a `Network` autoload (`scripts/network.gd`) that owns peer setup + spawn lifecycle.
93
+ > - Convert `./World/Player` from a static scene node into a `MultiplayerSpawner`-managed instance, spawned per peer on join.
94
+ > - Add a `MultiplayerSynchronizer` to the player scene replicating `position`, `rotation.y`, `health`.
95
+ > - Convert player input from local → input intent RPC sent to the authority.
96
+ > - Add a basic host/join UI on the main menu.
97
+ >
98
+ > Authority model: host. Transport: ENet. Tick: 30 Hz input, 20 Hz position. Confirm before I touch anything?
99
+
100
+ ### 7. Build it
101
+
102
+ #### a) Network autoload
103
+
104
+ `scripts/network.gd`:
105
+
106
+ ```gdscript
107
+ class_name Network
108
+ extends Node
109
+
110
+ const PORT := 7000
111
+ const MAX_PLAYERS := 4
112
+
113
+ signal player_joined(peer_id: int)
114
+ signal player_left(peer_id: int)
115
+
116
+ @export var player_scene: PackedScene
117
+
118
+ var _peer: MultiplayerPeer
119
+
120
+ func host_game() -> Error:
121
+ var enet := ENetMultiplayerPeer.new()
122
+ var err := enet.create_server(PORT, MAX_PLAYERS - 1)
123
+ if err != OK:
124
+ return err
125
+ _peer = enet
126
+ multiplayer.multiplayer_peer = _peer
127
+ multiplayer.peer_connected.connect(_on_peer_connected)
128
+ multiplayer.peer_disconnected.connect(_on_peer_disconnected)
129
+ # Host spawns its own player at id=1 (server id)
130
+ _spawn_player(1)
131
+ return OK
132
+
133
+ func join_game(address: String) -> Error:
134
+ var enet := ENetMultiplayerPeer.new()
135
+ var err := enet.create_client(address, PORT)
136
+ if err != OK:
137
+ return err
138
+ _peer = enet
139
+ multiplayer.multiplayer_peer = _peer
140
+ return OK
141
+
142
+ func _on_peer_connected(peer_id: int) -> void:
143
+ # Only the host spawns players. Clients receive via MultiplayerSpawner.
144
+ if multiplayer.is_server():
145
+ _spawn_player(peer_id)
146
+ player_joined.emit(peer_id)
147
+
148
+ func _on_peer_disconnected(peer_id: int) -> void:
149
+ var node_name := str(peer_id)
150
+ var players := get_tree().get_first_node_in_group("players_root")
151
+ if players != null and players.has_node(node_name):
152
+ players.get_node(node_name).queue_free()
153
+ player_left.emit(peer_id)
154
+
155
+ func _spawn_player(peer_id: int) -> void:
156
+ var player: Node = player_scene.instantiate()
157
+ player.name = str(peer_id) # name = peer id; sync by name
158
+ player.set_multiplayer_authority(peer_id)
159
+ var players_root := get_tree().get_first_node_in_group("players_root")
160
+ players_root.add_child(player, true)
161
+ ```
162
+
163
+ Register as autoload in `project.godot`:
164
+
165
+ ```
166
+ summer_project_setting(name="autoload/Network", value="*res://scripts/network.gd")
167
+ ```
168
+
169
+ #### b) Player scene with MultiplayerSynchronizer
170
+
171
+ In the player scene, add:
172
+
173
+ ```
174
+ Player (CharacterBody3D)
175
+ ├── CollisionShape3D
176
+ ├── Camera3D # local-only, attach in script if peer is local
177
+ ├── MeshInstance3D
178
+ └── MultiplayerSynchronizer
179
+ ```
180
+
181
+ ```
182
+ summer_add_node(parent="./World/Players/Player", type="MultiplayerSynchronizer", name="Sync")
183
+ ```
184
+
185
+ Configure the synchronizer to replicate `position`, `rotation.y`, `health`. Save as a `SceneReplicationConfig.tres` (NOT inline sub_resource — silent-fail trap):
186
+
187
+ ```gdscript
188
+ # scripts/player_net.gd
189
+ extends CharacterBody3D
190
+
191
+ @export var move_speed: float = 5.0
192
+ @export var max_health: int = 100
193
+
194
+ @onready var camera: Camera3D = $Camera3D
195
+ @onready var sync: MultiplayerSynchronizer = $Sync
196
+
197
+ var health: int = 100
198
+
199
+ func _ready() -> void:
200
+ health = max_health
201
+ var auth := get_multiplayer_authority()
202
+ var is_local := auth == multiplayer.get_unique_id()
203
+ camera.current = is_local
204
+ set_physics_process(is_local) # only the owner runs movement
205
+ set_process_input(is_local)
206
+
207
+ func _physics_process(delta: float) -> void:
208
+ var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
209
+ var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
210
+ if direction:
211
+ velocity.x = direction.x * move_speed
212
+ velocity.z = direction.z * move_speed
213
+ else:
214
+ velocity.x = move_toward(velocity.x, 0, move_speed)
215
+ velocity.z = move_toward(velocity.z, 0, move_speed)
216
+ if not is_on_floor():
217
+ velocity.y -= 9.8 * delta
218
+ move_and_slide()
219
+ # position is replicated automatically via MultiplayerSynchronizer
220
+
221
+ @rpc("any_peer", "call_local", "reliable")
222
+ func take_damage(amount: int) -> void:
223
+ if not multiplayer.is_server():
224
+ return # only host applies damage
225
+ health = max(0, health - amount)
226
+ if health == 0:
227
+ rpc("on_died")
228
+
229
+ @rpc("authority", "call_local", "reliable")
230
+ func on_died() -> void:
231
+ queue_free()
232
+ ```
233
+
234
+ #### c) RPC patterns — the four flavors
235
+
236
+ Lock the conventions:
237
+
238
+ | RPC config | Use case |
239
+ |---|---|
240
+ | `@rpc("authority", "call_local", "reliable")` | Host tells everyone something happened (health change result, death) |
241
+ | `@rpc("any_peer", "call_remote", "unreliable")` | Frequent input intent (movement, look) — drop is OK |
242
+ | `@rpc("any_peer", "call_remote", "reliable")` | Rare input event (fire weapon, pick up item) — drop NOT OK |
243
+ | `@rpc("any_peer", "call_local", "reliable")` | Player asks host to do something (request damage) |
244
+
245
+ **Authority validation is mandatory.** Inside any `any_peer` RPC, check `multiplayer.get_remote_sender_id()` and validate the sender owns the action.
246
+
247
+ ```gdscript
248
+ @rpc("any_peer", "call_local", "reliable")
249
+ func request_fire() -> void:
250
+ var sender := multiplayer.get_remote_sender_id()
251
+ if sender != get_multiplayer_authority():
252
+ return # someone else is trying to control this player; drop
253
+ # ... actually fire
254
+ ```
255
+
256
+ #### d) Lag compensation outline
257
+
258
+ Defer to a specialist skill, but flag the basics:
259
+
260
+ - For projectiles: spawn locally on owner's machine at fire time, replicate the spawn event with a timestamp. Other peers fast-forward the projectile by their RTT.
261
+ - For hit detection: server-authoritative + simple latency tolerance (host accepts hits within ~100ms past server time).
262
+ - For movement: client-side prediction + server reconciliation is the standard model. Godot doesn't ship this — you write it. If users ask for "competitive PvP shooter", say: this is a deeper specialist topic and link `multiplayer-and-networking/lag-compensation/SKILL.md` when it ships.
263
+
264
+ ### 8. Save and verify
265
+
266
+ ```
267
+ summer_save_scene
268
+ summer_get_script_errors
269
+ summer_play
270
+ # user tests by running two instances locally (Project → Run Multiple Instances → 2)
271
+ summer_stop
272
+ ```
273
+
274
+ Test checklist:
275
+ - [ ] Two instances connect (one hosts, one joins).
276
+ - [ ] Each instance sees its own player as local (camera attached, can move).
277
+ - [ ] Each instance sees the other player move smoothly (sync at 20 Hz).
278
+ - [ ] Damage RPC routes through host (not double-applied on each peer).
279
+ - [ ] Disconnect cleans up the disconnected player's node.
280
+
281
+ ## Common mistakes
282
+
283
+ | Don't | Do | Why |
284
+ |---|---|---|
285
+ | Decide authority model halfway through | Lock it in step 2 before touching code | Refactoring authority is a project-killer |
286
+ | Replicate every property | Replicate position, rotation, health, that's it | Bandwidth + bug surface |
287
+ | Run physics on every peer for every player | Physics on owner only, others receive transform | Otherwise jitter and divergence |
288
+ | Trust the client | Validate `get_remote_sender_id()` in every `any_peer` RPC | Cheaters route through the wrong RPC |
289
+ | Inline `SceneReplicationConfig` sub_resource | Save as standalone `.tres` | Silent-fail trap (see `_shared/mcp-tools-reference.md`) |
290
+ | Roll custom transport | ENet (or WebSocket for browser) | You will hit edge cases ENet already solved |
291
+ | Skip authority check in damage RPC | `if not multiplayer.is_server(): return` at the top of damage handlers | Otherwise damage applied N times |
292
+ | Spawn players on every peer | MultiplayerSpawner — host spawns, clients receive | Otherwise N copies of each player |
293
+ | `@rpc("any_peer")` everything | Use `authority` for server→clients, `any_peer` only for client→server | Mismatched flow = exploits |
294
+ | Camera replicated | Camera is local-only, set `current = is_local` on spawn | Each peer needs their own camera |
295
+
296
+ ## Anti-patterns
297
+
298
+ - **"Let's also have rollback netcode for our co-op shooter."** Rollback is a 6-month project on its own. Use it for fighting games and lock-step RTS, not co-op.
299
+ - **NAT punchthrough on day one.** Use Steam Networking or a relay; don't write punchthrough yourself for v1.
300
+ - **Dedicated servers without a deploy story.** If you can't redeploy in < 5 min, the dedicated path isn't ready.
301
+ - **Custom serialization.** Godot's variant serialization is fine. Don't write your own packing until you measure bandwidth.
302
+ - **Host migration in v1.** It's a feature, not a primitive. Ship without it; add later if churn data demands.
303
+
304
+ ## Collaborative protocol
305
+
306
+ This skill rewires the project at the architectural level. Always ask before each block of changes. Group writes — "I'm about to add the autoload + 2 scenes + 3 scripts + InputMap entries. OK?". See `_shared/collaborative-protocol.md`.
307
+
308
+ ## Want a working starter?
309
+
310
+ For a known-good multiplayer scaffold (host + join UI, ENet transport, MultiplayerSpawner, replicated player), point users at:
311
+
312
+ → **template-id**: TBD (`template-co-op-online` planned, see `_shared/template-registry.md`)
313
+
314
+ For now: this skill produces the scaffold inline. When the template lands, link from here.
315
+
316
+ ## See also
317
+
318
+ - `_shared/mcp-tools-reference.md` — full MCP tool list (project-setting, autoload registration)
319
+ - `_shared/godot-version.md` — `MultiplayerAPI` is medium-churn; `SceneReplicationConfig` settled in 4.0+
320
+ - `_shared/collaborative-protocol.md` — "May I write" pattern
321
+ - `_shared/gd-style.md` — typed GDScript conventions
322
+ - `multiplayer-and-networking/multiplayerapi-basics/SKILL.md` — deeper MultiplayerAPI patterns
323
+ - `multiplayer-and-networking/client-server-pattern/SKILL.md` — dedicated server architecture
324
+ - `multiplayer-and-networking/peer-replication/SKILL.md` — synchronizer config patterns
325
+ - `multiplayer-and-networking/enet-vs-websocket/SKILL.md` — transport choice
326
+ - `multiplayer-and-networking/lag-compensation/SKILL.md` — deeper netcode for competitive (when shipped)
327
+ - `multiplayer-and-networking/state-sync/SKILL.md` — selective replication patterns
328
+ - `multiplayer-and-networking/lobby-and-matchmaking/SKILL.md` — lobby UI + Steam matchmaking