@ericrisco/rsc 0.1.35 → 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 (78) hide show
  1. package/README.md +8 -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/game-design/SKILL.md +170 -0
  7. package/skills/game-design/evals/README.md +13 -0
  8. package/skills/game-design/evals/cases.yaml +47 -0
  9. package/skills/game-design/references/mechanics-and-loops.md +78 -0
  10. package/skills/game-design/references/playtesting-and-scope.md +58 -0
  11. package/skills/game-design/references/progression-and-economy.md +70 -0
  12. package/skills/game-storytelling/SKILL.md +159 -0
  13. package/skills/game-storytelling/evals/README.md +28 -0
  14. package/skills/game-storytelling/evals/cases.yaml +51 -0
  15. package/skills/game-storytelling/references/branching-and-state.md +134 -0
  16. package/skills/game-storytelling/references/dialogue-tools.md +117 -0
  17. package/skills/game-storytelling/references/environmental-storytelling.md +71 -0
  18. package/skills/gamedev-multiplayer/SKILL.md +250 -0
  19. package/skills/gamedev-multiplayer/evals/README.md +12 -0
  20. package/skills/gamedev-multiplayer/evals/cases.yaml +44 -0
  21. package/skills/gamedev-multiplayer/references/godot-high-level-multiplayer.md +133 -0
  22. package/skills/gamedev-multiplayer/references/prediction-and-latency.md +104 -0
  23. package/skills/gamedev-multiplayer/references/unity-and-unreal-netcode.md +166 -0
  24. package/skills/gamedev-pathing/SKILL.md +242 -0
  25. package/skills/gamedev-pathing/evals/README.md +14 -0
  26. package/skills/gamedev-pathing/evals/cases.yaml +49 -0
  27. package/skills/gamedev-pathing/references/navmesh-workflow.md +110 -0
  28. package/skills/gamedev-pathing/references/search-algorithms.md +135 -0
  29. package/skills/gamedev-pathing/references/steering-and-avoidance.md +120 -0
  30. package/skills/gamedev-physics/SKILL.md +225 -0
  31. package/skills/gamedev-physics/evals/README.md +12 -0
  32. package/skills/gamedev-physics/evals/cases.yaml +54 -0
  33. package/skills/gamedev-physics/references/character-controllers.md +120 -0
  34. package/skills/gamedev-physics/references/determinism-and-queries.md +124 -0
  35. package/skills/gamedev-physics/references/layers-and-masks.md +115 -0
  36. package/skills/gamedev-shaders/SKILL.md +252 -0
  37. package/skills/gamedev-shaders/evals/README.md +14 -0
  38. package/skills/gamedev-shaders/evals/cases.yaml +45 -0
  39. package/skills/gamedev-shaders/references/effect-recipes.md +205 -0
  40. package/skills/gamedev-shaders/references/godot-shading-language.md +133 -0
  41. package/skills/gamedev-shaders/references/unity-and-unreal-shaders.md +169 -0
  42. package/skills/gamedev-shipping/SKILL.md +236 -0
  43. package/skills/gamedev-shipping/evals/README.md +9 -0
  44. package/skills/gamedev-shipping/evals/cases.yaml +55 -0
  45. package/skills/gamedev-shipping/references/mobile-and-desktop.md +127 -0
  46. package/skills/gamedev-shipping/references/stores-and-ci.md +162 -0
  47. package/skills/gamedev-shipping/references/web-export-headers.md +140 -0
  48. package/skills/godot/SKILL.md +319 -0
  49. package/skills/godot/evals/README.md +13 -0
  50. package/skills/godot/evals/cases.yaml +50 -0
  51. package/skills/godot/references/export-and-testing.md +102 -0
  52. package/skills/godot/references/gdscript-style.md +84 -0
  53. package/skills/godot/references/godot3-to-4-traps.md +89 -0
  54. package/skills/godot/references/nodes-scenes-resources.md +109 -0
  55. package/skills/level-design/SKILL.md +175 -0
  56. package/skills/level-design/evals/README.md +35 -0
  57. package/skills/level-design/evals/cases.yaml +58 -0
  58. package/skills/level-design/references/blockout-to-art.md +118 -0
  59. package/skills/level-design/references/guidance-and-readability.md +152 -0
  60. package/skills/level-design/references/pacing-and-encounters.md +142 -0
  61. package/skills/roast-me/tools/adapters/__pycache__/__init__.cpython-312.pyc +0 -0
  62. package/skills/roast-me/tools/adapters/__pycache__/base.cpython-312.pyc +0 -0
  63. package/skills/roast-me/tools/adapters/__pycache__/claude.cpython-312.pyc +0 -0
  64. package/skills/roast-me/tools/adapters/__pycache__/codex.cpython-312.pyc +0 -0
  65. package/skills/roast-me/tools/adapters/__pycache__/gemini.cpython-312.pyc +0 -0
  66. package/skills/roast-me/tools/adapters/__pycache__/registry.cpython-312.pyc +0 -0
  67. package/skills/unity/SKILL.md +282 -0
  68. package/skills/unity/evals/README.md +8 -0
  69. package/skills/unity/evals/cases.yaml +43 -0
  70. package/skills/unity/references/build-and-platforms.md +65 -0
  71. package/skills/unity/references/input-system.md +102 -0
  72. package/skills/unity/references/lifecycle-deep-dive.md +75 -0
  73. package/skills/unreal/SKILL.md +270 -0
  74. package/skills/unreal/evals/README.md +12 -0
  75. package/skills/unreal/evals/cases.yaml +45 -0
  76. package/skills/unreal/references/blueprint-cpp-parity.md +109 -0
  77. package/skills/unreal/references/gameplay-framework.md +95 -0
  78. package/skills/unreal/references/packaging.md +83 -0
