@ericrisco/rsc 0.1.34 → 0.1.36

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.
Files changed (130) hide show
  1. package/README.md +28 -2
  2. package/manifest.json +241 -2
  3. package/package.json +1 -1
  4. package/scripts/lib/domains.js +1 -0
  5. package/scripts/lib/recommend.js +4 -0
  6. package/skills/agent-eval/SKILL.md +1 -1
  7. package/skills/analyze/SKILL.md +1 -1
  8. package/skills/brand-voice/SKILL.md +1 -1
  9. package/skills/brand-voice/references/voice-guide-template.md +14 -0
  10. package/skills/building-agents/SKILL.md +2 -2
  11. package/skills/competitor-watch/SKILL.md +9 -4
  12. package/skills/competitor-watch/references/tracker-schema.md +19 -0
  13. package/skills/constitution/references/constitution-template.md +10 -0
  14. package/skills/content-engine/SKILL.md +1 -1
  15. package/skills/content-engine/references/brief-and-pipeline.md +15 -1
  16. package/skills/course-builder/references/grounding-and-scoping.md +23 -0
  17. package/skills/course-storytelling/references/learner-grounding.md +22 -2
  18. package/skills/decision-records/SKILL.md +14 -1
  19. package/skills/decision-records/references/templates.md +20 -0
  20. package/skills/design/references/brand-grounding.md +11 -1
  21. package/skills/game-design/SKILL.md +170 -0
  22. package/skills/game-design/evals/README.md +13 -0
  23. package/skills/game-design/evals/cases.yaml +47 -0
  24. package/skills/game-design/references/mechanics-and-loops.md +78 -0
  25. package/skills/game-design/references/playtesting-and-scope.md +58 -0
  26. package/skills/game-design/references/progression-and-economy.md +70 -0
  27. package/skills/game-storytelling/SKILL.md +159 -0
  28. package/skills/game-storytelling/evals/README.md +28 -0
  29. package/skills/game-storytelling/evals/cases.yaml +51 -0
  30. package/skills/game-storytelling/references/branching-and-state.md +134 -0
  31. package/skills/game-storytelling/references/dialogue-tools.md +117 -0
  32. package/skills/game-storytelling/references/environmental-storytelling.md +71 -0
  33. package/skills/gamedev-multiplayer/SKILL.md +250 -0
  34. package/skills/gamedev-multiplayer/evals/README.md +12 -0
  35. package/skills/gamedev-multiplayer/evals/cases.yaml +44 -0
  36. package/skills/gamedev-multiplayer/references/godot-high-level-multiplayer.md +133 -0
  37. package/skills/gamedev-multiplayer/references/prediction-and-latency.md +104 -0
  38. package/skills/gamedev-multiplayer/references/unity-and-unreal-netcode.md +166 -0
  39. package/skills/gamedev-pathing/SKILL.md +242 -0
  40. package/skills/gamedev-pathing/evals/README.md +14 -0
  41. package/skills/gamedev-pathing/evals/cases.yaml +49 -0
  42. package/skills/gamedev-pathing/references/navmesh-workflow.md +110 -0
  43. package/skills/gamedev-pathing/references/search-algorithms.md +135 -0
  44. package/skills/gamedev-pathing/references/steering-and-avoidance.md +120 -0
  45. package/skills/gamedev-physics/SKILL.md +225 -0
  46. package/skills/gamedev-physics/evals/README.md +12 -0
  47. package/skills/gamedev-physics/evals/cases.yaml +54 -0
  48. package/skills/gamedev-physics/references/character-controllers.md +120 -0
  49. package/skills/gamedev-physics/references/determinism-and-queries.md +124 -0
  50. package/skills/gamedev-physics/references/layers-and-masks.md +115 -0
  51. package/skills/gamedev-shaders/SKILL.md +252 -0
  52. package/skills/gamedev-shaders/evals/README.md +14 -0
  53. package/skills/gamedev-shaders/evals/cases.yaml +45 -0
  54. package/skills/gamedev-shaders/references/effect-recipes.md +205 -0
  55. package/skills/gamedev-shaders/references/godot-shading-language.md +133 -0
  56. package/skills/gamedev-shaders/references/unity-and-unreal-shaders.md +169 -0
  57. package/skills/gamedev-shipping/SKILL.md +236 -0
  58. package/skills/gamedev-shipping/evals/README.md +9 -0
  59. package/skills/gamedev-shipping/evals/cases.yaml +55 -0
  60. package/skills/gamedev-shipping/references/mobile-and-desktop.md +127 -0
  61. package/skills/gamedev-shipping/references/stores-and-ci.md +162 -0
  62. package/skills/gamedev-shipping/references/web-export-headers.md +140 -0
  63. package/skills/godot/SKILL.md +319 -0
  64. package/skills/godot/evals/README.md +13 -0
  65. package/skills/godot/evals/cases.yaml +50 -0
  66. package/skills/godot/references/export-and-testing.md +102 -0
  67. package/skills/godot/references/gdscript-style.md +84 -0
  68. package/skills/godot/references/godot3-to-4-traps.md +89 -0
  69. package/skills/godot/references/nodes-scenes-resources.md +109 -0
  70. package/skills/harness/SKILL.md +3 -3
  71. package/skills/harness/references/daily-curation-automation.md +8 -6
  72. package/skills/harness/references/obsidian-scaffolding.md +16 -9
  73. package/skills/harness/references/wiki-article-template.md +31 -14
  74. package/skills/harness/references/wiki-index-template.md +7 -3
  75. package/skills/harness/references/wiki-protocol.md +120 -54
  76. package/skills/harness/references/wiki-worklog-template.md +5 -1
  77. package/skills/instagram-api/SKILL.md +8 -1
  78. package/skills/instagram-api/references/insights-metrics.md +8 -1
  79. package/skills/knowledge-ops/SKILL.md +9 -9
  80. package/skills/knowledge-ops/evals/cases.yaml +1 -1
  81. package/skills/knowledge-ops/references/gardening-playbook.md +18 -8
  82. package/skills/level-design/SKILL.md +175 -0
  83. package/skills/level-design/evals/README.md +35 -0
  84. package/skills/level-design/evals/cases.yaml +58 -0
  85. package/skills/level-design/references/blockout-to-art.md +118 -0
  86. package/skills/level-design/references/guidance-and-readability.md +152 -0
  87. package/skills/level-design/references/pacing-and-encounters.md +142 -0
  88. package/skills/linkedin-api/SKILL.md +10 -1
  89. package/skills/linkedin-strategy/SKILL.md +12 -0
  90. package/skills/linkedin-strategy/references/wiki-records.md +27 -1
  91. package/skills/marketing/references/brand-grounding.md +22 -2
  92. package/skills/medium-strategy/SKILL.md +12 -0
  93. package/skills/medium-strategy/references/wiki-records.md +39 -1
  94. package/skills/medium-writing/SKILL.md +19 -0
  95. package/skills/plan/references/plan-template.md +12 -1
  96. package/skills/roast-me/tools/adapters/__pycache__/__init__.cpython-312.pyc +0 -0
  97. package/skills/roast-me/tools/adapters/__pycache__/base.cpython-312.pyc +0 -0
  98. package/skills/roast-me/tools/adapters/__pycache__/claude.cpython-312.pyc +0 -0
  99. package/skills/roast-me/tools/adapters/__pycache__/codex.cpython-312.pyc +0 -0
  100. package/skills/roast-me/tools/adapters/__pycache__/gemini.cpython-312.pyc +0 -0
  101. package/skills/roast-me/tools/adapters/__pycache__/registry.cpython-312.pyc +0 -0
  102. package/skills/shortform-packaging/SKILL.md +7 -2
  103. package/skills/shortform-packaging/references/package-templates.md +13 -2
  104. package/skills/shortform-strategy/SKILL.md +30 -6
  105. package/skills/shortform-strategy/references/learning-loop-template.md +9 -1
  106. package/skills/specify/references/spec-template.md +12 -1
  107. package/skills/tiktok-api/SKILL.md +6 -2
  108. package/skills/tiktok-api/references/wiki-schema.md +38 -4
  109. package/skills/unity/SKILL.md +282 -0
  110. package/skills/unity/evals/README.md +8 -0
  111. package/skills/unity/evals/cases.yaml +43 -0
  112. package/skills/unity/references/build-and-platforms.md +65 -0
  113. package/skills/unity/references/input-system.md +102 -0
  114. package/skills/unity/references/lifecycle-deep-dive.md +75 -0
  115. package/skills/unreal/SKILL.md +270 -0
  116. package/skills/unreal/evals/README.md +12 -0
  117. package/skills/unreal/evals/cases.yaml +45 -0
  118. package/skills/unreal/references/blueprint-cpp-parity.md +109 -0
  119. package/skills/unreal/references/gameplay-framework.md +95 -0
  120. package/skills/unreal/references/packaging.md +83 -0
  121. package/skills/verify/SKILL.md +11 -1
  122. package/skills/youtube-api/SKILL.md +7 -2
  123. package/skills/youtube-api/references/wiki-schema.md +25 -4
  124. package/skills/youtube-packaging/SKILL.md +10 -2
  125. package/skills/youtube-packaging/references/description-and-chapters.md +70 -2
  126. package/skills/youtube-strategy/SKILL.md +9 -2
  127. package/skills/youtube-strategy/references/wiki-records.md +39 -6
  128. package/skills/youtube-strategy/scripts/verify.sh +4 -0
  129. package/skills/youtube-thumbnails/SKILL.md +10 -5
  130. package/skills/youtube-thumbnails/references/experiment-log-format.md +28 -2
