@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.
- package/README.md +8 -2
- package/manifest.json +241 -2
- package/package.json +1 -1
- package/scripts/lib/domains.js +1 -0
- package/scripts/lib/recommend.js +4 -0
- package/skills/game-design/SKILL.md +170 -0
- package/skills/game-design/evals/README.md +13 -0
- package/skills/game-design/evals/cases.yaml +47 -0
- package/skills/game-design/references/mechanics-and-loops.md +78 -0
- package/skills/game-design/references/playtesting-and-scope.md +58 -0
- package/skills/game-design/references/progression-and-economy.md +70 -0
- package/skills/game-storytelling/SKILL.md +159 -0
- package/skills/game-storytelling/evals/README.md +28 -0
- package/skills/game-storytelling/evals/cases.yaml +51 -0
- package/skills/game-storytelling/references/branching-and-state.md +134 -0
- package/skills/game-storytelling/references/dialogue-tools.md +117 -0
- package/skills/game-storytelling/references/environmental-storytelling.md +71 -0
- package/skills/gamedev-multiplayer/SKILL.md +250 -0
- package/skills/gamedev-multiplayer/evals/README.md +12 -0
- package/skills/gamedev-multiplayer/evals/cases.yaml +44 -0
- package/skills/gamedev-multiplayer/references/godot-high-level-multiplayer.md +133 -0
- package/skills/gamedev-multiplayer/references/prediction-and-latency.md +104 -0
- package/skills/gamedev-multiplayer/references/unity-and-unreal-netcode.md +166 -0
- package/skills/gamedev-pathing/SKILL.md +242 -0
- package/skills/gamedev-pathing/evals/README.md +14 -0
- package/skills/gamedev-pathing/evals/cases.yaml +49 -0
- package/skills/gamedev-pathing/references/navmesh-workflow.md +110 -0
- package/skills/gamedev-pathing/references/search-algorithms.md +135 -0
- package/skills/gamedev-pathing/references/steering-and-avoidance.md +120 -0
- package/skills/gamedev-physics/SKILL.md +225 -0
- package/skills/gamedev-physics/evals/README.md +12 -0
- package/skills/gamedev-physics/evals/cases.yaml +54 -0
- package/skills/gamedev-physics/references/character-controllers.md +120 -0
- package/skills/gamedev-physics/references/determinism-and-queries.md +124 -0
- package/skills/gamedev-physics/references/layers-and-masks.md +115 -0
- package/skills/gamedev-shaders/SKILL.md +252 -0
- package/skills/gamedev-shaders/evals/README.md +14 -0
- package/skills/gamedev-shaders/evals/cases.yaml +45 -0
- package/skills/gamedev-shaders/references/effect-recipes.md +205 -0
- package/skills/gamedev-shaders/references/godot-shading-language.md +133 -0
- package/skills/gamedev-shaders/references/unity-and-unreal-shaders.md +169 -0
- package/skills/gamedev-shipping/SKILL.md +236 -0
- package/skills/gamedev-shipping/evals/README.md +9 -0
- package/skills/gamedev-shipping/evals/cases.yaml +55 -0
- package/skills/gamedev-shipping/references/mobile-and-desktop.md +127 -0
- package/skills/gamedev-shipping/references/stores-and-ci.md +162 -0
- package/skills/gamedev-shipping/references/web-export-headers.md +140 -0
- package/skills/godot/SKILL.md +319 -0
- package/skills/godot/evals/README.md +13 -0
- package/skills/godot/evals/cases.yaml +50 -0
- package/skills/godot/references/export-and-testing.md +102 -0
- package/skills/godot/references/gdscript-style.md +84 -0
- package/skills/godot/references/godot3-to-4-traps.md +89 -0
- package/skills/godot/references/nodes-scenes-resources.md +109 -0
- package/skills/level-design/SKILL.md +175 -0
- package/skills/level-design/evals/README.md +35 -0
- package/skills/level-design/evals/cases.yaml +58 -0
- package/skills/level-design/references/blockout-to-art.md +118 -0
- package/skills/level-design/references/guidance-and-readability.md +152 -0
- package/skills/level-design/references/pacing-and-encounters.md +142 -0
- package/skills/roast-me/tools/adapters/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/base.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/claude.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/codex.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/gemini.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/registry.cpython-312.pyc +0 -0
- package/skills/unity/SKILL.md +282 -0
- package/skills/unity/evals/README.md +8 -0
- package/skills/unity/evals/cases.yaml +43 -0
- package/skills/unity/references/build-and-platforms.md +65 -0
- package/skills/unity/references/input-system.md +102 -0
- package/skills/unity/references/lifecycle-deep-dive.md +75 -0
- package/skills/unreal/SKILL.md +270 -0
- package/skills/unreal/evals/README.md +12 -0
- package/skills/unreal/evals/cases.yaml +45 -0
- package/skills/unreal/references/blueprint-cpp-parity.md +109 -0
- package/skills/unreal/references/gameplay-framework.md +95 -0
- package/skills/unreal/references/packaging.md +83 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Navmesh workflow — baking, links, dynamic obstacles, per-engine
|
|
2
|
+
|
|
3
|
+
Depth for the "Navmesh workflow" section of SKILL.md. A navmesh is the walkable surface expressed as
|
|
4
|
+
convex polygons; search runs over polygon adjacency, and movement follows the corridor via the funnel
|
|
5
|
+
algorithm (see `search-algorithms.md`).
|
|
6
|
+
|
|
7
|
+
## Bake parameters (the knobs that matter)
|
|
8
|
+
|
|
9
|
+
Every engine bakes by voxelizing collision geometry and reconstructing walkable polys (Recast under the
|
|
10
|
+
hood in Unity and Unreal; Godot has its own baker). The parameters map closely across engines:
|
|
11
|
+
|
|
12
|
+
| Parameter | What it does | Getting it wrong |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| **Agent radius** | Shrinks the mesh away from walls by this radius | Too small → agents clip walls / stick in doorways; too large → valid gaps vanish |
|
|
15
|
+
| **Agent height** | Min vertical clearance a poly must have | Agents path under geometry they can't fit beneath |
|
|
16
|
+
| **Max step / climb** | Tallest ledge treated as walkable (stairs, curbs) | Stairs become walls, or agents "climb" impossible ledges |
|
|
17
|
+
| **Max slope** | Steepest walkable incline (degrees) | Ramps drop out, or agents walk up cliffs |
|
|
18
|
+
| **Cell size / voxel size** | Bake resolution | Coarse → missing thin walkways; fine → slow bake, huge data |
|
|
19
|
+
| **Min region area** | Discards tiny isolated islands | Noise polys, or losing small legit platforms |
|
|
20
|
+
|
|
21
|
+
**One bake per agent size.** A large enemy and a small one need different radius/height; share a mesh
|
|
22
|
+
only if their footprints truly match. Unity/Unreal support multiple **agent types/profiles**; Godot uses
|
|
23
|
+
separate `NavigationMesh` resources or navigation layers.
|
|
24
|
+
|
|
25
|
+
## Regions, areas & costs
|
|
26
|
+
|
|
27
|
+
Tag surfaces with an **area type** carrying a traversal **cost multiplier** so paths *prefer* or *avoid*
|
|
28
|
+
terrain instead of it being all-or-nothing:
|
|
29
|
+
|
|
30
|
+
- Road = 0.5, grass = 1, mud = 4, water = 8 → agents naturally favour roads, cross mud only if shorter.
|
|
31
|
+
- Set an area's cost to *very high* rather than deleting it when you want "avoid unless no alternative".
|
|
32
|
+
- **Navigation layers / area masks** let different agents see different areas (e.g. only amphibious units
|
|
33
|
+
path through water) by filtering which polygons a query considers.
|
|
34
|
+
|
|
35
|
+
## Off-mesh / nav links
|
|
36
|
+
|
|
37
|
+
Explicit connections where an agent can move but no polygon adjacency exists: jump-downs, ladders,
|
|
38
|
+
teleporters, ziplines, doorways between disconnected meshes.
|
|
39
|
+
|
|
40
|
+
- **Directional**: define both directions if the agent can traverse both ways (a fall-down link is often
|
|
41
|
+
one-way). Give it a cost so pathing weighs it against walking around.
|
|
42
|
+
- The link only tells pathfinding the two ends connect — you still play the **traversal animation/motion**
|
|
43
|
+
yourself when the agent reaches the link (jump arc, climb, teleport).
|
|
44
|
+
- Godot `NavigationLink2D/3D` · Unity `NavMeshLink` · Unreal `NavLinkProxy` (with smart-link events).
|
|
45
|
+
|
|
46
|
+
## Dynamic obstacles: three different tools
|
|
47
|
+
|
|
48
|
+
Do not confuse these — each solves a different problem:
|
|
49
|
+
|
|
50
|
+
1. **Carving obstacle** — cuts a hole in the navmesh so *global* paths route around a placed object
|
|
51
|
+
(a dropped crate, a closed gate). Global and correct, but re-carving on every move is costly → use for
|
|
52
|
+
**stationary / rarely-moving** props. Unity `NavMeshObstacle` (Carve on) · Godot `NavigationObstacle`
|
|
53
|
+
(with vertices) · Unreal `NavModifierComponent` / `NavModifierVolume`.
|
|
54
|
+
2. **Local avoidance (RVO/ORCA)** — agents steer around each other and moving obstacles *without*
|
|
55
|
+
touching the mesh, every frame. For **other agents** and small moving things. See `steering-and-avoidance.md`.
|
|
56
|
+
3. **Re-bake** — regenerate the mesh (or affected tiles) when the level geometry itself changes
|
|
57
|
+
(destruction, procedural build). Prefer **tile/partial** rebake over full.
|
|
58
|
+
|
|
59
|
+
Rule of thumb: **moving agents → RVO; placed static blockers → carving; geometry changed → rebake.**
|
|
60
|
+
|
|
61
|
+
## Re-baking strategy
|
|
62
|
+
|
|
63
|
+
- **Static levels**: bake once at author time; ship the baked data. No runtime cost.
|
|
64
|
+
- **Occasional changes**: rebake the affected **tiles/region** only, ideally async/off the main thread.
|
|
65
|
+
- **Constant small changes** (a patrolling door): a carving obstacle beats rebaking.
|
|
66
|
+
- Full-map rebake every frame is never the answer — it is the "navmesh not updating, so I rebake
|
|
67
|
+
constantly" anti-pattern.
|
|
68
|
+
|
|
69
|
+
## Per-engine baking specifics
|
|
70
|
+
|
|
71
|
+
### Godot 4.x
|
|
72
|
+
|
|
73
|
+
- `NavigationRegion2D/3D` holds a `NavigationMesh` (3D) / `NavigationPolygon` (2D). Bake in-editor, or at
|
|
74
|
+
runtime with `region.bake_navigation_mesh()`.
|
|
75
|
+
- Source geometry: configure the `NavigationMesh` geometry parsing (parsed geometry type, collision mask,
|
|
76
|
+
source group) so the baker knows which nodes to voxelize.
|
|
77
|
+
- Advanced/procedural: build a `NavigationMeshSourceGeometryData3D` and call
|
|
78
|
+
`NavigationServer3D.bake_from_source_geometry_data(...)`, then assign to the region.
|
|
79
|
+
- Query directly: `NavigationServer3D.map_get_path(map, from, to, optimize, navigation_layers)`.
|
|
80
|
+
- Debug: enable **Visible Navigation** (Debug menu) to draw the mesh in-game.
|
|
81
|
+
|
|
82
|
+
### Unity (AI Navigation package, `com.unity.ai.navigation`)
|
|
83
|
+
|
|
84
|
+
- The legacy Navigation window / `Navigation Static` flag is deprecated. Add a **`NavMeshSurface`**
|
|
85
|
+
component to a root object; set its **Collect Objects** (All / Children / by layer) and **Agent Type**,
|
|
86
|
+
then **Bake**.
|
|
87
|
+
- Runtime: `surface.BuildNavMesh()` rebakes; combine with a data source for large worlds.
|
|
88
|
+
- `NavMeshModifier` overrides area/inclusion per-object; `NavMeshModifierVolume` does it by volume.
|
|
89
|
+
- Multiple agent sizes = multiple Agent Types (Navigation → Agents) + one surface each.
|
|
90
|
+
- Query without moving: `NavMesh.CalculatePath(from, to, areaMask, NavMeshPath)`; sample the mesh with
|
|
91
|
+
`NavMesh.SamplePosition` to snap a point onto it.
|
|
92
|
+
|
|
93
|
+
### Unreal (UE5, `RecastNavMesh`)
|
|
94
|
+
|
|
95
|
+
- A **`NavMeshBoundsVolume`** defines where navigation is generated; a `RecastNavMesh-Default` actor
|
|
96
|
+
appears (tune agent radius/height/step/slope in Project Settings → Navigation System / its details).
|
|
97
|
+
- **Runtime Generation** modes: **Static** (fastest, no runtime change), **Dynamic Modifiers Only**
|
|
98
|
+
(can only *remove* nav via modifiers at runtime), **Dynamic** (any change, rebuilds affected tiles).
|
|
99
|
+
- The mesh is **tiled**; runtime edits rebuild only overlapping tiles. Spawned geometry must have
|
|
100
|
+
collision + **Can Ever Affect Navigation** to influence it.
|
|
101
|
+
- `NavModifierVolume` + `NavArea` subclasses set cost/exclusion. Debug with the **`P`** key (Show
|
|
102
|
+
Navigation) in PIE.
|
|
103
|
+
|
|
104
|
+
## Common failure modes
|
|
105
|
+
|
|
106
|
+
- **Empty path**: start or end is off the mesh, or the two are on disconnected islands. Snap both to the
|
|
107
|
+
nearest polygon and test reachability before trusting the result.
|
|
108
|
+
- **Agents hug walls / stick in doors**: agent radius in the bake ≠ real collider radius.
|
|
109
|
+
- **Spawned wall ignored**: static bake — carve it, add a link, or switch to a dynamic/rebake mode.
|
|
110
|
+
- **Path exists but agent won't take a jump**: missing or wrong-direction off-mesh link.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Search algorithms & world representations
|
|
2
|
+
|
|
3
|
+
Depth for the "Search algorithms" and "Choosing a representation" sections of SKILL.md. Read this when
|
|
4
|
+
you are hand-rolling a search (a grid game, a custom graph, or a debugging session where the engine's
|
|
5
|
+
navmesh isn't enough).
|
|
6
|
+
|
|
7
|
+
## Representations in detail
|
|
8
|
+
|
|
9
|
+
### Uniform grid
|
|
10
|
+
|
|
11
|
+
Cells are nodes; neighbours are the adjacent cells. **4-connected** (N/E/S/W) gives Manhattan movement;
|
|
12
|
+
**8-connected** adds diagonals. On 8-connected grids, forbid *corner-cutting*: only allow a diagonal move
|
|
13
|
+
if both orthogonal neighbours are also walkable, or agents clip wall corners.
|
|
14
|
+
|
|
15
|
+
- Pros: trivial to build/edit, supports destructible/procedural terrain, weighted cells (mud=cost 3).
|
|
16
|
+
- Cons: node count explodes with resolution; raw paths look blocky and hug walls — smooth them (funnel).
|
|
17
|
+
|
|
18
|
+
### Waypoint graph
|
|
19
|
+
|
|
20
|
+
Hand- or tool-placed nodes with explicit edges. Agents path node-to-node, then steer between.
|
|
21
|
+
|
|
22
|
+
- Pros: tiny node count, full designer control (patrol loops, cover-to-cover, racing lines).
|
|
23
|
+
- Cons: movement is confined to edges; anything off-graph is invisible. Poor for large open spaces —
|
|
24
|
+
an agent can't cut across a room the graph didn't anticipate.
|
|
25
|
+
|
|
26
|
+
### Navmesh
|
|
27
|
+
|
|
28
|
+
A set of convex polygons covering the walkable surface. Search runs over the polygon adjacency graph
|
|
29
|
+
(few, large nodes), then the **funnel algorithm** pulls a straight path through the polygon corridor.
|
|
30
|
+
|
|
31
|
+
- Pros: compact, natural any-angle paths, handles 3D height/slopes, one representation for a whole level.
|
|
32
|
+
- Cons: needs a bake step; representing fine dynamic change means re-baking or carving.
|
|
33
|
+
|
|
34
|
+
## A* — the workhorse
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
function A*(start, goal, h):
|
|
38
|
+
open = min-heap keyed by f; push start with g=0, f=h(start,goal)
|
|
39
|
+
gScore = { start: 0 }
|
|
40
|
+
came = {}
|
|
41
|
+
while open not empty:
|
|
42
|
+
current = open.pop() # lowest f
|
|
43
|
+
if current == goal: return reconstruct(came, current)
|
|
44
|
+
for (nbr, stepCost) in neighbours(current):
|
|
45
|
+
tentative = gScore[current] + stepCost
|
|
46
|
+
if tentative < gScore.get(nbr, +inf):
|
|
47
|
+
came[nbr] = current
|
|
48
|
+
gScore[nbr] = tentative
|
|
49
|
+
f = tentative + h(nbr, goal)
|
|
50
|
+
open.push_or_decrease(nbr, f)
|
|
51
|
+
return NO_PATH # goal unreachable
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`f(n) = g(n) + h(n)`: `g` is known cost from start, `h` is the estimated cost to goal.
|
|
55
|
+
|
|
56
|
+
- Use a **binary heap** (or pairing heap) for the open set — a linear scan is the classic "A* is slow" bug.
|
|
57
|
+
- Track a closed/visited set (or the `gScore` map above) so nodes aren't re-expanded needlessly.
|
|
58
|
+
- `neighbours` returns `(node, stepCost)`; diagonal step cost is `√2` (~1.414), not 1.
|
|
59
|
+
|
|
60
|
+
### Heuristics & admissibility
|
|
61
|
+
|
|
62
|
+
- **Admissible**: `h(n)` never *over*-estimates the true remaining cost ⇒ A* returns the optimal path.
|
|
63
|
+
- **Consistent (monotone)**: `h(n) ≤ stepCost(n,m) + h(m)` for every edge ⇒ no node is ever re-opened
|
|
64
|
+
(strictly stronger than admissible). Standard grid/navmesh heuristics below are both.
|
|
65
|
+
- Match `h` to the movement:
|
|
66
|
+
- 4-connected grid → **Manhattan** `|dx|+|dy|`.
|
|
67
|
+
- 8-connected grid → **octile** `max(dx,dy) + (√2−1)·min(dx,dy)`.
|
|
68
|
+
- any-angle / navmesh → **Euclidean** `√(dx²+dy²)`.
|
|
69
|
+
- **Tie-breaking**: many nodes share the same `f`, giving ugly wandering paths. Break ties toward lower
|
|
70
|
+
`h`, or nudge `h` up by a tiny factor (`h *= 1.0 + ε`) so the search leans goalward. This trades strict
|
|
71
|
+
optimality for straighter, faster searches — usually worth it.
|
|
72
|
+
|
|
73
|
+
### Weighted / greedy A*
|
|
74
|
+
|
|
75
|
+
`f = g + w·h` with `w > 1` inflates the heuristic: fewer expansions, path at most `w×` optimal. Good when
|
|
76
|
+
frame budget beats perfection. `w → ∞` degenerates to **greedy best-first** (fast, often ugly, not optimal).
|
|
77
|
+
|
|
78
|
+
## Dijkstra
|
|
79
|
+
|
|
80
|
+
A* with `h = 0`: expands uniformly outward by cost. Reach for it when:
|
|
81
|
+
|
|
82
|
+
- There is **no single target** — find the nearest of several exits/items in one search.
|
|
83
|
+
- You need the **whole cost field** from a source (the basis of flow fields — see steering reference).
|
|
84
|
+
|
|
85
|
+
Slower than A* to one goal because it explores in all directions. **BFS** is the unweighted special case
|
|
86
|
+
(all step costs equal) — fine for simple grids, but Dijkstra/A* generalize to terrain costs.
|
|
87
|
+
|
|
88
|
+
## JPS (Jump Point Search)
|
|
89
|
+
|
|
90
|
+
An A* accelerator for **uniform-cost grids only** (every walkable cell same cost). It exploits grid
|
|
91
|
+
symmetry: instead of expanding every cell, it "jumps" in a straight line until it hits a *jump point* (a
|
|
92
|
+
cell with a forced neighbour caused by an obstacle). Often 10×+ fewer node expansions with the **same
|
|
93
|
+
optimal** result, and it stores fewer nodes.
|
|
94
|
+
|
|
95
|
+
- Requires a uniform-cost, 8-connected grid; **not** applicable to weighted terrain or navmeshes.
|
|
96
|
+
- Pairs well with JPS+ / precomputed jump distances for static maps.
|
|
97
|
+
|
|
98
|
+
## Hierarchical / HPA*
|
|
99
|
+
|
|
100
|
+
For very large maps where a single flat A* misses the frame budget:
|
|
101
|
+
|
|
102
|
+
1. Partition the map into **clusters** (e.g. 10×10 tiles).
|
|
103
|
+
2. Precompute **entrances** between adjacent clusters and intra-cluster distances (an abstract graph).
|
|
104
|
+
3. Search the small abstract graph cluster-to-cluster, then refine each segment with a local A* only
|
|
105
|
+
when/where the agent actually reaches it.
|
|
106
|
+
|
|
107
|
+
Trades a little path optimality for a large speedup and enables **path caching** and streaming. Reach for
|
|
108
|
+
it at thousands-of-tiles scale, or when many agents share long routes.
|
|
109
|
+
|
|
110
|
+
## Any-angle paths (Theta*)
|
|
111
|
+
|
|
112
|
+
Grid A* paths bend only at 45°/90°. **Theta\*** relaxes this: when expanding, if the parent has
|
|
113
|
+
line-of-sight to the neighbour, it links directly to the parent instead of the current cell — yielding
|
|
114
|
+
straight, any-angle paths without a separate smoothing pass. Cheaper alternative: run grid A* then smooth.
|
|
115
|
+
|
|
116
|
+
## Path smoothing — the funnel (string-pulling)
|
|
117
|
+
|
|
118
|
+
Raw grid/navmesh paths waste motion. Two fixes:
|
|
119
|
+
|
|
120
|
+
- **Line-of-sight smoothing** (grids): walk the path; if node `i` has clear LoS to node `i+2`, drop `i+1`.
|
|
121
|
+
Repeat. Removes staircase kinks cheaply.
|
|
122
|
+
- **Funnel algorithm** (navmesh): given the polygon corridor, pull a taut string through the shared
|
|
123
|
+
edges (portals), advancing left/right apexes — produces the shortest path inside the corridor. This is
|
|
124
|
+
what quality navmesh systems run after the polygon search; emulate it if you build your own.
|
|
125
|
+
|
|
126
|
+
## Choosing quickly
|
|
127
|
+
|
|
128
|
+
| Situation | Pick |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| Free-roaming characters, 3D or open 2D | Navmesh + A* over polys + funnel |
|
|
131
|
+
| Tile-locked / destructible / RTS grid | Grid + A* (or JPS if uniform cost) |
|
|
132
|
+
| Constrained routes, patrols, rails | Waypoint graph + A* |
|
|
133
|
+
| Nearest of many goals / cost field | Dijkstra |
|
|
134
|
+
| Thousands of tiles, long shared routes | HPA* / hierarchical |
|
|
135
|
+
| Many agents → one goal | Flow field (steering reference) |
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Steering, local avoidance & flow fields
|
|
2
|
+
|
|
3
|
+
Depth for the "Steering & local avoidance" section of SKILL.md. Steering is the **local** layer: given a
|
|
4
|
+
global path (from A*/navmesh), produce a per-frame velocity that follows it while dodging neighbours. All
|
|
5
|
+
formulas below assume a max speed and a max steering force (acceleration clamp).
|
|
6
|
+
|
|
7
|
+
## The steering model (Reynolds)
|
|
8
|
+
|
|
9
|
+
Each behaviour returns a **steering force** = `desired_velocity − current_velocity`, clamped to
|
|
10
|
+
`max_force`. Apply it as acceleration, integrate velocity (clamped to `max_speed`), integrate position.
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
steering = clamp(desired - velocity, max_force)
|
|
14
|
+
velocity = clamp(velocity + steering, max_speed)
|
|
15
|
+
position = position + velocity * dt
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Primitives (desired velocity per behaviour)
|
|
19
|
+
|
|
20
|
+
- **Seek**: `desired = normalize(target - pos) * max_speed`.
|
|
21
|
+
- **Flee**: seek negated — `desired = normalize(pos - target) * max_speed`.
|
|
22
|
+
- **Arrive**: seek that ramps down inside a slowing radius, so the agent settles instead of orbiting:
|
|
23
|
+
```text
|
|
24
|
+
to_target = target - pos; dist = length(to_target)
|
|
25
|
+
speed = (dist < slow_radius) ? max_speed * dist/slow_radius : max_speed
|
|
26
|
+
desired = to_target/dist * speed
|
|
27
|
+
```
|
|
28
|
+
- **Pursue**: seek the target's **predicted** position: `predict = target_pos + target_vel * T`, where
|
|
29
|
+
`T` scales with distance (`T = dist / max_speed`). **Evade** = flee that prediction.
|
|
30
|
+
- **Wander**: keep a point on a small circle projected ahead of the agent, jitter it slightly each frame,
|
|
31
|
+
seek it — gives smooth, non-robotic idle motion (unlike raw random which twitches).
|
|
32
|
+
- **Obstacle avoidance**: cast a probe (feeler) ahead; if it hits a static obstacle, add a lateral force
|
|
33
|
+
away from the obstacle's center. This is a *steering* dodge, distinct from navmesh carving.
|
|
34
|
+
|
|
35
|
+
## Combining behaviours
|
|
36
|
+
|
|
37
|
+
- **Weighted sum**: `total = Σ wᵢ · forceᵢ`, then clamp. Simple; can average into indecision (two forces
|
|
38
|
+
cancel and the agent freezes between them).
|
|
39
|
+
- **Priority / arbitration**: evaluate in order (avoidance → separation → path-follow → cohesion) and
|
|
40
|
+
take the first non-trivial force, or accumulate with a running-force budget until clamp is hit. More
|
|
41
|
+
robust for "avoidance must win".
|
|
42
|
+
|
|
43
|
+
## Path following
|
|
44
|
+
|
|
45
|
+
Do **not** seek the far goal — seek a moving point along the corridor:
|
|
46
|
+
|
|
47
|
+
1. Find the point on the path polyline nearest the agent.
|
|
48
|
+
2. Advance a **look-ahead** distance along the path from there → target point.
|
|
49
|
+
3. **Arrive** at that target; when close to the final waypoint, arrive at the true goal.
|
|
50
|
+
|
|
51
|
+
Look-ahead too short = the agent hugs corners and jitters; too long = it cuts corners and leaves the
|
|
52
|
+
corridor. Tune to roughly one agent radius + a fraction of current speed. Engine agents
|
|
53
|
+
(`NavigationAgent.get_next_path_position`, `NavMeshAgent`) do this internally — you feed the corridor,
|
|
54
|
+
they hand back the next point.
|
|
55
|
+
|
|
56
|
+
## Flocking (boids)
|
|
57
|
+
|
|
58
|
+
Three neighbour-based forces within a perception radius; sum with weights:
|
|
59
|
+
|
|
60
|
+
- **Separation** — steer away from the average offset to close neighbours (usually the strongest weight;
|
|
61
|
+
prevents overlap).
|
|
62
|
+
- **Alignment** — steer toward the average heading of neighbours (group moves as one).
|
|
63
|
+
- **Cohesion** — steer toward the average position of neighbours (group stays together).
|
|
64
|
+
|
|
65
|
+
Typical starting weights: separation 1.5, alignment 1.0, cohesion 1.0 — then tune. Combine with
|
|
66
|
+
path-following (the flock follows a global path while holding formation).
|
|
67
|
+
|
|
68
|
+
## Local avoidance: RVO / ORCA
|
|
69
|
+
|
|
70
|
+
Reynolds separation is reactive and oscillates in crowds. **Velocity-obstacle** methods are the modern
|
|
71
|
+
answer:
|
|
72
|
+
|
|
73
|
+
- A **velocity obstacle (VO)** for a neighbour is the set of your velocities that would collide with it
|
|
74
|
+
within a time horizon. **RVO** (Reciprocal VO) assumes *both* agents share the avoidance effort, so
|
|
75
|
+
each takes half the correction — this kills the "two agents dance side-to-side" oscillation.
|
|
76
|
+
- **ORCA** (Optimal Reciprocal Collision Avoidance) formulates each neighbour as a half-plane constraint
|
|
77
|
+
on your velocity and picks, via linear programming, the collision-free velocity nearest your desired
|
|
78
|
+
one. It scales to dense crowds and is what most engine crowd systems implement.
|
|
79
|
+
- Key idea: avoidance adjusts the **intended velocity before you move**. It is *not* collision response
|
|
80
|
+
— the physics solver (see `gamedev-physics`) is the last-resort backstop when agents still overlap
|
|
81
|
+
under pressure. Overlap under crush is expected, not a bug.
|
|
82
|
+
|
|
83
|
+
**Per-engine:** Godot `NavigationAgent` avoidance (`avoidance_enabled` + `set_velocity` →
|
|
84
|
+
`velocity_computed` signal, RVO2-based). Unity `NavMeshAgent.obstacleAvoidanceType` (quality levels,
|
|
85
|
+
RVO). Unreal **Detour Crowd** manager / `DetourCrowdAIController` (RVO/ORCA-style for many pawns);
|
|
86
|
+
`RVOAvoidance` on `CharacterMovementComponent` for a lighter option.
|
|
87
|
+
|
|
88
|
+
## Flow fields — many agents, one goal
|
|
89
|
+
|
|
90
|
+
When hundreds/thousands of agents share a goal (RTS swarm, tower-defense creeps), one A* *per agent* is
|
|
91
|
+
wasteful. Build the field once, then every agent samples O(1):
|
|
92
|
+
|
|
93
|
+
1. **Cost field**: per-cell traversal cost (walls = impassable, terrain = weighted).
|
|
94
|
+
2. **Integration field**: run **Dijkstra/BFS from the goal** outward, storing each cell's cheapest total
|
|
95
|
+
cost to reach the goal. (This is why Dijkstra, not A* — you want the whole field, not one path.)
|
|
96
|
+
3. **Flow field**: for each cell, store a direction vector pointing to the lowest-cost neighbour.
|
|
97
|
+
4. **Per agent, per frame**: sample the flow vector at the agent's cell, use it as the desired direction,
|
|
98
|
+
then layer local avoidance (separation/RVO) so they don't stack.
|
|
99
|
+
|
|
100
|
+
Rebuild only when the goal or the cost field changes. Multiple goals = seed the Dijkstra from all goal
|
|
101
|
+
cells at once (agents flow to the nearest). Flow fields also smooth naturally (bilinear-sample the
|
|
102
|
+
vectors across cells).
|
|
103
|
+
|
|
104
|
+
When to choose it: **many agents → few goals**. For a handful of agents to distinct goals, per-agent A*
|
|
105
|
+
or navmesh is simpler and gives better individual paths.
|
|
106
|
+
|
|
107
|
+
## Combining the two layers (the whole loop)
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
on new goal: path = search(start, goal) # global, occasional
|
|
111
|
+
every frame per agent:
|
|
112
|
+
look = lookahead_point(path, pos) # follow the corridor
|
|
113
|
+
desired = arrive(look) # path following
|
|
114
|
+
desired += separation + rvo(neighbours) # local avoidance
|
|
115
|
+
velocity = integrate(clamp(desired)) # steer, don't teleport
|
|
116
|
+
move(velocity); resolve_collisions() # physics backstop
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Global path answers *where*; steering + avoidance answer *how to move now*; physics is the final contact
|
|
120
|
+
resolver. Keep them distinct and each stays cheap and debuggable.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gamedev-physics
|
|
3
|
+
description: "Use when working with a game engine's physics — rigid bodies, colliders, collision layers/masks, character controllers, joints, forces vs impulses, raycasts/shapecasts, or when physics is unstable. Triggers: \"my character falls through the floor\", \"bullet tunnels through walls at high speed\", \"rigidbody jitters / vibrates / explodes\", \"objects that should collide pass through each other\", \"which collision layer and mask do I set\", \"kinematic vs dynamic body\", \"move_and_slide / CharacterBody\", \"AddForce vs impulse vs setting velocity\", \"physics in FixedUpdate or _physics_process\", \"slope and step detection\", \"trigger / Area overlap not firing\", Godot RigidBody/StaticBody/CharacterBody/Area, Unity Rigidbody/Collider/CharacterController, Unreal Chaos/collision presets. NOT rendering, materials, or visual jitter from shaders (use gamedev-shaders); NOT AI navigation, pathfinding, or navmesh (use gamedev-pathing)."
|
|
4
|
+
tags: [physics, collision, rigidbody, character-controller, colliders]
|
|
5
|
+
recommends: [godot, unity, unreal, gamedev-pathing]
|
|
6
|
+
profiles: [full]
|
|
7
|
+
origin: risco
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Game physics & collision
|
|
11
|
+
|
|
12
|
+
Make bodies collide, move, and stay stable across Godot, Unity, and Unreal. This skill owns the
|
|
13
|
+
mental model (body types, colliders, layers/masks, character controllers, joints, queries,
|
|
14
|
+
determinism) and maps every concept onto each engine's real API. When physics "feels broken" —
|
|
15
|
+
jitter, tunneling, sinking, ghost collisions — the cause is almost always one of: wrong body type,
|
|
16
|
+
a layer/mask mismatch, or moving/integrating outside the fixed physics tick. Check those first.
|
|
17
|
+
|
|
18
|
+
## Version contract — read first (never emit these)
|
|
19
|
+
|
|
20
|
+
| Engine | Target | Banned / deprecated | Use instead |
|
|
21
|
+
| --- | --- | --- | --- |
|
|
22
|
+
| **Godot** | 4.x | `move_and_slide(velocity, up, ...)` with arguments (that is Godot 3) | Godot 4 `move_and_slide()` takes **no args**; set the `velocity` property first |
|
|
23
|
+
| Godot | 4.x | `KinematicBody2D/3D`, `RayShape`, `WorldMarginShape`, `linear_velocity *= delta` before `move_and_slide()` | `CharacterBody2D/3D`, `SeparationRayShape2D/3D`, `WorldBoundaryShape2D/3D`; `move_and_slide()` is already delta-scaled |
|
|
24
|
+
| **Unity** | Unity 6 (6000.x) | `Rigidbody.velocity` / `Rigidbody2D.velocity`, `rb.drag`, `rb.angularDrag` | `linearVelocity` (+ `linearVelocityX/Y`), `linearDamping`, `angularDamping`; new `AddForceX/Y` |
|
|
25
|
+
| Unity | Unity 6 | moving a dynamic `Rigidbody` from `Update`; `transform.position =` on a physics body | move in `FixedUpdate`; use `MovePosition`/`AddForce`, never write `transform` on a simulated body |
|
|
26
|
+
| **Unreal** | UE5 (Chaos) | any PhysX/APEX API, `bUseAsyncScene`, `NvCloth` | Chaos is the only physics backend in UE5; use `UPrimitiveComponent` physics + `PhysicsConstraintComponent` |
|
|
27
|
+
|
|
28
|
+
If you are unsure a symbol exists in the user's exact version, say so instead of inventing it.
|
|
29
|
+
|
|
30
|
+
## When to use / When NOT to use
|
|
31
|
+
|
|
32
|
+
**Use when:** designing which body type a thing should be; something falls through, sinks into, or
|
|
33
|
+
passes through geometry; a rigid body jitters, drifts, or launches; setting up collision
|
|
34
|
+
layers/masks/channels; building or fixing a character/player controller; adding joints; casting
|
|
35
|
+
rays/shapes or overlap tests; or physics behaves differently at different frame rates.
|
|
36
|
+
|
|
37
|
+
**When NOT to use (delegate):**
|
|
38
|
+
- Visual-only wobble, shader/vertex animation, material or lighting issues → **gamedev-shaders**.
|
|
39
|
+
- AI moving along a path, navmesh, A*, steering, avoidance → **gamedev-pathing** (physics only
|
|
40
|
+
*executes* the move; pathing decides *where*).
|
|
41
|
+
- Deep, engine-specific project setup / editor / build questions → **godot**, **unity**, **unreal**.
|
|
42
|
+
- Netcode, lockstep sync, server-authoritative movement → **gamedev-multiplayer** (this skill gives
|
|
43
|
+
you the *deterministic fixed-tick* foundation multiplayer builds on).
|
|
44
|
+
|
|
45
|
+
## 1. Body types — pick before you touch anything
|
|
46
|
+
|
|
47
|
+
Three categories exist in every engine; choosing wrong is the root of most bugs.
|
|
48
|
+
|
|
49
|
+
- **Static** — never moves, infinite mass, cheap. Level geometry, walls, floors. Do **not** move a
|
|
50
|
+
static body every frame; the broadphase caches it. If it must move, it is not static.
|
|
51
|
+
- **Kinematic / character** — moved *by you* (code/animation), pushes dynamics but is not pushed by
|
|
52
|
+
them, ignores forces and gravity unless you add them. Players, moving platforms, doors, elevators.
|
|
53
|
+
- **Dynamic (rigid)** — fully simulated: gravity, forces, impulses, collision response. Crates,
|
|
54
|
+
ragdolls, debris, vehicles. You influence it with forces/impulses, never by writing its transform.
|
|
55
|
+
|
|
56
|
+
**Move each the right way:**
|
|
57
|
+
|
|
58
|
+
| | Static | Kinematic / character | Dynamic |
|
|
59
|
+
| --- | --- | --- | --- |
|
|
60
|
+
| Godot | `StaticBody2D/3D` (leave still) | `CharacterBody2D/3D` + `move_and_slide()`; `AnimatableBody2D/3D` for platforms (set `sync_to_physics`) | `RigidBody2D/3D` (`apply_impulse`, `apply_force`) |
|
|
61
|
+
| Unity | Collider, no Rigidbody (or `Rigidbody2D` bodyType Static) | `CharacterController.Move()`, or `Rigidbody.isKinematic=true` + `MovePosition` in `FixedUpdate` | `Rigidbody` (`AddForce`) |
|
|
62
|
+
| Unreal | `Static`/`Movable` mobility, Simulate Physics off | `Character` + `CharacterMovementComponent`; movable component moved by code | Simulate Physics on (`SetSimulatePhysics(true)`) |
|
|
63
|
+
|
|
64
|
+
A Godot `RigidBody` can be temporarily frozen: set `freeze = true` with `freeze_mode =
|
|
65
|
+
FREEZE_MODE_KINEMATIC` to move it by transform without waking the solver wrongly. Prefer
|
|
66
|
+
`AnimatableBody` for a permanent kinematic mover.
|
|
67
|
+
|
|
68
|
+
## 2. Colliders / shapes
|
|
69
|
+
|
|
70
|
+
Collider ≠ visual mesh. Give every body a **separate, simpler** collision shape.
|
|
71
|
+
|
|
72
|
+
- **Primitives** (box, sphere/circle, capsule) — cheapest and most stable. Prefer a **capsule** for
|
|
73
|
+
characters (rounded ends slide over steps and seams). Use these whenever possible.
|
|
74
|
+
- **Convex hull** — dynamic bodies can use a convex approximation; a concave prop needs **multiple
|
|
75
|
+
convex pieces** (a compound), not one concave hull.
|
|
76
|
+
- **Concave / trimesh** — exact triangle mesh, but **static-only** in every engine. A moving
|
|
77
|
+
concave-mesh collider is a top cause of tunneling and solver blowups. Never put a trimesh on a
|
|
78
|
+
dynamic body.
|
|
79
|
+
- **Compound** — several child shapes on one body. Godot: multiple `CollisionShape` children. Unity:
|
|
80
|
+
multiple `Collider` components. Unreal: multiple primitives / a body setup.
|
|
81
|
+
- **Triggers / areas** — detect overlap, **no physical response**. Godot `Area2D/3D` (signals
|
|
82
|
+
`body_entered` / `area_entered`); Unity collider `Is Trigger` (`OnTriggerEnter/Stay/Exit`); Unreal
|
|
83
|
+
set response to **Overlap** + `Generate Overlap Events` (`OnComponentBeginOverlap`). Use for
|
|
84
|
+
pickups, damage zones, checkpoints, sensors.
|
|
85
|
+
|
|
86
|
+
## 3. Collision layers & masks — the #1 confusion
|
|
87
|
+
|
|
88
|
+
**Layer = "what I am." Mask = "what I scan for."** They are separate bit sets.
|
|
89
|
+
|
|
90
|
+
- **Godot:** two bodies interact when **one's `collision_mask` includes the other's
|
|
91
|
+
`collision_layer`** — it is an **OR**, so detection can be asymmetric (A sees B without B seeing
|
|
92
|
+
A). Keep them symmetric unless you deliberately want one-way detection. An `Area`'s `mask` decides
|
|
93
|
+
what it detects; its `layer` decides what detects *it*.
|
|
94
|
+
- **Unity:** GameObject **Layers** + the **Layer Collision Matrix** (Project Settings → Physics)
|
|
95
|
+
decide which layer pairs collide. Raycasts/overlaps take a `LayerMask` argument. Toggle a pair at
|
|
96
|
+
runtime with `Physics.IgnoreLayerCollision`.
|
|
97
|
+
- **Unreal:** **Object Type** (channel) = what I am; per-channel **Response** = Block / Overlap /
|
|
98
|
+
Ignore. Package as a reusable **Collision Preset**. Trace channels (Visibility, Camera) are for
|
|
99
|
+
queries; object channels are for physical collision.
|
|
100
|
+
|
|
101
|
+
Full worked examples (player/enemy/pickup/wall, bit math, one-way platforms) →
|
|
102
|
+
`references/layers-and-masks.md`.
|
|
103
|
+
|
|
104
|
+
## 4. Character controllers
|
|
105
|
+
|
|
106
|
+
Two philosophies — decide up front, don't mix:
|
|
107
|
+
|
|
108
|
+
- **Kinematic character** (recommended for most players): you compute velocity and move via a
|
|
109
|
+
sweeping helper that resolves collisions and slides. Precise, snappy, no solver fighting. Godot
|
|
110
|
+
`CharacterBody`, Unity `CharacterController`, Unreal `CharacterMovementComponent`.
|
|
111
|
+
- **Dynamic (rigidbody) character**: physical pushing/being-pushed for free, but needs high friction
|
|
112
|
+
or a physics material, angular constraints (freeze rotation), and tuning to stop tipping/sliding.
|
|
113
|
+
|
|
114
|
+
Godot 4 (2D — the 3D version is identical with `Vector3` and `get_gravity()`):
|
|
115
|
+
|
|
116
|
+
```gdscript
|
|
117
|
+
extends CharacterBody2D
|
|
118
|
+
|
|
119
|
+
const SPEED := 300.0
|
|
120
|
+
const JUMP_VELOCITY := -400.0
|
|
121
|
+
|
|
122
|
+
func _physics_process(delta: float) -> void:
|
|
123
|
+
if not is_on_floor():
|
|
124
|
+
velocity += get_gravity() * delta # accumulate accel: scale by delta
|
|
125
|
+
if Input.is_action_just_pressed("jump") and is_on_floor():
|
|
126
|
+
velocity.y = JUMP_VELOCITY
|
|
127
|
+
var dir := Input.get_axis("move_left", "move_right")
|
|
128
|
+
velocity.x = dir * SPEED if dir else move_toward(velocity.x, 0.0, SPEED)
|
|
129
|
+
move_and_slide() # Godot 4: NO args, uses `velocity`, already delta-scaled
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`is_on_floor()` / `is_on_wall()`, `floor_max_angle` (slope limit), `floor_snap_length` (stick to
|
|
133
|
+
ground on ramps/stairs), and `up_direction` handle ground/slope/step behavior. `get_gravity()` is
|
|
134
|
+
Godot 4.3+; on older 4.x read `ProjectSettings` gravity. Full per-engine controllers (Unity
|
|
135
|
+
`CharacterController` + custom gravity, Unreal movement modes, ground detection, slopes, steps,
|
|
136
|
+
moving platforms) → `references/character-controllers.md`.
|
|
137
|
+
|
|
138
|
+
## 5. Joints/constraints & how to apply motion
|
|
139
|
+
|
|
140
|
+
**Joints** connect two bodies with a constraint. Godot: `PinJoint`, `HingeJoint3D`, `SliderJoint3D`,
|
|
141
|
+
`Generic6DOFJoint3D`, `DampedSpringJoint2D`. Unity: `HingeJoint`, `FixedJoint`, `SpringJoint`,
|
|
142
|
+
`ConfigurableJoint`, `CharacterJoint`. Unreal: `PhysicsConstraintComponent` (one 6-DOF constraint
|
|
143
|
+
covers hinge/slider/ball). Keep connected bodies' **mass ratios close** — a heavy body chained to a
|
|
144
|
+
light one is the classic joint-explosion.
|
|
145
|
+
|
|
146
|
+
**Force vs impulse vs direct velocity** (apply all in the fixed tick):
|
|
147
|
+
|
|
148
|
+
| Want | Use | Godot | Unity | Unreal |
|
|
149
|
+
| --- | --- | --- | --- | --- |
|
|
150
|
+
| Continuous push (thrust, wind), mass-scaled | **Force** | `apply_central_force` | `AddForce(f, Force)` | `AddForce` |
|
|
151
|
+
| Instant kick (jump, explosion, hit), mass-scaled | **Impulse** | `apply_central_impulse` | `AddForce(f, Impulse)` | `AddImpulse` |
|
|
152
|
+
| Instant velocity change, **ignoring mass** | mass-independent impulse | set `linear_velocity` | `AddForce(f, VelocityChange)` | `SetPhysicsLinearVelocity` |
|
|
153
|
+
|
|
154
|
+
Setting velocity directly on a *dynamic* body teleports its momentum and can fight the solver — fine
|
|
155
|
+
for character/kinematic bodies, use sparingly on dynamics (prefer forces/impulses).
|
|
156
|
+
|
|
157
|
+
## 6. Queries — raycasts, shapecasts, overlaps
|
|
158
|
+
|
|
159
|
+
Run queries from the **fixed physics tick** so results match the simulated state.
|
|
160
|
+
|
|
161
|
+
- **Godot:** get `get_world_2d().direct_space_state` (or 3d), build
|
|
162
|
+
`PhysicsRayQueryParameters2D/3D` / `PhysicsShapeQueryParameters2D/3D`, call `intersect_ray`,
|
|
163
|
+
`intersect_shape`, `cast_motion`. Node helpers: `RayCast2D/3D`, `ShapeCast2D/3D`. Queries respect
|
|
164
|
+
`collision_mask`.
|
|
165
|
+
- **Unity:** `Physics.Raycast`, `SphereCast`/`CapsuleCast`, `OverlapSphere`, `CheckSphere` (2D:
|
|
166
|
+
`Physics2D.*`). Always pass a `LayerMask`; prefer non-allocating `RaycastNonAlloc`/`RaycastAll`.
|
|
167
|
+
- **Unreal:** `LineTraceSingleByChannel`, `SweepSingleByChannel`, `OverlapMultiByChannel` (and
|
|
168
|
+
`...ByObjectType`). Use a **trace channel**, set the query params (`bTraceComplex`).
|
|
169
|
+
|
|
170
|
+
Details, snippets, and the tunneling-safe shapecast pattern → `references/determinism-and-queries.md`.
|
|
171
|
+
|
|
172
|
+
## 7. Determinism & stability
|
|
173
|
+
|
|
174
|
+
The rules that keep physics from jittering, drifting, or tunneling:
|
|
175
|
+
|
|
176
|
+
1. **Physics lives in the fixed tick.** Godot `_physics_process(delta)`, Unity `FixedUpdate()`,
|
|
177
|
+
Unreal substepping / async physics tick. Read input in the frame update, *apply* it in the fixed
|
|
178
|
+
tick. Never simulate in the render frame.
|
|
179
|
+
2. **Scale by delta.** Multiply forces/accelerations/manual position changes by the tick's `delta` /
|
|
180
|
+
`fixedDeltaTime` — but **not** the output of a helper that already integrates time (Godot
|
|
181
|
+
`move_and_slide()`, Unity `CharacterController.Move` when you pass a per-second velocity).
|
|
182
|
+
3. **CCD for fast/thin things.** Small fast projectiles + thin walls tunnel. Enable Continuous
|
|
183
|
+
Collision Detection: Godot `continuous_cd`, Unity `collisionDetectionMode = Continuous*`, Unreal
|
|
184
|
+
`Use CCD`. Enable only on the fast bodies (it costs). Or use a shapecast/raycast-then-move.
|
|
185
|
+
4. **Keep mass ratios sane.** Stacks and joints between wildly different masses (100:1+) blow up.
|
|
186
|
+
5. **Never scale a collider at runtime**, and never write `transform`/`position` on a dynamic body —
|
|
187
|
+
both corrupt the broadphase and cause explosions or ghost collisions. Move via the API.
|
|
188
|
+
6. **Interpolate for smooth visuals** at high frame rates: Godot physics interpolation, Unity
|
|
189
|
+
`Rigidbody.interpolation = Interpolate`. This is visual only — it never changes the simulation.
|
|
190
|
+
|
|
191
|
+
Deeper: fixed-timestep math, substepping, sleeping, why `transform` writes break things →
|
|
192
|
+
`references/determinism-and-queries.md`.
|
|
193
|
+
|
|
194
|
+
## Guardrails / gotchas
|
|
195
|
+
|
|
196
|
+
- Falling through a thin floor at speed → CCD off, or moving in the render frame. Fix both.
|
|
197
|
+
- A moving platform doesn't carry the player → use a kinematic mover (Godot `AnimatableBody` with
|
|
198
|
+
`sync_to_physics`, Unity kinematic Rigidbody `MovePosition`), not a static body.
|
|
199
|
+
- "Ghost" collisions / catching on seams between tiles → merge colliders or use a capsule character.
|
|
200
|
+
- Trigger never fires → in Unity a trigger pair needs at least one **Rigidbody**; in Godot the
|
|
201
|
+
Area's `mask` must include the body's `layer`; in Unreal enable Generate Overlap Events on **both**.
|
|
202
|
+
- Rigidbody vibrates against the ground → mass ratio, too-soft solver iterations, or you're also
|
|
203
|
+
writing its transform. Stop writing the transform.
|
|
204
|
+
- Character launches off ramps → enable floor snapping (`floor_snap_length` / stop-on-slope).
|
|
205
|
+
|
|
206
|
+
## Related skills
|
|
207
|
+
|
|
208
|
+
- **godot** / **unity** / **unreal** — engine setup, editor, language, and build specifics.
|
|
209
|
+
- **gamedev-pathing** — decides *where* to move; this skill *executes* the move and collisions.
|
|
210
|
+
- **gamedev-multiplayer** — builds on the deterministic fixed-tick foundation here.
|
|
211
|
+
- **gamedev-shaders** — for visual/material issues that only look like physics.
|
|
212
|
+
|
|
213
|
+
## Checklist
|
|
214
|
+
|
|
215
|
+
- [ ] Every moving thing has the right **body type** (static / kinematic / dynamic).
|
|
216
|
+
- [ ] Colliders are **primitives/capsules**; no trimesh on a dynamic body; compound = many convex.
|
|
217
|
+
- [ ] Layers/masks (or channels/presets) set so exactly the intended pairs collide; triggers use
|
|
218
|
+
overlap/Area, not solid response.
|
|
219
|
+
- [ ] All physics motion and queries run in the **fixed tick**; deltas scaled correctly (helper
|
|
220
|
+
output not double-scaled).
|
|
221
|
+
- [ ] Forces/impulses/velocity chosen deliberately; no `transform`/`position` writes on dynamics; no
|
|
222
|
+
runtime collider scaling.
|
|
223
|
+
- [ ] Fast/thin bodies have **CCD**; character has floor/slope/step handling; mass ratios are sane.
|
|
224
|
+
- [ ] Correct API for the stated engine **version** (Godot 4 `move_and_slide()` no args; Unity 6
|
|
225
|
+
`linearVelocity`; UE5 Chaos — no PhysX).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Eval harness — `gamedev-physics` skill
|
|
2
|
+
|
|
3
|
+
These cases are read by the rsc skill-eval harness (a Claude agent with the full catalog loaded), or
|
|
4
|
+
by hand. They check two things. **Triggering:** the description fires on real physics symptoms —
|
|
5
|
+
tunneling, jitter, layer/mask filtering, forces-vs-impulses, character controllers — and stays quiet
|
|
6
|
+
on near-misses, routing shader shimmer to `gamedev-shaders`, navmesh/pathfinding to
|
|
7
|
+
`gamedev-pathing`, editor/export setup to `godot`, netcode to `gamedev-multiplayer`, and gameplay
|
|
8
|
+
design to `game-design`. **Capability:** with `SKILL.md` and its references loaded, the answer uses
|
|
9
|
+
the version-correct API (Godot 4 `move_and_slide()` with no args, Unity 6 `linearVelocity`, UE5
|
|
10
|
+
Chaos), puts simulation in the fixed tick, scales delta correctly, and picks the right body type /
|
|
11
|
+
collider / layer-mask / CCD fix. Routing is non-deterministic — run 3–5 trials per prompt and pass
|
|
12
|
+
on a majority; capability is judged by reading the output against the `must_include` bullets.
|