@@ -0,0 +1,117 @@
1
+ # Dialogue tools — the four primitives, a pick-one guide, and one conversation in three formats
2
+
3
+ Deep dive for the SKILL's "Dialogue systems" section. **Engine-agnostic:** author dialogue in a portable format here; the engine skill (`godot`/`unity`/`unreal`) wires the runner, presenter, save/serialize, and any plugin. Do not write engine binding code in this skill.
4
+
5
+ ## Every tool reduces to four primitives
6
+
7
+ Design against these, then map them onto whichever tool the project uses:
8
+
9
+ | Primitive | What it is | Ink | Yarn Spinner | Twine (Twee) | articy:draft X |
10
+ | --- | --- | --- | --- | --- | --- |
11
+ | **Node** | addressable unit of content | knot `=== name ===` / stitch `= name` | node with `title:` + `===` end | passage `:: Name` | flow / dialogue fragment |
12
+ | **Condition** | boolean over state gating content | `{ cond: ... }`, `{ cond: a \| b }` | `<<if $x>> ... <<endif>>` | `(if: $x)[...]` (Harlowe) / `<<if $x>>` (SugarCube) | condition node / expression on a pin |
13
+ | **Variable** | the story state read/written | `VAR x = 0`, `~ x = 1` | `<<set $x to 1>>` | `(set: $x to 1)` / `<<set $x = 1>>` | global variable in the variable set |
14
+ | **Effect** | side effect a node fires | `~ func()`, tags `#`, external funcs | `<<command>>`, functions, `<<set>>` | macros, `<<run>>` (SugarCube) | instruction node / script pin |
15
+
16
+ If you can express a conversation in these four primitives, it ports to any of the tools below.
17
+
18
+ ## The tools (pointers, current as of 2026)
19
+
20
+ ### Ink — inkle's narrative scripting language
21
+ - **Shape:** a writer-first *language* (not a visual editor). Text is the default; logic is woven in. Prose-heavy, highly expressive.
22
+ - **Core syntax:** knots `=== name ===`, stitches `= name`, diverts `-> name`, once-only choices `*`, sticky choices `+`, gathers `-`, weave (nested choices/gathers), `VAR`, `CONST`, `LIST` (state machines / multi-value), conditionals `{ }`, tunnels `-> x ->`, threads `<- x`, functions, tags `#`, glue `<>`.
23
+ - **Runtime:** compiles to JSON; run with **inkjs** (web/JS) or engine integrations (Unity's `ink-unity-integration`; community Godot integrations). **Inky** is the authoring editor with live preview.
24
+ - **Best for:** branching prose, interactive fiction, dialogue with lots of state and reincorporation. Weakest as a visual overview for non-writers.
25
+
26
+ ### Yarn Spinner — the friendly dialogue tool (v3.x current)
27
+ - **Shape:** node-based, screenplay-ish; friendlier to designers and non-programmers than raw Ink. Nodes edit well in a graph or plain text.
28
+ - **Core syntax:** node = header (`title: Name`) + `---` + body + `===`; options with `->`; `<<set $x to 1>>`, `<<if>>/<<elseif>>/<<else>>/<<endif>>`, `<<jump Node>>`, `<<declare>>`, custom `<<commands>>`, functions, and inline markup `[b]...[/b]`.
29
+ - **Runtime & engines:** first-class **Unity** support; **Godot** via official C# (beta) and GDScript (alpha) integrations; **Unreal** integration in progress for 2026. String tables export for localization are built in.
30
+ - **Best for:** game dialogue with a designer-writer split, VO line management, and Unity projects.
31
+
32
+ ### Twine — visual hypertext editor
33
+ - **Shape:** a **visual node editor** that publishes a self-contained HTML file. Lowest barrier to entry; excellent for prototyping and interactive fiction shipped as a web page.
34
+ - **Story formats matter:** scripting depends on the chosen format — **Harlowe** (default, macro-based `(set:)/(if:)`), **SugarCube** (`<<set>>/<<if>>`, save system, richer for games), **Snowman** (JS-forward), **Chapbook** (prose-forward). Pick the format for the job.
35
+ - **Twee** is Twine's plain-text form (`:: Passage` headers) — version-controllable and a good neutral interchange for design even if the game engine isn't Twine.
36
+ - **Best for:** prototyping branches fast, web-delivered IF, and design-time storyboarding. Not a drop-in engine dialogue runtime for a AAA build.
37
+
38
+ ### articy:draft X — visual narrative database (commercial)
39
+ - **Shape:** a heavyweight **narrative DB + flow editor**. Models flow fragments, dialogue fragments, hubs, conditions, instructions, plus a full entity/location/variable database and templates. Built for large teams and large scripts.
40
+ - **Pipeline:** free **Unity** and **Unreal** importers pull dialogues, entities, variables, and localization in cleanly; a **Generic Engine Export** produces JSON (+ optional assets) for any custom engine. Runs on Windows and macOS.
41
+ - **Best for:** big branching RPGs, teams that need a shared narrative database, template-driven content, and translation at scale. Overkill for a small project.
42
+
43
+ ## Pick one — a fast decision guide
44
+
45
+ | If… | Reach for |
46
+ | --- | --- |
47
+ | Prose-heavy IF, lots of state, writer-driven | **Ink** |
48
+ | Game dialogue, designer/writer split, Unity, VO + loc built in | **Yarn Spinner** |
49
+ | Rapid prototype, web-delivered, or teaching branching visually | **Twine** (SugarCube for game-like saves) |
50
+ | Large team, huge script, shared entity/variable DB, Unity/Unreal | **articy:draft X** |
51
+ | You just need a portable design artifact, engine undecided | **Twee** or an Ink file (both are plain text, diff-able) |
52
+
53
+ When the engine is fixed, let the **engine skill** confirm the best-supported integration; when it isn't, author in plain-text (Ink/Twee) so nothing locks you in.
54
+
55
+ ## The same conversation, three formats
56
+
57
+ A guard who lets you pass only if `has_pass` is true; otherwise you can bribe (needs `gold >= 10`) or leave.
58
+
59
+ **Ink**
60
+ ```ink
61
+ VAR has_pass = false
62
+ VAR gold = 0
63
+
64
+ === gate ===
65
+ The guard blocks the door.
66
+ { has_pass:
67
+ "Pass, citizen." -> town
68
+ }
69
+ * [Offer a bribe] { gold >= 10:
70
+ ~ gold = gold - 10
71
+ The guard pockets it. -> town
72
+ - else:
73
+ "You're ten short." -> gate
74
+ }
75
+ * [Leave] -> street
76
+ ```
77
+
78
+ **Yarn Spinner**
79
+ ```yarn
80
+ title: Gate
81
+ ---
82
+ The guard blocks the door.
83
+ <<if $has_pass>>
84
+ Guard: Pass, citizen.
85
+ <<jump Town>>
86
+ <<endif>>
87
+ -> Offer a bribe <<if $gold >= 10>>
88
+ <<set $gold = $gold - 10>>
89
+ The guard pockets it.
90
+ <<jump Town>>
91
+ -> Leave
92
+ <<jump Street>>
93
+ ===
94
+ ```
95
+
96
+ **Twee (SugarCube)**
97
+ ```twee
98
+ :: Gate
99
+ The guard blocks the door.
100
+ <<if $has_pass>>Guard: "Pass, citizen." [[Town]]<<else>>\
101
+ [[Offer a bribe->Bribe]]
102
+ [[Leave->Street]]
103
+ <</if>>
104
+
105
+ :: Bribe
106
+ <<if $gold gte 10>><<set $gold to $gold - 10>>The guard pockets it. [[Town]]\
107
+ <<else>>"You're ten short." [[Gate]]<</if>>
108
+ ```
109
+
110
+ Same four primitives (node / condition / variable / effect), three syntaxes. Design once; the engine skill binds it.
111
+
112
+ ## Delivery notes (design-side)
113
+
114
+ - **Barks vs. conversations** — short, interruptible one-liners (barks) need a priority + cooldown model so they don't stack or repeat; full conversations need focus/turn-taking. Design them as different node types.
115
+ - **Line IDs & loc** — every player-facing line carries a stable id and lives in a string table (see the SKILL's localization notes). Ink tags, Yarn's string-table export, and articy's loc columns all support this; use them.
116
+ - **Never concatenate** sentence fragments across variables for grammar (gender/plural/word order) — author full templated lines with named placeholders.
117
+ - **Presentation is the engine's job** — typewriter effect, portraits, camera, choice-timer UI, skip/auto — hand these to the engine skill; keep the dialogue content pure.
@@ -0,0 +1,71 @@
1
+ # Environmental & systemic storytelling — show, don't tell
2
+
3
+ Deep dive for the SKILL's "Environmental & systemic storytelling" section. The goal: deliver story *through the world and its rules* so the player assembles meaning and owns it, instead of being narrated at. Engine-agnostic; coordinate the *space* with `level-design` and the *rules* with `game-design`.
4
+
5
+ ## Why show over tell
6
+
7
+ Told information is received; discovered information is *earned*. A player who infers "someone died here defending this door" from a skeleton, a spent revolver, and claw marks believes it more, and remembers it longer, than the same fact in a text box. Reserve telling (codex, narrator, log dumps) for what the world genuinely cannot carry — precise numbers, distant history, abstract rules.
8
+
9
+ **Show-don't-tell ladder** (prefer the top; fall back only when needed):
10
+
11
+ 1. **Enacted** — it happens in front of the player through play (an ally sacrifices themselves in a scripted-but-interactive beat).
12
+ 2. **Environmental** — the world's state implies the event (aftermath, traces, arrangement).
13
+ 3. **Overheard / ambient** — barks, one-sided radio, NPCs talking to each other.
14
+ 4. **Readable in-world** — item descriptions, signage, graffiti, terminals that *belong* to the world.
15
+ 5. **Collectible logs** — audio logs, diaries, notes. Useful but the most "tell"-like; easy to overuse.
16
+ 6. **Non-diegetic** — narrator, codex, cutscene exposition, HUD text. Last resort.
17
+
18
+ ## Environmental storytelling — the technique catalog
19
+
20
+ - **Set dressing with intent.** Every prop can carry meaning: a single toy in a barracks, wedding photos in a looted home. Composition points the eye (leading lines, light, framing a key object) — coordinate with `level-design` and lighting.
21
+ - **Aftermath / the frozen moment.** Show the scene *after* the event and let the player reconstruct it: overturned table, blood trail, a meal half-eaten. The gap between "what I see" and "what happened" is the story.
22
+ - **Traces & evidence.** Bullet holes, scorch marks, footprints in dust, a barricade built from the wrong side. Physical cause-and-effect the player reads like a detective.
23
+ - **Spatial sequencing.** The *order* the player encounters space tells a story: descend from opulence into rot; a triumphant mural, then the mass grave behind it. The space is a sentence; its layout is the grammar (a `level-design` collaboration).
24
+ - **Object placement & juxtaposition.** Meaning from adjacency — a child's drawing pinned above a weapons cache; a prayer shrine facing a bricked-up window.
25
+ - **Readable descriptions.** Item and examine text that implies a life ("Dented canteen. Someone scratched three tally marks and stopped."). Small, optional, high-flavor; rewards curiosity without gating.
26
+ - **World-state change.** The strongest environmental beat: the space *changes* in response to the player or the plot (a hub burns, a garden regrows, a faction's banners replace another's). The player sees consequence written on the world.
27
+ - **Absence.** What's missing tells a story too — an empty crib, a cleared-out desk, a name scratched off a roster.
28
+
29
+ Guardrails:
30
+
31
+ - **Legibility vs. mystery.** Give enough signal that most players read the intended gist, with deeper layers for the attentive. If *no one* gets it, it's decoration, not storytelling; if it's spelled out, it's a text box.
32
+ - **Consistency = trust.** Once the world's props reliably mean things, players *read* every scene. One arbitrary/contradictory arrangement teaches them to stop looking.
33
+ - **Optional, not load-bearing.** Environmental story enriches; don't hide *required* plot solely in a missable detail. Critical beats need a guaranteed channel.
34
+
35
+ ## Diegetic vs. non-diegetic delivery
36
+
37
+ | | Diegetic (in-world) | Non-diegetic (outside the fiction) |
38
+ | --- | --- | --- |
39
+ | **Examples** | signage, barks, radio, environmental change, in-world terminals | narrator VO, codex/lore menu, HUD text, tutorial popups, cutscene title cards |
40
+ | **Feel** | immersive, earned, "the world tells me" | efficient, precise, "the game tells me" |
41
+ | **Use for** | mood, place, character, consequence, most story | exact numbers, dense history, rules the world can't show, accessibility |
42
+ | **Cost of overuse** | can be missed/ambiguous | breaks immersion; reads as a lecture |
43
+
44
+ **Diegetic first.** Push information into the world; drop to non-diegetic only for what the world can't carry. Note the honest exceptions: **accessibility** (subtitles, clear objective text, difficulty-scaled hints) and **usability** (a control prompt) are worth a non-diegetic cost — never sacrifice comprehension for purity.
45
+
46
+ ## Systemic & emergent storytelling — the anecdote factory
47
+
48
+ Scripted story is *authored*; systemic story is *generated* by rules + player + world state producing events no writer placed. The famous colonist who starved defending the fridge, the rival who keeps hunting you across a sandbox — nobody wrote those; the systems did.
49
+
50
+ You don't write these stories. You design the **conditions and vocabulary** that let them happen and be legible — an "anecdote factory." This is primarily a `game-design` collaboration; your narrative-design job is to make the output *readable as story*.
51
+
52
+ Design levers:
53
+
54
+ - **Legible actors** — entities with names, traits, memory, and relationships (a guard who remembers you spared him). Persistent identity turns a system event into a *character* moment.
55
+ - **Consequence that persists** — the world remembers and reflects actions (reputation, scars, changed factions), so events chain into arcs instead of resetting.
56
+ - **Readable causality** — the player can *see why* something happened (a needs/mood readout, a reaction bark, a kill-cam). An unexplained emergent event is just noise; a readable one is an anecdote.
57
+ - **Framing & capture** — surfacing tools (event log, photo mode, relationship screen, killfeed, end-of-run summary) that let the player *notice and retell* the story the systems produced. The retelling is the payoff.
58
+ - **Meaningful randomness** — variety within authored bounds (curated event pools, weighted tables) so runs differ without becoming incoherent.
59
+ - **Interacting systems** — emergence needs ≥2 systems that touch (fire + wind + wood; hunger + weather + AI). One system alone produces events, not stories.
60
+
61
+ Guardrails:
62
+
63
+ - **Coherence budget.** Pure emergence drifts into nonsense; pure authorship can't surprise. Set authored guardrails (tone, bounds, curated pools) inside which systems play.
64
+ - **Framing is half the work.** An emergent event the player never notices or can't interpret isn't a story. Invest in the surfacing/retelling tools as much as the simulation.
65
+ - **Name things.** Named actors, places, and factions convert anonymous system state into story the player cares about and repeats.
66
+
67
+ ## Where this hands off
68
+
69
+ - **`level-design`** — owns the physical space, blockout, sightlines, and encounter flow that environmental beats live inside. Co-own set-dressing that carries narrative.
70
+ - **`game-design`** — owns the systems whose interaction produces emergent story; you specify what must be *legible* and *persistent* for those events to read as narrative.
71
+ - **the engine skill** (`godot`/`unity`/`unreal`) — implements world-state changes, triggers, bark systems, event logs, and the persistence that makes consequence stick.
@@ -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.