@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,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.
@@ -0,0 +1,166 @@
1
+ # Unity & Unreal netcode
2
+
3
+ Per-engine deep dive for the two C#/C++ engines. Godot lives in its own reference.
4
+
5
+ ---
6
+
7
+ ## Unity — Netcode for GameObjects (NGO)
8
+
9
+ NGO is Unity's first-party high-level netcode. **UNet / HLAPI is removed** — never emit `NetworkIdentity`,
10
+ `[Command]`, `[SyncVar]`, `NetworkServer`, or anything under `UnityEngine.Networking`. (Mirror is a
11
+ community fork with a UNet-like API; if a repo uses it, name it, but this reference targets NGO.)
12
+
13
+ ### Setup & session
14
+
15
+ - One `NetworkManager` in the scene, with a chosen transport (Unity Transport / UTP).
16
+ - Start a session: `NetworkManager.Singleton.StartHost()` (server + local client), `.StartServer()`
17
+ (dedicated/headless), or `.StartClient()`.
18
+ - Networked prefabs need a `NetworkObject` and must be registered in the NetworkManager prefab list;
19
+ the **server** spawns them: `Instantiate(...).GetComponent<NetworkObject>().Spawn()`.
20
+
21
+ ### NetworkBehaviour, roles, lifecycle
22
+
23
+ Networked scripts derive from `NetworkBehaviour`. Use `OnNetworkSpawn()` / `OnNetworkDespawn()` instead of
24
+ `Start` for network-aware init. Role/ownership properties: `IsServer`, `IsClient`, `IsHost`, `IsOwner`,
25
+ `OwnerClientId`, `NetworkObjectId`.
26
+
27
+ ### NetworkVariable — replicated state
28
+
29
+ ```csharp
30
+ public class PlayerState : NetworkBehaviour {
31
+ // Default: read Everyone, write Server → authoritative health.
32
+ public NetworkVariable<int> Health = new(100);
33
+
34
+ // Client convenience state: owner writes, everyone reads.
35
+ public NetworkVariable<int> SkinIndex =
36
+ new(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
37
+
38
+ // Hidden-from-others: owner reads, owner writes.
39
+ public NetworkVariable<int> Ammo =
40
+ new(default, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Owner);
41
+
42
+ public override void OnNetworkSpawn() {
43
+ Health.OnValueChanged += (prev, cur) => UpdateHealthBar(cur);
44
+ }
45
+ }
46
+ ```
47
+
48
+ `NetworkVariableReadPermission` is `Everyone` | `Owner`; `NetworkVariableWritePermission` is `Server` |
49
+ `Owner`. Default (no perms arg) = read Everyone, write Server — the right default for authoritative gameplay
50
+ values. Only the permitted writer may assign `.Value`; a client writing a server-write variable is ignored.
51
+
52
+ ### RPCs
53
+
54
+ NGO 2.x's unified attribute is preferred:
55
+
56
+ ```csharp
57
+ [Rpc(SendTo.Server)] // client → server
58
+ void FireRpc(Vector3 target) { /* runs on server; validate + apply */ }
59
+
60
+ [Rpc(SendTo.ClientsAndHost)] // server → all clients (+ host)
61
+ void PlayFxRpc(Vector3 at) { /* cosmetic */ }
62
+
63
+ [Rpc(SendTo.Owner)] // server → the owning client only
64
+ void YouWereHitRpc(int dmg) { }
65
+ ```
66
+
67
+ `SendTo` options include `Server`, `ClientsAndHost`, `Everyone`, `Owner`, `NotServer`, `SpecifiedInParams`.
68
+ The **legacy** attributes still compile and enforce method-name suffixes:
69
+
70
+ ```csharp
71
+ [ServerRpc(RequireOwnership = true)] void FireServerRpc(Vector3 t) { } // name MUST end in ServerRpc
72
+ [ClientRpc] void PlayFxClientRpc(Vector3 a) { } // name MUST end in ClientRpc
73
+ ```
74
+
75
+ Prefer `[Rpc(SendTo.X)]` on new code; recognize the legacy pair in existing code. RPCs are unreliable by
76
+ default for some configs — mark reliability via the attribute's `Delivery` where consistency matters.
77
+
78
+ ### Transforms & movement
79
+
80
+ `NetworkTransform` replicates position/rotation/scale (server-authoritative by default; an
81
+ `OwnerNetworkTransform`/authority setting allows client-authoritative). For server-authoritative movement
82
+ with responsiveness you add prediction yourself (NGO does not ship full prediction; see
83
+ prediction-and-latency.md). `NetworkAnimator` syncs Animator state.
84
+
85
+ ### Gotchas
86
+
87
+ - Only the **server** may `Spawn()`/`Despawn()` NetworkObjects.
88
+ - Writing a `NetworkVariable` you don't have write permission for silently no-ops — check perms first.
89
+ - `IsOwner` ≠ `IsServer`; on a listen host both can be true. Gate authoritative logic on `IsServer`.
90
+ - Legacy RPC name-suffix rule is a compile error if violated; the unified `[Rpc]` has no such rule.
91
+
92
+ ---
93
+
94
+ ## Unreal Engine 5.x — Actor replication
95
+
96
+ Replication is per-`AActor`. Turn it on (`bReplicates = true` in the constructor or `SetReplicates(true)`),
97
+ mark properties, and register them. RPCs are `UFUNCTION`s with network specifiers.
98
+
99
+ ### Replicated properties
100
+
101
+ ```cpp
102
+ // Header
103
+ UPROPERTY(Replicated)
104
+ int32 Score = 0;
105
+
106
+ UPROPERTY(ReplicatedUsing = OnRep_Health) // RepNotify: OnRep_Health() runs on clients on change
107
+ float Health = 100.f;
108
+
109
+ UFUNCTION() void OnRep_Health();
110
+
111
+ // Cpp — every replicated prop MUST be registered here
112
+ void AMyPawn::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const {
113
+ Super::GetLifetimeReplicatedProps(Out);
114
+ DOREPLIFETIME(AMyPawn, Score);
115
+ DOREPLIFETIME(AMyPawn, Health);
116
+ // DOREPLIFETIME_CONDITION(AMyPawn, Ammo, COND_OwnerOnly); // owner-only relevancy
117
+ }
118
+ ```
119
+
120
+ `ReplicatedUsing` (RepNotify) is how you react to a change — use it instead of polling the value in `Tick`.
121
+ The **push model** (`SetReplicatedByCondition` / `MARK_PROPERTY_DIRTY`) avoids per-frame comparison for
122
+ high-count actors.
123
+
124
+ ### RPCs
125
+
126
+ ```cpp
127
+ UFUNCTION(Server, Reliable, WithValidation) // client → server (validated)
128
+ void ServerFire(FVector Target);
129
+ bool ServerFire_Validate(FVector Target) { return Target.SizeSquared() < 1e8f; }
130
+ void ServerFire_Implementation(FVector Target) { /* server applies */ }
131
+
132
+ UFUNCTION(Client, Reliable) // server → owning client
133
+ void ClientNotifyHit(int32 Dmg);
134
+
135
+ UFUNCTION(NetMulticast, Unreliable) // server → all relevant clients
136
+ void MulticastMuzzleFx(FVector At);
137
+ ```
138
+
139
+ - `Server` RPCs run on the server, callable by the owning client; add `WithValidation` for gameplay-critical
140
+ ones and reject cheating inputs in `_Validate`.
141
+ - `Client` RPCs run on the actor's owning client only.
142
+ - `NetMulticast` runs on the server and all relevant clients — cosmetic events; keep unreliable.
143
+ - `Reliable` vs `Unreliable`: reliable is guaranteed+ordered but can saturate; reserve for gameplay.
144
+
145
+ ### Roles, authority, relevancy
146
+
147
+ - `HasAuthority()` → true on the server (or standalone). Guard server logic with it.
148
+ - `GetLocalRole()`: `ROLE_Authority` (server), `ROLE_AutonomousProxy` (the owning client's predicted pawn),
149
+ `ROLE_SimulatedProxy` (an observed remote actor). Prediction lives on autonomous proxies.
150
+ - **Relevancy** controls what each client receives: `NetCullDistanceSquared`, `bAlwaysRelevant`,
151
+ `bOnlyRelevantToOwner`, override `IsNetRelevantFor`, and **dormancy** (`SetNetDormancy(DORM_DormantAll)`)
152
+ to stop replicating quiescent actors. This is the main bandwidth/anti-wallhack lever.
153
+
154
+ ### Movement & prediction
155
+
156
+ `UCharacterMovementComponent` already implements client-side prediction + server reconciliation for
157
+ character movement — use `ACharacter` and let it do the work rather than hand-rolling. For abilities, the
158
+ Gameplay Ability System (GAS) has prediction; the Network Prediction plugin generalizes it (experimental).
159
+
160
+ ### Gotchas
161
+
162
+ - Forgetting `DOREPLIFETIME` = the property silently never replicates.
163
+ - RPC specifiers are directional: calling a `Server` RPC from the server, or a `Client` RPC from a client,
164
+ does nothing useful — respect the flow (client→Server, server→Client/Multicast).
165
+ - Actors only replicate if `bReplicates` is true *and* the actor is net-relevant to that client.
166
+ - On a listen server the host is authority + local player; `HasAuthority()` is your gate.
@@ -0,0 +1,242 @@
1
+ ---
2
+ name: gamedev-pathing
3
+ description: "Use when implementing AI navigation or pathfinding in a game - moving enemies/NPCs/units toward goals, \"how do enemies navigate the level\", A*/Dijkstra/JPS/HPA* search, choosing grid vs waypoint-graph vs navmesh, baking a navmesh and tuning agent radius/height, off-mesh/nav links, dynamic obstacles and re-baking, steering (seek/flee/arrive/pursue/wander/path-following/flocking), local avoidance (RVO/ORCA, crowds), and flow fields for many agents to one goal. Per-engine: Godot 4.x (NavigationServer2D/3D, NavigationRegion, NavigationAgent, get_next_path_position), Unity (NavMeshAgent + NavMeshObstacle + AI Navigation baking), Unreal (RecastNavMesh + AIController + Behavior Trees/EQS). Triggers: \"my enemies get stuck\", \"units clump together\", \"navmesh not updating\", \"agentes se atascan\". NOT physics/rigidbody collision response or character-controller tuning (gamedev-physics), and NOT high-level enemy AI behaviour design like aggro/states/difficulty (game-design)."
4
+ tags: [pathfinding, navmesh, navigation, steering, a-star]
5
+ recommends: [godot, unity, unreal, gamedev-physics]
6
+ profiles: [full]
7
+ origin: risco
8
+ ---
9
+
10
+ # Game AI navigation & pathfinding
11
+
12
+ Move agents through a level intelligently and per-engine-correctly: pick a world representation,
13
+ run the right search, then follow the result with steering + local avoidance. This skill owns the
14
+ **navigation stack**; it stops where physics collision response and high-level behaviour design begin.
15
+
16
+ ## Version targets & the API ban-list (read first)
17
+
18
+ Navigation APIs were renamed hard between engine generations. Emitting an old name compiles into
19
+ nothing or silent breakage. Target these versions; never emit the banned column.
20
+
21
+ | Engine (target) | NEVER emit (deprecated / removed) | Use instead |
22
+ | --- | --- | --- |
23
+ | **Godot 4.x** | `Navigation` / `NavigationMeshInstance` / `Navigation2D` nodes | `NavigationServer2D/3D` + `NavigationRegion2D/3D` |
24
+ | Godot 4.x | `agent.get_next_location()` | `agent.get_next_path_position()` |
25
+ | Godot 4.x | `NavigationAgent.set_target_location()` | set the `target_position` property |
26
+ | Godot 4.x | reading `velocity` directly when avoidance is on | feed `set_velocity()`, read the `velocity_computed(safe)` signal |
27
+ | **Unity (AI Navigation pkg)** | the legacy **Navigation window** static bake, `Navigation Static` flag, built-in `OffMeshLink` component | `NavMeshSurface` (`com.unity.ai.navigation`), `NavMeshLink`, `NavMeshModifier` |
28
+ | Unity | `agent.destination = p` then reading a path same frame | `SetDestination(p)`, then gate on `!pathPending && remainingDistance <= stoppingDistance` |
29
+ | **Unreal (UE5)** | hand-rolling A* over your own grid for pawns | `AAIController::MoveTo*` over `RecastNavMesh`; BT `MoveTo` task |
30
+ | Unreal | expecting a **Static** navmesh to react to spawned geometry | set RecastNavMesh **Runtime Generation = Dynamic** (or Dynamic Modifiers Only) |
31
+
32
+ ## The two-layer mental model (center of gravity)
33
+
34
+ The single most common navigation bug is collapsing two jobs into one. Keep them separate:
35
+
36
+ 1. **Global pathfinding — *where* to go.** A discrete search (A* / navmesh query) over a static-ish
37
+ representation, run *occasionally* (on new goal, or throttled), returns a **corridor of waypoints**.
38
+ 2. **Local steering + avoidance — *how* to move this frame.** A cheap per-frame vector: follow the
39
+ corridor, dodge other agents and dynamic obstacles, respect acceleration. Runs *every frame*.
40
+
41
+ Path for the map, steering for the moment. Symptoms of merging them: re-running A* every frame (CPU
42
+ melts), or agents that walk the path but pile into each other (no local avoidance). Every engine's
43
+ `NavMeshAgent` / `NavigationAgent` bundles both — know which layer you are configuring.
44
+
45
+ ## Choosing a representation
46
+
47
+ | Representation | Fits | Cost / caveat |
48
+ | --- | --- | --- |
49
+ | **Uniform grid** | tile/2D games, RTS, roguelikes, destructible terrain | many nodes; needs path smoothing to avoid staircase paths; JPS accelerates it |
50
+ | **Waypoint graph** | sparse hand-placed routes, patrol nets, rails, racing lines | cheap; agents snap to nodes, off-graph space is invisible — brittle for open areas |
51
+ | **Navmesh** | 3D and most open 2D worlds; the default for character movement | bake step; represents *walkable surface* as convex polys — fewer nodes, natural paths |
52
+
53
+ Rule: **navmesh for free-roaming characters, grid for tile-locked/destructible worlds, waypoint graph
54
+ only for constrained routes.** Detail & path-smoothing (funnel algorithm) → `references/search-algorithms.md`.
55
+
56
+ ## Search algorithms (essentials)
57
+
58
+ - **A\*** — the default. Dijkstra + a heuristic `h(n)` that estimates remaining cost. Admissible `h`
59
+ (never *over*-estimates) ⇒ optimal path; use **octile** distance on 8-connected grids, **Euclidean**
60
+ on navmesh/any-angle. `h` must also stay ≤ true edge costs (consistency) to skip re-expansions.
61
+ - **Dijkstra** — A* with `h=0`. Use when there is **no single goal** (nearest of many exits) or you need
62
+ the full cost field (see flow fields). Slower than A* to one target.
63
+ - **Weighted / greedy** — inflate `h` (`f = g + w·h`, `w>1`) for faster, slightly suboptimal paths when
64
+ frame budget beats optimality.
65
+ - **JPS (Jump Point Search)** — A* speedup for **uniform-cost grids only**; skips symmetric paths, often
66
+ 10×+ fewer expansions. Not for weighted terrain or navmeshes.
67
+ - **Hierarchical / HPA\*** — for **huge maps**: partition into clusters, path cluster-to-cluster, refine
68
+ locally. Reach for it when a single flat A* blows the frame budget or you have thousands of tiles.
69
+
70
+ Always run search off a **binary-heap open set** and a closed set. Pseudocode, tie-breaking, any-angle
71
+ (Theta*), and the funnel string-pull → `references/search-algorithms.md`.
72
+
73
+ ## Navmesh workflow (essentials)
74
+
75
+ 1. **Bake** the walkable surface from level geometry. Key params: **agent radius** (how far the mesh is
76
+ shrunk from walls — the #1 knob), **agent height**, **max step/climb**, **max slope**, cell size.
77
+ 2. **One navmesh per agent size.** A tank and a rat need different bakes; do not share one mesh and hope.
78
+ 3. **Regions / areas + costs** — tag surfaces (water, mud, road) with a traversal cost so paths prefer
79
+ roads and avoid hazards, rather than deleting the area outright.
80
+ 4. **Off-mesh / nav links** — explicit edges for jumps, ladders, teleporters, doors: places agents move
81
+ but no polygon connects. Godot `NavigationLink`, Unity `NavMeshLink`, Unreal `NavLinkProxy`.
82
+ 5. **Dynamic obstacles** — two tools, do not confuse them:
83
+ - **Carving obstacle** (Unity `NavMeshObstacle` carving, Godot `NavigationObstacle`, Unreal
84
+ `NavModifier`): punches a hole so *global* paths route around a placed prop. Costs a re-carve on move.
85
+ - **Local avoidance** (RVO/ORCA): agents dodge *without* touching the mesh — for other moving agents.
86
+ 6. **Re-baking** — full rebake is expensive; prefer **tile/partial** rebake or carving for runtime
87
+ changes. Bake offline for static levels. Full param table + per-engine baking → `references/navmesh-workflow.md`.
88
+
89
+ ## Steering & local avoidance (essentials)
90
+
91
+ Steering = a desired-velocity vector combined and clamped to max force/speed. Primitives:
92
+
93
+ - **Seek / Flee** — accelerate toward / away from a target point.
94
+ - **Arrive** — seek that ramps speed down inside a slowing radius (no overshoot/jitter at the goal).
95
+ - **Pursue / Evade** — seek/flee the target's *predicted future* position, not its current one.
96
+ - **Wander** — smoothed random heading for idle/ambient motion.
97
+ - **Path following** — steer toward a look-ahead point along the A*/navmesh corridor, not the far goal.
98
+ - **Flocking** = separation + alignment + cohesion (Reynolds boids) for groups.
99
+
100
+ Combine either as a **weighted sum** (simple) or **priority/arbitration** (avoidance wins over cohesion).
101
+
102
+ **Local avoidance (RVO / ORCA):** each agent picks a velocity that is collision-free assuming neighbours
103
+ share the burden (*reciprocal* — hence no oscillating "dance"). This is avoidance, **not** collision
104
+ *response*: it changes intended velocity *before* moving; the physics/collision solver is a separate,
105
+ last-resort backstop (→ `gamedev-physics`). For dense crowds use the engine's crowd/avoidance system.
106
+
107
+ **Flow fields** — for **many agents → one (or few) goals** (RTS swarm, tower-defense creeps): run one
108
+ **Dijkstra from the goal** over the grid to build a cost/integration field, derive a per-cell direction
109
+ vector once, then every agent just samples its cell. O(1) per agent vs one A* each. Full derivation,
110
+ boids weights, and RVO intuition → `references/steering-and-avoidance.md`.
111
+
112
+ ## Per-engine mapping
113
+
114
+ ### Godot 4.x (`NavigationAgent3D` — 2D is identical with `2D` suffix)
115
+
116
+ ```gdscript
117
+ extends CharacterBody3D
118
+ @onready var agent: NavigationAgent3D = $NavigationAgent3D
119
+ @export var speed := 4.0
120
+
121
+ func _ready() -> void:
122
+ agent.avoidance_enabled = true # RVO local avoidance
123
+ agent.velocity_computed.connect(_on_velocity_computed)
124
+
125
+ func set_goal(p: Vector3) -> void:
126
+ agent.target_position = p # property, NOT set_target_location()
127
+
128
+ func _physics_process(_delta: float) -> void:
129
+ if agent.is_navigation_finished():
130
+ return
131
+ var next := agent.get_next_path_position() # 4.x name (was get_next_location)
132
+ var desired := global_position.direction_to(next) * speed
133
+ agent.set_velocity(desired) # feed RVO; result via signal
134
+
135
+ func _on_velocity_computed(safe: Vector3) -> void: # only fires while avoidance enabled
136
+ velocity = safe
137
+ move_and_slide()
138
+ ```
139
+
140
+ One-off query without an agent: `NavigationServer3D.map_get_path(map_rid, from, to, true)`. Rebake a
141
+ region at runtime: `$NavigationRegion3D.bake_navigation_mesh()`. Off-mesh: `NavigationLink3D`. If
142
+ avoidance is **off**, skip the signal and `move_and_slide()` with `desired` directly.
143
+
144
+ ### Unity (AI Navigation package + `NavMeshAgent`)
145
+
146
+ ```csharp
147
+ using UnityEngine;
148
+ using UnityEngine.AI;
149
+
150
+ [RequireComponent(typeof(NavMeshAgent))]
151
+ public class Chaser : MonoBehaviour {
152
+ NavMeshAgent agent;
153
+ void Awake() => agent = GetComponent<NavMeshAgent>();
154
+
155
+ public void Chase(Transform target) => agent.SetDestination(target.position);
156
+
157
+ public bool ReachedGoal() =>
158
+ !agent.pathPending && agent.remainingDistance <= agent.stoppingDistance
159
+ && (!agent.hasPath || agent.velocity.sqrMagnitude < 0.01f);
160
+ }
161
+ ```
162
+
163
+ Bake: add a **`NavMeshSurface`** to a scene root and Bake (the legacy Navigation window is gone).
164
+ Runtime rebake: `surface.BuildNavMesh()`. Dynamic blockers: **`NavMeshObstacle`** (enable *Carving* for
165
+ stationary props, leave off for moving agents so RVO handles them). Off-mesh: **`NavMeshLink`**. Local
166
+ avoidance quality: `agent.obstacleAvoidanceType`. Area costs: `agent.SetAreaCost(area, cost)`.
167
+
168
+ ### Unreal (UE5 — `RecastNavMesh` + `AIController` + Behavior Tree / EQS)
169
+
170
+ 1. Drop a **`NavMeshBoundsVolume`** around playable space → a `RecastNavMesh` auto-generates. For
171
+ runtime changes set its **Runtime Generation = Dynamic** (or *Dynamic Modifiers Only*); spawned
172
+ geometry must have collision + **Can Ever Affect Navigation**.
173
+ 2. Possess the pawn with an **`AAIController`**. Movement:
174
+
175
+ ```cpp
176
+ AAIController* AICon = Cast<AAIController>(GetController());
177
+ AICon->MoveToActor(TargetActor, /*AcceptanceRadius*/ 50.f); // or MoveToLocation(FVector)
178
+ ```
179
+
180
+ 3. **Behavior Tree + Blackboard** for decisions; the **`MoveTo`** task walks the navmesh. **EQS**
181
+ (Environment Query System) picks *where* to go (cover, flank, nearest item) via scored queries.
182
+ 4. Costs/holes: **`NavModifierVolume`** + `NavArea` classes. Off-mesh: **`NavLinkProxy`**. Crowd
183
+ avoidance: enable the **Detour Crowd** manager (or `DetourCrowdAIController`) for RVO on many agents.
184
+
185
+ > High-level BT *design* (states, aggro, difficulty) is `game-design`; this skill wires the BT's
186
+ > movement/EQS tasks to navigation, not the decision tree's semantics.
187
+
188
+ ## Guardrails / gotchas
189
+
190
+ - **Agent radius mismatch** is the top "stuck in doorways / clipping walls" cause — bake with the real radius.
191
+ - Bake covers **static** geometry; runtime-spawned meshes need a rebake, a carving obstacle, or a nav link.
192
+ - Path returns empty when start/end are **off the navmesh** or in disconnected islands — snap to nearest poly and check reachability first.
193
+ - Avoidance (RVO) ≠ collision (physics). Agents can still overlap under pressure; that is expected, not a physics bug.
194
+ - Off-mesh links are **directional and manual** — a jump-down link does not imply a jump-up link.
195
+ - Smooth grid paths (funnel / string-pull) or agents walk visible zig-zag staircases.
196
+
197
+ ## Anti-patterns / rationalizations → STOP
198
+
199
+ | Rationalization | Reality / do instead |
200
+ | --- | --- |
201
+ | "Just A* every frame, it's fine" | Melts CPU at scale; path on goal-change, steer between frames. |
202
+ | "One navmesh bake works for every unit" | Radius/height differ; bake per agent size or use agent-type profiles. |
203
+ | "Agents clip through each other — physics bug" | Missing **local avoidance**; enable RVO/crowd, that's the navigation layer. |
204
+ | "Navmesh doesn't see my spawned wall" | Static bake; set Dynamic/rebake, or add a carving obstacle/nav link. |
205
+ | "I'll grid-search my own A* in Unity/Unreal" | Reinvents the built-in navmesh; use `NavMeshAgent` / `AAIController::MoveTo`. |
206
+ | "500 zombies each run A* to the player" | That's a **flow field**: one Dijkstra from the goal, agents sample cells. |
207
+ | "Set `agent.destination` and read the path same frame" | Path is async (`pathPending`); gate on it before trusting `remainingDistance`. |
208
+ | "Bigger heuristic = smarter" | Over-estimating `h` breaks A* optimality; keep it admissible (or weight it *knowingly*). |
209
+
210
+ ## Related skills
211
+
212
+ - [`godot`](../godot/SKILL.md) — GDScript/scene specifics; this skill owns the navigation subsystem it plugs into.
213
+ - [`unity`](../unity/SKILL.md) — Unity/C# project setup; here for the NavMesh + AI Navigation package details.
214
+ - [`unreal`](../unreal/SKILL.md) — UE5/Blueprint/C++; here for RecastNavMesh + AIController/BT/EQS wiring.
215
+ - [`gamedev-physics`](../gamedev-physics/SKILL.md) — collision *response*, rigidbodies, character controllers; steering decides intended velocity, physics resolves the contact.
216
+ - `game-design` — high-level enemy behaviour, aggro, encounter/difficulty design (what the AI *decides*, not how it *moves*).
217
+
218
+ ## Checklist
219
+
220
+ - [ ] Representation chosen deliberately (navmesh / grid / waypoint) and justified for the world type.
221
+ - [ ] Global path and local steering are **separate layers**; A* is not per-frame.
222
+ - [ ] Navmesh baked with the correct **agent radius/height/step/slope** (per agent size if they differ).
223
+ - [ ] Area costs set for hazards/terrain instead of hard-deleting walkable space.
224
+ - [ ] Off-mesh/nav links placed for every jump/ladder/teleport gap (correct direction).
225
+ - [ ] Dynamic obstacles handled by carving *or* rebake *or* local avoidance — the right one for each case.
226
+ - [ ] Local avoidance / crowd enabled if multiple agents share space; flow field used for many→one-goal.
227
+ - [ ] Correct **current** engine APIs (no banned names from the version table); paths validated for reachability.
228
+ - [ ] Grid paths smoothed (funnel) so movement isn't staircased.
229
+
230
+ ## Project grounding (02-DOCS + CLAUDE.md)
231
+
232
+ When this runs in a project with a `02-DOCS/` layer (the [`harness`](../harness/SKILL.md) wiki), record
233
+ the project's navigation decisions in `02-DOCS/wiki/stack/gamedev-pathing.md` (indexed from
234
+ `02-DOCS/wiki/index.md`): engine + version, chosen representation, bake settings (agent sizes, cell
235
+ size), avoidance/crowd choice, custom links/areas. Read it first on every use; bump its `Updated` date
236
+ when a convention changes. No `02-DOCS/` layer? Skip silently — conventions are *recorded, not gated*.
237
+
238
+ ## See Also — local references (read when)
239
+
240
+ - `references/search-algorithms.md` — representations, A*/Dijkstra/JPS/HPA* pseudocode, heuristics & admissibility, tie-breaking, any-angle, funnel smoothing.
241
+ - `references/navmesh-workflow.md` — bake-parameter table, areas/costs, off-mesh links, obstacles vs carving vs partial rebake, per-engine baking, debugging.
242
+ - `references/steering-and-avoidance.md` — steering primitives, path following, boids weights, RVO/ORCA intuition, crowds, flow-field construction.
@@ -0,0 +1,14 @@
1
+ # Eval harness — `gamedev-pathing` skill
2
+
3
+ `cases.yaml` is a human/CI-readable trigger-and-capability spec, not an automated runner. It asserts two
4
+ things. **Triggering**: the skill fires on real navigation asks — engine-specific ("enemies stuck in
5
+ Godot 4", "Unreal navmesh won't see a spawned crate"), representation/algorithm theory (A* heuristics,
6
+ grid vs navmesh), crowd/avoidance ("units dance and pile up"), and a Catalan off-mesh-link phrasing — and
7
+ stays quiet on near-misses that route to `gamedev-physics` (collision response), `game-design` (what the
8
+ AI decides), `unity` (lightmap baking — the "bake" false friend), `godot` (animation state machine), and
9
+ `gamedev-multiplayer` (netcode). **Capability**: with `SKILL.md` + references loaded, the answer uses
10
+ the current Godot 4.x API (`target_position`, `get_next_path_position`, `velocity_computed` — never the
11
+ Godot-3 names), separates global pathfinding from per-frame steering, and reaches for a flow field for
12
+ many-agents-to-one-goal. Run 3–5 trials per prompt (LLM routing is non-deterministic); a prompt passes on
13
+ a majority. Judge `capability` by reading the output against `must_include`; the with-skill answer should
14
+ clearly beat a no-skill baseline.
@@ -0,0 +1,49 @@
1
+ skill: gamedev-pathing
2
+
3
+ should_trigger:
4
+ - prompt: "My enemies in Godot 4 keep getting stuck on walls and never reach the player. How do I get them to actually path around the level?"
5
+ why: "Core trigger: AI navigation in Godot 4.x. Should produce a NavigationAgent3D loop with target_position + get_next_path_position, and flag agent-radius baking as the stuck-on-walls cause."
6
+ - prompt: "I've got 400 zombies all chasing the player in my Unity horde game and the framerate tanks because each one runs its own pathfinding. What's the right approach?"
7
+ why: "Many-agents-to-one-goal is the flow-field trigger; should recommend a Dijkstra-from-goal flow field instead of per-agent A*, plus local avoidance."
8
+ - prompt: "In Unreal my AI won't walk around a crate I spawn at runtime — it just walks straight through where it should be. The navmesh doesn't seem to update."
9
+ why: "Dynamic-obstacle / re-bake trigger; should point to RecastNavMesh Runtime Generation = Dynamic (or a NavModifier), Can Ever Affect Navigation, not a static bake."
10
+ - prompt: "What's the difference between using a grid, a waypoint graph, and a navmesh for pathfinding, and when should I pick each?"
11
+ why: "Representation trade-offs are a named core section; should give the grid/waypoint/navmesh table with the 'navmesh for free-roaming characters' rule."
12
+ - prompt: "My squad of units all pile onto the exact same spot and jitter against each other when they reach the destination. How do I make them spread out and stop dancing?"
13
+ why: "Local avoidance / crowd trigger (RVO/ORCA reciprocal avoidance); should distinguish avoidance from collision and enable the engine's crowd/avoidance system, plus arrive-behaviour to stop jitter."
14
+ - prompt: "Explain A* — how the heuristic works, what admissible means, and when I'd use JPS or a hierarchical version instead."
15
+ why: "Search-algorithm core section; should cover f=g+h, admissibility/consistency, octile vs Euclidean h, JPS for uniform grids, HPA* for huge maps."
16
+ - prompt: "Els meus NPCs no saben baixar d'una plataforma a una altra al navmesh, com els connecto?"
17
+ why: "Catalan off-mesh-link trigger; should recommend an explicit directional nav link (NavigationLink / NavMeshLink / NavLinkProxy)."
18
+
19
+ should_not_trigger:
20
+ - prompt: "My character controller sinks into the floor and bounces off walls weirdly — the rigidbody collision response feels wrong."
21
+ route_to: "gamedev-physics"
22
+ why: "Collision response / rigidbody / character-controller tuning is physics, not navigation; steering only sets intended velocity, physics resolves the contact."
23
+ - prompt: "Design my enemy AI: when should the boss enrage, how does aggro/threat work, and how do difficulty tiers change its behaviour?"
24
+ route_to: "game-design"
25
+ why: "High-level behaviour/encounter/difficulty design is what the AI decides, not how it moves through space — belongs to game-design."
26
+ - prompt: "Write a GDScript state machine for my player's idle/run/jump/attack animation states in Godot."
27
+ route_to: "godot"
28
+ why: "General GDScript / animation state machine is engine-language work, not the navigation subsystem."
29
+ - prompt: "Set up a dedicated server so multiple players' positions stay in sync with low latency."
30
+ route_to: "gamedev-multiplayer"
31
+ why: "Netcode / replication / lag compensation is multiplayer networking, unrelated to pathfinding."
32
+ - prompt: "How do I bake lighting and set up baked global illumination for my Unity level?"
33
+ route_to: "unity"
34
+ why: "'Baking' here is lightmaps, not a navmesh; near-miss on the word 'bake' that must route to the engine skill, not navigation."
35
+
36
+ capability:
37
+ - scenario: "In Godot 4.x, make a CharacterBody3D enemy chase the player across a baked navmesh, pathing around static geometry and avoiding other enemies, without getting stuck in doorways."
38
+ must_include:
39
+ - "Uses a NavigationAgent3D with the `target_position` property (NOT the removed set_target_location) to set the goal"
40
+ - "Calls `get_next_path_position()` each physics frame (NOT the Godot-3 `get_next_location()`) and moves toward it"
41
+ - "Enables avoidance (`avoidance_enabled = true`), feeds `set_velocity(desired)`, and reads the safe velocity from the `velocity_computed` signal before move_and_slide()"
42
+ - "Separates global pathfinding (occasional, on new goal) from per-frame steering/movement — does not re-run a full search every frame"
43
+ - "Names agent radius in the navmesh bake as the fix for getting stuck in doorways / clipping walls"
44
+ - "Notes that local avoidance (RVO) is distinct from physics collision response"
45
+ - scenario: "Advise on moving 500+ units to a single rally point in an RTS efficiently."
46
+ must_include:
47
+ - "Recommends a flow field instead of per-agent A*"
48
+ - "Describes building it via Dijkstra/BFS from the goal to make an integration/cost field, then a per-cell direction vector agents sample in O(1)"
49
+ - "Layers local avoidance / separation on top so units don't stack on the goal cell"