@@ -0,0 +1,250 @@
1
+ ---
2
+ name: gamedev-multiplayer
3
+ description: "Use when adding multiplayer, netcode, or replication to a game — syncing players/state across a network, online co-op or PvP, client-server vs peer-to-peer, server-authoritative design, client-side prediction + server reconciliation, snapshot interpolation, lag compensation, tick/netrate, and per-engine wiring: Godot 4.x MultiplayerSpawner/MultiplayerSynchronizer/@rpc/ENetMultiplayerPeer/set_multiplayer_authority, Unity Netcode for GameObjects (NetworkObject/NetworkBehaviour/NetworkVariable/[Rpc]/ServerRpc/ClientRpc), Unreal Actor replication (Replicated/ReplicatedUsing, Server/Client/NetMulticast RPCs, relevancy). Triggers: 'how do I sync players', 'client and server desync', 'players rubber-band or teleport', 'who has authority', 'laggy movement', 'cheater set their own health'. NOT single-player gameplay, AI, or input (route to engine skill godot/unity/unreal), and NOT matchmaking, dedicated-server hosting, or backend infra (route to deployment)."
4
+ tags: [multiplayer, netcode, replication, networking, prediction, authority]
5
+ recommends: [godot, unity, unreal, gamedev-physics]
6
+ profiles: [full]
7
+ origin: risco
8
+ ---
9
+
10
+ # Game multiplayer & netcode
11
+
12
+ Design and wire the network layer of a game across Godot 4.x, Unity, and Unreal: choose a
13
+ topology, put authority in the right place, replicate the right state, and hide latency without
14
+ opening the door to cheaters. This skill owns the *networking* decisions; the engine skills own
15
+ the local gameplay those decisions sit on top of.
16
+
17
+ ## Version contract — read first
18
+
19
+ Target the current APIs. **Never emit these deprecated / removed APIs:**
20
+
21
+ | Engine | Target | Banned (removed/legacy) | Emit instead |
22
+ |---|---|---|---|
23
+ | **Godot** | 4.x | `master`/`puppet`/`remote`/`sync` keywords, `rpc_config()` as a func, `rset`, `NetworkedMultiplayerENet` | `@rpc(...)` annotation, `set_multiplayer_authority()`, `ENetMultiplayerPeer`, `MultiplayerSynchronizer` |
24
+ | **Unity** | Netcode for GameObjects (NGO) 1.x/2.x | UNet / HLAPI: `NetworkIdentity`, `[Command]`, `[SyncVar]`, `NetworkServer`, `UnityEngine.Networking` | NGO `NetworkObject`, `NetworkBehaviour`, `NetworkVariable<T>`, `[Rpc(SendTo.X)]` |
25
+ | **Unreal** | 5.x | replicating by polling in `Tick`, skipping `WithValidation` on gameplay RPCs | `UPROPERTY(Replicated / ReplicatedUsing=...)` + `GetLifetimeReplicatedProps`, `Server`/`Client`/`NetMulticast` `UFUNCTION`s |
26
+
27
+ UNet is **removed** from modern Unity — do not scaffold it. If a repo genuinely uses Mirror (a
28
+ community UNet-like fork), say so; this skill targets first-party NGO. In NGO 2.x the unified
29
+ `[Rpc(SendTo.Server)]` / `[Rpc(SendTo.ClientsAndHost)]` attribute is preferred; the older
30
+ `[ServerRpc]` / `[ClientRpc]` still compile (and enforce `…ServerRpc` / `…ClientRpc` method-name
31
+ suffixes) — cover them but prefer the unified form on new code.
32
+
33
+ ## Fires on / When NOT
34
+
35
+ **Fires on:** "how do I sync players", replication, netcode, RPCs, authority, online co-op or PvP,
36
+ "client and server disagree", rubber-banding / teleporting players, prediction & reconciliation,
37
+ lag compensation, tick rate / send rate, spawning networked objects, "the client can cheat".
38
+
39
+ **When NOT (route away):**
40
+
41
+ - Single-player movement, AI, input, animation, save files → the engine skill: `godot`, `unity`, `unreal`.
42
+ - Physics determinism, collision, character controllers → `gamedev-physics` (netcode *consumes* it).
43
+ - Matchmaking service, lobby backend, dedicated-server hosting/orchestration, relays, DB → `deployment`.
44
+ - Web realtime (chat, presence, generic WebSocket app) that is not a game world → `webhooks` / a backend skill.
45
+
46
+ ## Topologies & authority
47
+
48
+ Pick the topology first; everything else follows from it.
49
+
50
+ - **Client-server, dedicated server** — a headless authoritative process no player controls. The gold
51
+ standard for competitive/persistent games: one source of truth, hardest to cheat, costs money to run.
52
+ - **Client-server, listen server** — one player's machine is also the server (host). Cheap, zero infra,
53
+ but the host has a latency advantage and can tamper; fine for co-op and casual PvP. **Design so the
54
+ same code runs on both** (a listen server is a dedicated server that also has a local client).
55
+ - **Peer-to-peer** — no central authority; peers exchange state directly. Low infra cost and low latency
56
+ between peers, but NAT traversal is painful, it is the easiest to cheat, and it needs lockstep or a
57
+ rollback model to stay consistent. Reasonable for small trusted lobbies or deterministic fighting games.
58
+
59
+ **Server-authoritative is the default.** The server owns the truth; clients send *intent* (inputs,
60
+ requests), never results. **Never trust the client:** the client cannot set its own health, position,
61
+ score, currency, or hit results — it *asks*, the server *decides*, everyone else *observes*. A client
62
+ that says "I moved to X / I did 40 damage / I have 999 gold" is either lagging or cheating; treat both
63
+ the same. The one thing a client legitimately owns is its own input and, by convention, cosmetic-only
64
+ state. See references/prediction-and-latency.md for how to keep this responsive.
65
+
66
+ ## State replication vs RPCs
67
+
68
+ Two complementary tools — use the right one:
69
+
70
+ - **State replication** = *continuous* synchronized values (health, position, ammo, team). The framework
71
+ watches a variable and pushes changes to relevant peers; late-joiners get the current value automatically.
72
+ Use for anything a client needs the *latest* of. It is idempotent and self-healing on packet loss.
73
+ - **RPCs (remote procedure calls)** = *one-shot events* ("fire weapon", "play explosion at P", "open door").
74
+ Fire-and-forget; a dropped unreliable RPC is simply lost. Use for events, **never** for state you need to
75
+ stay consistent — a missed "you died" RPC leaves a client desynced forever; a replicated `isDead` bool
76
+ self-corrects.
77
+
78
+ **Rule of thumb:** replicate *nouns* (state), RPC *verbs* (events). What to replicate: only what other
79
+ peers must see, at the coarsest rate that still looks right — position/rotation of visible actors,
80
+ gameplay stats, animation state flags. Do **not** replicate values a peer can derive locally, purely
81
+ cosmetic particles, or high-frequency data no one observes.
82
+
83
+ **Ownership / authority** decides *who may write*. Every networked object has an owner (a peer id) and an
84
+ authority (usually the server). Writes to replicated state must come from the authority; ownership lets a
85
+ client drive *its own* pawn's input while the server still validates the outcome. Getting ownership wrong
86
+ is the #1 cause of "it works for the host but not the client" bugs.
87
+
88
+ ## The hard problems
89
+
90
+ Latency (round-trip time, RTT) is the enemy; jitter and packet loss make it worse. Four techniques,
91
+ covered in depth in **references/prediction-and-latency.md**:
92
+
93
+ 1. **Client-side prediction** — the owning client applies its own input *immediately* instead of waiting a
94
+ full RTT for the server, so local movement feels instant.
95
+ 2. **Server reconciliation** — the server is still authoritative; the client tags each input with a sequence
96
+ number, and when the authoritative state arrives it *replays* any inputs the server hadn't processed yet.
97
+ Mismatch → a correction (the visible "rubber-band"); minimize it, don't hide it.
98
+ 3. **Snapshot interpolation** — *remote* entities are rendered ~100 ms in the past, interpolating between the
99
+ last two received snapshots, so other players move smoothly instead of teleporting between updates.
100
+ 4. **Lag compensation** — for hitscan/instant hits, the server rewinds other entities to where the shooter
101
+ *saw* them (at the shooter's render time) before testing the hit. Fairer for the shooter; occasionally
102
+ "shot behind a wall" for the victim. A deliberate tradeoff, not a bug.
103
+
104
+ **Tick rate / netrate:** the server simulates at a fixed tick (e.g. 20–64 Hz) and sends state at a *send
105
+ rate* that may be lower than its sim rate and decoupled from client render FPS. Never tie gameplay to frame
106
+ rate on a networked game — simulate on a fixed timestep, interpolate for rendering.
107
+
108
+ ## Per-engine mapping
109
+
110
+ Short form here; full patterns in the references. All snippets are current-API.
111
+
112
+ ### Godot 4.x — high-level multiplayer
113
+
114
+ `ENetMultiplayerPeer` for transport, `@rpc` for events, `MultiplayerSynchronizer` for state, and
115
+ `MultiplayerSpawner` to replicate object spawns. Authority via `set_multiplayer_authority(peer_id)`.
116
+
117
+ ```gdscript
118
+ # Host / join
119
+ func host(port := 9999) -> void:
120
+ var peer := ENetMultiplayerPeer.new()
121
+ peer.create_server(port, 8)
122
+ multiplayer.multiplayer_peer = peer
123
+
124
+ func join(ip: String, port := 9999) -> void:
125
+ var peer := ENetMultiplayerPeer.new()
126
+ peer.create_client(ip, port)
127
+ multiplayer.multiplayer_peer = peer
128
+
129
+ # RPC: params are optional and order-free. any_peer lets clients call it;
130
+ # authority (default) means only the multiplayer authority may call it.
131
+ @rpc("any_peer", "call_local", "reliable")
132
+ func request_fire(target: Vector3) -> void:
133
+ if not multiplayer.is_server(): return # server validates
134
+ var shooter := multiplayer.get_remote_sender_id()
135
+ _resolve_shot(shooter, target) # server decides the result
136
+ ```
137
+
138
+ - `set_multiplayer_authority(id)` on a node; check with `is_multiplayer_authority()`. A player scene
139
+ usually gives its input node authority to the owning peer while the server keeps authority over health.
140
+ - `MultiplayerSynchronizer` streams a chosen property list (position, etc.) from the authority to peers;
141
+ set its replication interval and visibility (`set_visibility_for(peer, bool)`) to scope bandwidth.
142
+ - `MultiplayerSpawner` replicates instancing of scenes under a path so late peers get existing objects.
143
+ - Details, prediction wiring, and the full player-scene pattern: **references/godot-high-level-multiplayer.md**.
144
+
145
+ ### Unity — Netcode for GameObjects
146
+
147
+ `NetworkObject` (the identity, spawned via `Spawn()`), `NetworkBehaviour` (networked scripts),
148
+ `NetworkVariable<T>` (replicated state), and RPCs. `NetworkManager.Singleton.StartHost/StartServer/StartClient`.
149
+
150
+ ```csharp
151
+ public class Player : NetworkBehaviour {
152
+ // Default perms: everyone reads, only the SERVER writes → authoritative health.
153
+ public NetworkVariable<int> Health = new(100);
154
+
155
+ [Rpc(SendTo.Server)] // client → server: an intent
156
+ void FireRpc(Vector3 target) {
157
+ // runs on the server; validate, then apply and let NetworkVariables replicate the result
158
+ ResolveShot(OwnerClientId, target);
159
+ }
160
+
161
+ [Rpc(SendTo.ClientsAndHost)] // server → everyone: an event
162
+ void PlayMuzzleFxRpc(Vector3 at) { /* cosmetic only */ }
163
+ }
164
+ ```
165
+
166
+ - Ownership/role: `IsOwner`, `IsServer`, `IsClient`, `IsHost`, `OwnerClientId`. Only owners should send
167
+ owner-intent RPCs; the server validates regardless.
168
+ - `NetworkVariable<T>(value, readPerm, writePerm)` — `NetworkVariableWritePermission.Server` (default) vs
169
+ `.Owner`; subscribe via `OnValueChanged`. Use `NetworkTransform` for pos/rot sync.
170
+ - Prefer the unified `[Rpc(SendTo.X)]`; legacy `[ServerRpc]`/`[ClientRpc]` still work but need the
171
+ method-name suffixes. Full patterns: **references/unity-and-unreal-netcode.md**.
172
+
173
+ ### Unreal — Actor replication
174
+
175
+ Set `AActor::bReplicates = true` (or `SetReplicates(true)`). Replicate properties with `UPROPERTY` and
176
+ register them in `GetLifetimeReplicatedProps`; use RPC `UFUNCTION` specifiers for events.
177
+
178
+ ```cpp
179
+ UPROPERTY(ReplicatedUsing = OnRep_Health) // RepNotify fires on clients when it changes
180
+ float Health = 100.f;
181
+
182
+ UFUNCTION(Server, Reliable, WithValidation) // client → server intent (+ validation)
183
+ void ServerFire(FVector Target);
184
+
185
+ UFUNCTION(NetMulticast, Unreliable) // server → all: cosmetic event
186
+ void MulticastMuzzleFx(FVector At);
187
+
188
+ void AMyPawn::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const {
189
+ Super::GetLifetimeReplicatedProps(Out);
190
+ DOREPLIFETIME(AMyPawn, Health); // or DOREPLIFETIME_CONDITION for push/owner-only
191
+ }
192
+ ```
193
+
194
+ - Authority check: `HasAuthority()` (server or standalone); roles `ROLE_Authority`, `ROLE_AutonomousProxy`
195
+ (the owning client), `ROLE_SimulatedProxy` (observed elsewhere).
196
+ - **Relevancy** keeps bandwidth sane: `NetCullDistanceSquared`, `bAlwaysRelevant`, `IsNetRelevantFor`, and
197
+ dormancy (`SetNetDormancy`) stop replicating actors a client can't perceive. Movement replicates via
198
+ `CharacterMovementComponent`, which already does prediction+reconciliation for you.
199
+ - RepNotify vs Multicast, GAS/Network Prediction notes: **references/unity-and-unreal-netcode.md**.
200
+
201
+ ## Security / anti-cheat basics
202
+
203
+ - **Validate every client input on the server** — bounds-check movement deltas, cooldowns, line-of-sight,
204
+ and resource costs. Reject the impossible; never assume a well-behaved client.
205
+ - **Send a client only what it may know.** Don't replicate enemy positions through walls (wallhack fuel),
206
+ full inventories of others, or hidden RNG seeds — scope with relevancy/visibility.
207
+ - Rate-limit and sanity-check RPCs; a client spamming `ServerFire` faster than the weapon allows is caught
208
+ by server-side cooldowns, not client-side ones.
209
+ - Encryption/DTLS stops packet tampering on the wire but does **not** stop a modified client — only server
210
+ authority does. Treat anti-cheat as defense in depth, not a product this skill ships.
211
+
212
+ ## Testing multiplayer locally
213
+
214
+ - **Godot:** enable *Debug → Run Multiple Instances* (2–4 windows) to host+join on one machine.
215
+ - **Unity:** *Multiplayer Play Mode* (or ParrelSync) runs several virtual players from one project;
216
+ Network Simulator injects latency/loss.
217
+ - **Unreal:** PIE *Number of Players* + *Net Mode* (Play As Listen Server / Client), and *Net PktLag* /
218
+ packet-emulation console vars to fake latency.
219
+ - Always test **with simulated latency and packet loss**, not just on localhost — localhost hides every
220
+ prediction/reconciliation bug. Test the client path, not only the host's.
221
+
222
+ ## Guardrails / gotchas
223
+
224
+ - Localhost is a liar: 0 ms RTT masks the exact desync/rubber-band bugs your players will hit. Inject lag.
225
+ - Don't RPC state you need to stay consistent — replicate it (an unreliable event drop is permanent).
226
+ - Don't tie simulation to render FPS; fixed timestep on the sim, interpolate on the render.
227
+ - The listen-server host is a client too — guard authority-only logic behind an `is_server`/`HasAuthority`
228
+ check or it double-executes on the host.
229
+ - Wrong ownership = "works for host, breaks for client." Set authority/ownership explicitly on spawn.
230
+ - Replicating everything, every tick, to everyone melts bandwidth — scope by relevancy/visibility and rate.
231
+
232
+ ## Related skills
233
+
234
+ - **`godot` / `unity` / `unreal`** — the engine your game runs in; owns local gameplay, input, scenes, build.
235
+ - **`gamedev-physics`** — determinism, collision, character controllers; netcode replicates its results and
236
+ needs its determinism for prediction/rollback.
237
+ - **`deployment`** — dedicated-server hosting, matchmaking/lobby backend, relays, and CI for game servers.
238
+ - **`secure-coding`** — general threat modeling; this skill keeps only the game-specific anti-cheat controls.
239
+
240
+ ## Checklist
241
+
242
+ - [ ] Topology chosen (dedicated / listen / P2P) and the *same code* runs on server and listen host.
243
+ - [ ] Server-authoritative: clients send intent only; server validates and owns all gameplay state.
244
+ - [ ] Each networked value is either replicated (state/nouns) or an RPC (events/verbs) — not the wrong one.
245
+ - [ ] Ownership/authority set explicitly per object; authority-only logic guarded on the host.
246
+ - [ ] Owning-client movement predicted + reconciled; remote entities snapshot-interpolated.
247
+ - [ ] Hitscan uses server-side lag compensation; simulation on a fixed tick, decoupled from render FPS.
248
+ - [ ] Only need-to-know state replicated per client (relevancy/visibility scoped); RPCs rate-limited.
249
+ - [ ] Tested with simulated latency + packet loss across multiple instances, exercising the client path.
250
+ - [ ] No banned APIs (UNet/HLAPI, Godot 3.x `master`/`puppet`/`rset`, `Tick`-polled Unreal replication).
@@ -0,0 +1,12 @@
1
+ # Eval harness — `gamedev-multiplayer` skill
2
+
3
+ `cases.yaml` is a human/CI-readable trigger-and-capability spec, not an automated runner. Load the full
4
+ skill-catalog descriptions into a routing agent and, for each `should_trigger` prompt, confirm this skill
5
+ fires (including the near-miss phrasings that never say "netcode"); for each `should_not_trigger` prompt,
6
+ confirm it stays silent and that whatever does fire matches the stated `route_to` sibling (godot, deployment,
7
+ gamedev-physics, webhooks, secure-coding). Because LLM routing is non-deterministic, run several trials and
8
+ take the majority. For the `capability` scenario, run it once with the skill body unavailable and once with
9
+ `SKILL.md` + references loaded, then check each output against `must_include` — the with-skill answer must
10
+ use current APIs (ENetMultiplayerPeer, `@rpc("any_peer", …)`, `is_server()` authority gate, no Godot 3.x
11
+ `master`/`puppet`/`rset`) and clearly beat the baseline. Keep `cases.yaml` in sync with SKILL.md's
12
+ "Fires on / When NOT" whenever scope changes.
@@ -0,0 +1,44 @@
1
+ skill: gamedev-multiplayer
2
+
3
+ should_trigger:
4
+ - prompt: "I have a single-player platformer in Godot 4 and I want to add online co-op. How do I sync the two players' positions and let one host the game?"
5
+ why: "Adding multiplayer/sync to a game is the core trigger; should produce ENetMultiplayerPeer host/join + MultiplayerSynchronizer/authority guidance."
6
+ - prompt: "In my Unity shooter the client sets its own health with a NetworkVariable and players can cheat. How should authority actually work?"
7
+ why: "Server-authoritative / never-trust-the-client + NetworkVariable write permissions; center of gravity of this skill."
8
+ - prompt: "Players rubber-band and teleport around when there's lag in my online game. What's the right way to fix the jitter?"
9
+ why: "Symptom text for prediction/reconciliation + snapshot interpolation; routes here without the word 'netcode'."
10
+ - prompt: "Should I use a dedicated server or peer-to-peer for my PvP game, and how do I stop clients from cheating?"
11
+ why: "Topology + authority decision, the second core section; not an infra/hosting question about how to run the server."
12
+ - prompt: "In Unreal, how do I replicate a health value to all clients and run a RepNotify when it changes, and what's the difference between a Server RPC and a NetMulticast?"
13
+ why: "Unreal Actor replication mapping: ReplicatedUsing/DOREPLIFETIME + Server vs NetMulticast RPC semantics."
14
+ - prompt: "What tick rate should my authoritative server run at, and how do I test multiplayer locally with fake latency before I ship?"
15
+ why: "Tick/netrate + local multiplayer testing with simulated latency, both explicitly owned here."
16
+ - prompt: "Is ServerRpc/ClientRpc still the way to do RPCs in Unity Netcode for GameObjects, or is there a newer attribute?"
17
+ why: "Version-contract question the skill answers directly: unified [Rpc(SendTo.X)] preferred, legacy pair still supported, UNet removed."
18
+
19
+ should_not_trigger:
20
+ - prompt: "How do I read gamepad input and make my character jump in Godot 4?"
21
+ route_to: "godot"
22
+ why: "Single-player input/gameplay is the engine skill's job; no network dimension."
23
+ - prompt: "Set up a matchmaking backend and auto-scale dedicated game servers on a cloud provider with a lobby database."
24
+ route_to: "deployment"
25
+ why: "Server hosting/matchmaking/orchestration infra is delegated to deployment; this skill designs the in-game net layer, not the fleet."
26
+ - prompt: "My character controller clips through walls at high speed and collisions feel inconsistent — how do I fix the physics?"
27
+ route_to: "gamedev-physics"
28
+ why: "Collision/character-controller/determinism is physics; netcode consumes determinism but doesn't own the physics fix."
29
+ - prompt: "Build a realtime chat app with presence indicators over WebSockets for my website."
30
+ route_to: "webhooks"
31
+ why: "Generic web realtime that is not a game world/simulation is not this skill's domain."
32
+ - prompt: "Do a general threat model and authz review of my app's permission boundaries."
33
+ route_to: "secure-coding"
34
+ why: "Language/domain-agnostic threat modeling is secure-coding; this skill keeps only game-specific anti-cheat controls."
35
+
36
+ capability:
37
+ - scenario: "In Godot 4.x, wire a server-authoritative 'fire weapon' from a client so the server validates and applies the shot, with the same code working on a listen-server host. Show the RPC and the authority handling."
38
+ must_include:
39
+ - "Uses ENetMultiplayerPeer (create_server / create_client) assigned to multiplayer.multiplayer_peer — NOT the removed NetworkedMultiplayerENet"
40
+ - "Declares the intent RPC with @rpc(\"any_peer\", ...) so a client may call it, and the client sends it with rpc_id(1, ...) to the server"
41
+ - "Gates the body with multiplayer.is_server() (or is_multiplayer_authority) so authoritative logic runs once, correctly on a listen host"
42
+ - "Identifies the sender with multiplayer.get_remote_sender_id() and validates before applying (server decides the result, client sends intent only)"
43
+ - "Uses no Godot 3.x APIs (no master/puppet/remote keywords, no rset, no rpc_config as a legacy call)"
44
+ - "Distinguishes replicating authoritative state (e.g. via MultiplayerSynchronizer / a replicated var) from firing the one-shot RPC event"
@@ -0,0 +1,133 @@
1
+ # Godot 4.x high-level multiplayer
2
+
3
+ The high-level API sits on top of `MultiplayerPeer` and gives you three moving parts: **transport**
4
+ (`ENetMultiplayerPeer`), **events** (`@rpc`), and **state** (`MultiplayerSynchronizer` +
5
+ `MultiplayerSpawner`). Authority is per-node, set with `set_multiplayer_authority()`.
6
+
7
+ Everything here is Godot 4.x. Godot 3.x used `master`/`puppet`/`remote`/`remotesync` keywords, `rset`,
8
+ and `NetworkedMultiplayerENet` — **all gone**; never emit them.
9
+
10
+ ## Transport & connection
11
+
12
+ ```gdscript
13
+ const PORT := 9999
14
+ const MAX_PLAYERS := 8
15
+
16
+ func host() -> void:
17
+ var peer := ENetMultiplayerPeer.new()
18
+ var err := peer.create_server(PORT, MAX_PLAYERS)
19
+ if err != OK: push_error("host failed: %s" % err); return
20
+ multiplayer.multiplayer_peer = peer
21
+
22
+ func join(ip := "127.0.0.1") -> void:
23
+ var peer := ENetMultiplayerPeer.new()
24
+ peer.create_client(ip, PORT)
25
+ multiplayer.multiplayer_peer = peer
26
+
27
+ func _ready() -> void:
28
+ multiplayer.peer_connected.connect(_on_peer_connected) # server-side: a client joined
29
+ multiplayer.peer_disconnected.connect(_on_peer_disconnected)
30
+ multiplayer.connected_to_server.connect(_on_connected) # client-side
31
+ multiplayer.server_disconnected.connect(_on_server_gone)
32
+ ```
33
+
34
+ Useful `multiplayer` (SceneMultiplayer) members: `is_server()`, `get_unique_id()` (1 == server),
35
+ `get_remote_sender_id()` (valid only inside an RPC body), `multiplayer_peer`.
36
+
37
+ ## RPCs — the `@rpc` annotation
38
+
39
+ Params are optional and **order-free**. Signature: mode, sync, transfer mode, channel.
40
+
41
+ - **mode**: `"authority"` (default — only the node's multiplayer authority may invoke it on remotes) or
42
+ `"any_peer"` (any client may invoke it — use this for client→server intent, then validate).
43
+ - **sync**: `"call_remote"` (default) or `"call_local"` (also run on the caller).
44
+ - **transfer**: `"unreliable"` (default), `"unreliable_ordered"`, or `"reliable"`.
45
+ - **channel**: an int, and it must be the **last** argument.
46
+
47
+ ```gdscript
48
+ # Client asks the server to fire. any_peer + call_local so the host's own call runs too.
49
+ @rpc("any_peer", "call_local", "reliable")
50
+ func request_fire(target: Vector3) -> void:
51
+ if not multiplayer.is_server(): return # authority gate
52
+ var shooter := multiplayer.get_remote_sender_id()
53
+ if not _can_fire(shooter): return # server-side cooldown / validation
54
+ _apply_shot(shooter, target) # server mutates authoritative state
55
+
56
+ # Server → everyone, cosmetic only. Unreliable is fine for FX.
57
+ @rpc("authority", "call_local", "unreliable")
58
+ func play_muzzle_fx(at: Vector3) -> void:
59
+ _spawn_fx(at)
60
+
61
+ # Invoking: rpc() = all peers; rpc_id(peer, ...) = one peer; rpc_id(1, ...) = the server.
62
+ func _on_fire_pressed(target: Vector3) -> void:
63
+ rpc_id(1, "request_fire", target) # client sends intent to server
64
+ ```
65
+
66
+ Both sides must declare the **same** `@rpc` annotation on a function with the same name/path, or the call
67
+ is rejected. A missing/mismatched annotation is the usual "my RPC does nothing" cause.
68
+
69
+ ## Authority
70
+
71
+ ```gdscript
72
+ # On spawn, give the owning peer authority over its input node; server keeps authority over stats.
73
+ func setup(owner_peer_id: int) -> void:
74
+ $Input.set_multiplayer_authority(owner_peer_id) # recursive=true by default
75
+ # $Stats stays under server authority (peer id 1)
76
+
77
+ func _physics_process(delta: float) -> void:
78
+ if $Input.is_multiplayer_authority(): # only the owner reads local input
79
+ var dir := Input.get_vector("left","right","up","down")
80
+ rpc_id(1, "submit_input", dir) # send intent to server
81
+ ```
82
+
83
+ `set_multiplayer_authority(id, recursive := true)` sets it on the node (and children). Check with
84
+ `is_multiplayer_authority()`; read with `get_multiplayer_authority()`. Authority drives who a
85
+ `MultiplayerSynchronizer` streams *from* and who may call `"authority"`-mode RPCs.
86
+
87
+ ## State — MultiplayerSynchronizer
88
+
89
+ Add a `MultiplayerSynchronizer` node, assign it a **replication config** (`SceneReplicationConfig`, edited
90
+ in the inspector: list the properties to sync, e.g. `position`, `rotation`, and per-property spawn vs sync).
91
+ It streams those properties **from the node's authority to the other peers** every replication interval.
92
+
93
+ - `replication_interval` — how often synced properties are sent (throttle to save bandwidth).
94
+ - `delta_interval` — for delta-synced properties.
95
+ - Visibility: `set_visibility_for(peer_id, visible)`, `public_visibility`, `update_visibility()` — scope
96
+ who receives a given synchronizer (relevancy / anti-wallhack).
97
+
98
+ Because it is authority-driven, an owner-authoritative synchronizer trusts the client's position — fine for
99
+ co-op, risky for competitive. For server-authoritative movement, keep authority on the server and have the
100
+ client send input via RPC (see prediction-and-latency.md).
101
+
102
+ ## Spawning — MultiplayerSpawner
103
+
104
+ `MultiplayerSpawner` replicates *instancing*. Point its `spawn_path` at the parent node, register spawnable
105
+ scenes with `add_spawnable_scene("res://player.tscn")`, and when the authority instances a child under that
106
+ path (or you call `spawn(data)`), the same instance appears on every peer — including late joiners, which is
107
+ the big win over manually RPC-ing spawns.
108
+
109
+ ```gdscript
110
+ @onready var spawner := $MultiplayerSpawner
111
+
112
+ func _ready() -> void:
113
+ spawner.spawn_function = _spawn_player # optional custom factory
114
+
115
+ func _on_peer_connected(id: int) -> void: # server only
116
+ if multiplayer.is_server():
117
+ spawner.spawn(id) # data passed to spawn_function on every peer
118
+
119
+ func _spawn_player(peer_id: int) -> Node:
120
+ var p := preload("res://player.tscn").instantiate()
121
+ p.name = str(peer_id)
122
+ p.setup(peer_id)
123
+ return p
124
+ ```
125
+
126
+ ## Gotchas
127
+
128
+ - Server (peer 1) is also a client on a listen host — always gate authority logic behind `is_server()`.
129
+ - `get_remote_sender_id()` is only meaningful *inside* an RPC; it returns 0 outside one.
130
+ - Node paths must match across peers for RPCs/synchronizers to resolve — spawn via `MultiplayerSpawner` so
131
+ names line up, and set `name` deterministically (e.g. the peer id).
132
+ - Reliable RPCs are ordered per channel and cost more; keep FX/heartbeats unreliable, keep state changes
133
+ reliable or (better) replicated.
@@ -0,0 +1,104 @@
1
+ # Prediction, reconciliation & latency hiding
2
+
3
+ The whole reason netcode is hard: the speed of light. A round trip (RTT) of 60–150 ms is normal, and if the
4
+ owning client had to wait a full RTT to see its own movement, the game would feel broken. The techniques
5
+ below keep it responsive **without** giving up server authority. This is engine-agnostic theory; the
6
+ per-engine references show where the hooks live.
7
+
8
+ ## The authoritative loop (mental model)
9
+
10
+ 1. Client samples input each tick, tags it with a **sequence number**, sends it to the server, and **applies
11
+ it locally immediately** (prediction).
12
+ 2. Server receives inputs, simulates them on its fixed tick, and produces authoritative state. It stamps each
13
+ state snapshot with the last input sequence number it processed for that client.
14
+ 3. Client receives the authoritative snapshot, **reconciles**: snap to the server state, then re-apply every
15
+ input *after* the acknowledged sequence number (they haven't reached the server yet).
16
+ 4. Other (remote) entities are rendered from buffered snapshots, **interpolated** slightly in the past.
17
+
18
+ Server is always the source of truth. The client only ever *guesses ahead* and gets corrected.
19
+
20
+ ## 1. Client-side prediction
21
+
22
+ The owning client runs the same movement code the server will run, on the same fixed timestep, the instant it
23
+ presses a key — no waiting. Requirements:
24
+
25
+ - **Deterministic** movement/simulation for the predicted entity (same input + same state → same result).
26
+ This is why `gamedev-physics` determinism matters here.
27
+ - A **ring buffer** of unacknowledged inputs (`{seq, dt, input}`), kept until the server acknowledges them.
28
+ - Predict only what the client owns (its pawn). Never predict other players — interpolate them instead.
29
+
30
+ ## 2. Server reconciliation
31
+
32
+ When an authoritative snapshot arrives tagged "processed up to input N":
33
+
34
+ ```
35
+ clientState = serverState # trust the server
36
+ discard buffered inputs with seq <= N # server already applied these
37
+ for each remaining buffered input (seq > N):
38
+ clientState = simulate(clientState, input) # replay the unacknowledged ones
39
+ ```
40
+
41
+ If the replayed result matches what the client already had, the correction is invisible. If it diverges (the
42
+ server disagreed — lag, a missed collision, a rejected move), the entity snaps: the visible **rubber-band**.
43
+ Goal is to *minimize* divergence, not to suppress the correction — hiding a legitimate correction is how you
44
+ get players walking through walls on their screen.
45
+
46
+ - **Smoothing**: for small corrections, blend toward the corrected position over a few frames instead of
47
+ hard-snapping, so a 2 cm disagreement isn't a visible pop. Hard-snap large ones.
48
+
49
+ ## 3. Snapshot interpolation (remote entities)
50
+
51
+ You receive remote entities' state only every send-tick (e.g. every 50 ms) — rendering the latest packet
52
+ directly makes them teleport. Instead, keep a small buffer and render them ~1–2 snapshots **in the past**:
53
+
54
+ - **Interpolation delay** ≈ 100 ms (2× send interval + jitter margin). Render remote entities at
55
+ `now − delay`, interpolating between the two snapshots that straddle that time.
56
+ - Trade-off: everyone else is shown slightly in the past. That is fine and standard.
57
+ - **Extrapolation** (dead reckoning) predicts a remote entity forward when a packet is late — cheaper on
58
+ bandwidth but wrong on direction changes; prefer interpolation and only extrapolate briefly to cover a gap.
59
+
60
+ So on one client, three clocks coexist: **its own pawn** = present (predicted), **remote entities** = past
61
+ (interpolated), **the server** = the arbiter reconciling the two.
62
+
63
+ ## 4. Lag compensation (server-side rewind)
64
+
65
+ For instant/hitscan hits, the shooter aimed at where they *saw* the target — which is `interpolation delay +
66
+ ½ RTT` in the past. If the server tests the hit against the target's *present* position, the shooter misses
67
+ despite a perfect aim. Fix: the server keeps a short **history** of every entity's recent positions and, when
68
+ processing a shot, **rewinds** the world to the shooter's view time before testing the hit.
69
+
70
+ - Store ~1 second of position history per entity (a ring of transforms per tick).
71
+ - On a shot: reconstruct positions at `clientRenderTime`, run the trace, then restore.
72
+ - Trade-off: the victim can be hit just after ducking behind cover ("shot behind the wall"). This is an
73
+ accepted fairness choice favoring the shooter; cap how far back you rewind to bound the unfairness.
74
+
75
+ ## 5. Tick rate, send rate, netrate
76
+
77
+ - **Simulation tick** — fixed Hz the server steps the world (20 / 30 / 60+; higher = more precise + more CPU).
78
+ - **Send rate / snapshot rate** — how often state goes on the wire; often lower than sim tick, and independent
79
+ of client render FPS. This is the "netrate/updaterate/cmdrate" family in shooters.
80
+ - **Client render FPS** — decoupled from both; render via interpolation between fixed-step states.
81
+ - Bandwidth ≈ replicated-bytes × send-rate × relevant-peers. Shrink it with relevancy/visibility scoping,
82
+ quantized/delta-compressed fields, and lower send rates for distant or unimportant actors.
83
+
84
+ **Never run gameplay in a variable `_process`/`Update`/`Tick` on a networked game** — use a fixed timestep
85
+ (`_physics_process`, `NetworkUpdate`/`FixedUpdate`, Unreal's fixed sim) so prediction and reconciliation
86
+ replay identically on client and server.
87
+
88
+ ## Alternative model: deterministic lockstep / rollback
89
+
90
+ For fighting games and some RTS: no per-frame state replication at all — every peer runs the identical
91
+ deterministic simulation and only **inputs** are exchanged. **Lockstep** waits for all inputs before advancing
92
+ (one slow peer stalls everyone). **Rollback** (GGPO-style) predicts absent inputs, then rolls back and
93
+ re-simulates when the real inputs arrive — excellent feel, but demands *bit-perfect determinism* (watch
94
+ floats, iteration order, RNG) and bounded state you can snapshot/restore cheaply. Choose it only when you can
95
+ guarantee that determinism; otherwise the client-server model above is the safer default.
96
+
97
+ ## Checklist for a responsive authoritative game
98
+
99
+ - [ ] Owning pawn predicted locally on a fixed timestep; inputs buffered with sequence numbers.
100
+ - [ ] Server acks the last processed input seq in each snapshot; client replays unacknowledged inputs.
101
+ - [ ] Small corrections smoothed, large ones snapped; corrections never fully suppressed.
102
+ - [ ] Remote entities interpolated ~100 ms in the past from a snapshot buffer.
103
+ - [ ] Hitscan uses server-side rewind bounded to ~1 s of history.
104
+ - [ ] Sim tick / send rate / render FPS all decoupled; bandwidth scoped by relevancy.