@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,162 @@
|
|
|
1
|
+
# Stores & CI — Steam, itch.io, Play/App Store, and headless build pipelines
|
|
2
|
+
|
|
3
|
+
Back to the entrypoint: `../SKILL.md`. Store submission mechanics and per-engine CI.
|
|
4
|
+
|
|
5
|
+
## Steam (Steamworks + SteamPipe)
|
|
6
|
+
|
|
7
|
+
1. **Partner setup**: Steamworks account, an **App ID**, and the store page (assets, description,
|
|
8
|
+
trailers, tags, pricing) filled in the partner site.
|
|
9
|
+
2. **Depots**: a depot is a bucket of files for a platform/language (e.g. one Windows depot, one
|
|
10
|
+
macOS depot). Map each to your App ID.
|
|
11
|
+
3. **SteamPipe upload** with `steamcmd` driven by VDF scripts.
|
|
12
|
+
|
|
13
|
+
`app_build_APPID.vdf`:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
"appbuild"
|
|
17
|
+
{
|
|
18
|
+
"appid" "480"
|
|
19
|
+
"desc" "1.2.3 - CI build"
|
|
20
|
+
"buildoutput" "output/"
|
|
21
|
+
"contentroot" "build/"
|
|
22
|
+
"setlive" "" // leave empty; set a branch live in the site, or "beta"
|
|
23
|
+
"depots" { "481" "depot_build_481.vdf" }
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`depot_build_481.vdf`:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
"DepotBuild"
|
|
31
|
+
{
|
|
32
|
+
"DepotID" "481"
|
|
33
|
+
"FileMapping" { "LocalPath" "*" "DepotPath" "." "recursive" "1" }
|
|
34
|
+
"FileExclusion" "*.pdb"
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Upload:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
steamcmd +login <builder_account> +run_app_build $(pwd)/app_build_APPID.vdf +quit
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- Push to a **beta branch** first, test, then set it as the default (live) branch in the partner site.
|
|
45
|
+
- Use a dedicated **build account** with 2FA handled for CI (Steam Guard); never the studio owner login.
|
|
46
|
+
- `.pdb`/debug symbols usually excluded from the shipped depot.
|
|
47
|
+
|
|
48
|
+
## itch.io (butler)
|
|
49
|
+
|
|
50
|
+
`butler` is itch's CLI. A **channel** encodes the platform:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
butler push build/windows user/game:windows --userversion 1.2.3
|
|
54
|
+
butler push build/html5 user/game:html5 --userversion 1.2.3
|
|
55
|
+
butler push build/osx.zip user/game:mac --userversion 1.2.3
|
|
56
|
+
butler status user/game # see channels + versions
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- Channel names containing `windows`/`osx`/`linux`/`android` tag the platform for the itch app;
|
|
60
|
+
`html5`/`web` marks a browser build.
|
|
61
|
+
- For threaded web builds, in the uploaded file's settings tick **"This file will be played in
|
|
62
|
+
the browser" ▸ SharedArrayBuffer support** so itch serves COOP/COEP (see `web-export-headers.md`).
|
|
63
|
+
- CI auth: `BUTLER_API_KEY` env var (from itch API keys). `butler push` is idempotent and diff-based.
|
|
64
|
+
|
|
65
|
+
## Google Play
|
|
66
|
+
|
|
67
|
+
- Upload an **AAB**; enable **Play App Signing**.
|
|
68
|
+
- Meet the current **target API level** requirement (Play enforces a recent level for new uploads and updates).
|
|
69
|
+
- Complete: **content rating** (IARC questionnaire), **Data safety** form, store listing (icon,
|
|
70
|
+
feature graphic, screenshots per device, short/full description), pricing/countries.
|
|
71
|
+
- Tracks: internal → closed → open → production. Promote a build up the tracks.
|
|
72
|
+
- `versionCode` strictly increasing; can't reuse one.
|
|
73
|
+
|
|
74
|
+
## Apple App Store
|
|
75
|
+
|
|
76
|
+
- Archive + upload to **App Store Connect** (Xcode Organizer / Transporter).
|
|
77
|
+
- App Store record: name, subtitle, keywords, description, screenshots per device size, app icon,
|
|
78
|
+
privacy nutrition labels, **age rating** questionnaire, pricing.
|
|
79
|
+
- **TestFlight** for beta before submitting for review.
|
|
80
|
+
- The store notarizes automatically — no manual `notarytool` for App Store builds.
|
|
81
|
+
|
|
82
|
+
## Age ratings (shared)
|
|
83
|
+
|
|
84
|
+
- **IARC** questionnaire generates ratings for Google Play, Microsoft Store, Nintendo eShop in one pass.
|
|
85
|
+
- **ESRB** (NA), **PEGI** (EU), **USK** (DE), **CERO** (JP) as required by platform/region.
|
|
86
|
+
- Apple has its own age-rating questionnaire in App Store Connect.
|
|
87
|
+
- Answer truthfully — misdeclaring content (violence, gambling, user-generated content) risks removal.
|
|
88
|
+
|
|
89
|
+
## CI/CD — headless builds
|
|
90
|
+
|
|
91
|
+
Principles: run the export headless, feed a **version/build number** from the CI run, restore
|
|
92
|
+
**signing material from secrets** at runtime, upload the artifact.
|
|
93
|
+
|
|
94
|
+
### Godot (GitHub Actions)
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
name: export
|
|
98
|
+
on: { push: { tags: ['v*'] } }
|
|
99
|
+
jobs:
|
|
100
|
+
build:
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
container: barichello/godot-ci:4.5.1 # image bundles matching export templates
|
|
103
|
+
steps:
|
|
104
|
+
- uses: actions/checkout@v4
|
|
105
|
+
- name: Prepare templates
|
|
106
|
+
run: |
|
|
107
|
+
mkdir -p ~/.local/share/godot/export_templates/4.5.1.stable
|
|
108
|
+
mv /root/.local/share/godot/export_templates/4.5.1.stable/* \
|
|
109
|
+
~/.local/share/godot/export_templates/4.5.1.stable/
|
|
110
|
+
- name: Export Web + Linux
|
|
111
|
+
run: |
|
|
112
|
+
mkdir -p build/web build/linux
|
|
113
|
+
godot --headless --export-release "Web" build/web/index.html
|
|
114
|
+
godot --headless --export-release "Linux/X11" build/linux/game.x86_64
|
|
115
|
+
- uses: actions/upload-artifact@v4
|
|
116
|
+
with: { name: builds, path: build/ }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The template version in the image **must** equal the editor build (`4.5.1` here). For Android in CI,
|
|
120
|
+
add JDK 17 + the Android SDK and restore the keystore from a secret.
|
|
121
|
+
|
|
122
|
+
### Unity (GitHub Actions)
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
- uses: game-ci/unity-builder@v4
|
|
126
|
+
with:
|
|
127
|
+
targetPlatform: Android
|
|
128
|
+
buildMethod: BuildScript.PerformBuild
|
|
129
|
+
env:
|
|
130
|
+
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
|
131
|
+
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
|
132
|
+
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- License activation is required (personal/pro); store it in secrets via `game-ci/activate`.
|
|
136
|
+
- Matrix over `targetPlatform` for multi-target. Keystore + passwords come from secrets for signed Android.
|
|
137
|
+
|
|
138
|
+
### Unreal (self-hosted)
|
|
139
|
+
|
|
140
|
+
- The engine install is multi-GB, so Unreal CI needs a **self-hosted runner** with the engine + SDKs installed.
|
|
141
|
+
- Drive with `RunUAT BuildCookRun ... -clientconfig=Shipping -cook -stage -pak -package -archivedirectory=...`.
|
|
142
|
+
|
|
143
|
+
### Versioning
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
env:
|
|
147
|
+
BUILD_NUMBER: ${{ github.run_number }} # monotonic → versionCode / CFBundleVersion / Steam desc
|
|
148
|
+
VERSION: ${{ github.ref_name }} # semver tag → versionName / marketing version
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- Semver tag (`v1.2.3`) triggers a release build; the run number is the strictly-increasing integer.
|
|
152
|
+
- Feed `BUILD_NUMBER` into Android `versionCode`, iOS `CFBundleVersion`, and the Steam build `desc`.
|
|
153
|
+
- Publish signing secrets (keystore base64, App Store Connect API key, `BUTLER_API_KEY`, `STEAM_*`) as encrypted repo/environment secrets — never in the workflow file.
|
|
154
|
+
|
|
155
|
+
## Checklist
|
|
156
|
+
|
|
157
|
+
- [ ] Store page assets + metadata + age rating complete for the target store.
|
|
158
|
+
- [ ] Steam: depots mapped, SteamPipe VDFs correct, pushed to a beta branch first, build account with Steam Guard.
|
|
159
|
+
- [ ] itch: correct channel names, SAB checkbox for web, `BUTLER_API_KEY` in CI.
|
|
160
|
+
- [ ] Play: AAB + Play App Signing + target API level + Data safety.
|
|
161
|
+
- [ ] App Store: archive uploaded, TestFlight tested, privacy labels + age rating done.
|
|
162
|
+
- [ ] CI: headless export, version/build number from the run, signing material from secrets, artifact uploaded.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Web export — cross-origin isolation, headers, and hosts
|
|
2
|
+
|
|
3
|
+
Back to the entrypoint: `../SKILL.md`. This is the single deepest source of "my web build
|
|
4
|
+
works locally but breaks when hosted" bugs. Read it before you host a threaded WebAssembly game.
|
|
5
|
+
|
|
6
|
+
## Why the headers exist
|
|
7
|
+
|
|
8
|
+
Modern engines run the game loop on Web Workers and share memory between them with
|
|
9
|
+
**`SharedArrayBuffer`**. After Spectre/Meltdown, browsers only expose `SharedArrayBuffer` (and
|
|
10
|
+
high-resolution timers) to pages that are **cross-origin isolated**. A page becomes isolated only
|
|
11
|
+
when the server sends **both**:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
15
|
+
Cross-Origin-Embedder-Policy: require-corp
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- **COOP: same-origin** severs the page from cross-origin windows/openers (its own browsing-context group).
|
|
19
|
+
- **COEP: require-corp** requires every subresource to explicitly opt in to being embedded (via CORP or CORS), so no untrusted cross-origin resource shares the isolated process.
|
|
20
|
+
|
|
21
|
+
With both present, `self.crossOriginIsolated === true` and `SharedArrayBuffer` is defined. Miss
|
|
22
|
+
either and you get one of: `SharedArrayBuffer is not defined`, a JS exception during engine init,
|
|
23
|
+
an infinite loading bar, or a blank canvas. The **files are correct** — the *response headers* are the bug.
|
|
24
|
+
|
|
25
|
+
Quick self-test in the browser console on the hosted page:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
console.log(self.crossOriginIsolated); // must be true for threaded builds
|
|
29
|
+
console.log(typeof SharedArrayBuffer); // must be "function"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Do you even need the headers?
|
|
33
|
+
|
|
34
|
+
| Situation | Headers needed? |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Godot 4 web export with **Thread Support ON** | Yes — both. |
|
|
37
|
+
| Godot 4 web export with Thread Support OFF (single-thread) | No — but audio/perf are worse. |
|
|
38
|
+
| Unity WebGL with **multithreading** / threads enabled | Yes — both. |
|
|
39
|
+
| Unity WebGL single-threaded (default older setups) | No. |
|
|
40
|
+
| A build that only *uses* WASM but not threads/SAB | No. |
|
|
41
|
+
|
|
42
|
+
If you can host somewhere that sets headers, **keep threads on** — it's a real perf win. Only
|
|
43
|
+
disable threads when your host genuinely can't set headers and the shim below isn't viable.
|
|
44
|
+
|
|
45
|
+
## Setting the headers per host
|
|
46
|
+
|
|
47
|
+
**Netlify** — `public/_headers` (or the publish dir):
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
/*
|
|
51
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
52
|
+
Cross-Origin-Embedder-Policy: require-corp
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Vercel** — `vercel.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"headers": [{
|
|
60
|
+
"source": "/(.*)",
|
|
61
|
+
"headers": [
|
|
62
|
+
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
|
|
63
|
+
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
|
|
64
|
+
]
|
|
65
|
+
}]
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**nginx**:
|
|
70
|
+
|
|
71
|
+
```nginx
|
|
72
|
+
location / {
|
|
73
|
+
add_header Cross-Origin-Opener-Policy same-origin always;
|
|
74
|
+
add_header Cross-Origin-Embedder-Policy require-corp always;
|
|
75
|
+
# serve pre-compressed wasm/pck if present
|
|
76
|
+
gzip_static on;
|
|
77
|
+
}
|
|
78
|
+
# correct MIME + long cache for the payload
|
|
79
|
+
types { application/wasm wasm; }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Apache** — `.htaccess`:
|
|
83
|
+
|
|
84
|
+
```apache
|
|
85
|
+
Header set Cross-Origin-Opener-Policy "same-origin"
|
|
86
|
+
Header set Cross-Origin-Embedder-Policy "require-corp"
|
|
87
|
+
AddType application/wasm .wasm
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Cloudflare Pages** — a `_headers` file like Netlify's, or a Transform Rule adding both response headers.
|
|
91
|
+
|
|
92
|
+
**itch.io** — no file needed: in the upload settings tick **"This file will be played in the
|
|
93
|
+
browser"**, then check **"SharedArrayBuffer support"**. itch serves your build from a domain that
|
|
94
|
+
sends COOP/COEP for you. This is the fastest correct web host for a jam/demo.
|
|
95
|
+
|
|
96
|
+
**GitHub Pages / plain static CDN with no header control** — you **cannot** set response headers.
|
|
97
|
+
Two escapes:
|
|
98
|
+
|
|
99
|
+
1. Export **without** threads (single-threaded) — always works, no headers, some perf loss.
|
|
100
|
+
2. The **`coi-serviceworker`** shim: a small service worker (`coi-serviceworker.js`) you include
|
|
101
|
+
from the page; on first load it registers, reloads once, and thereafter re-serves every
|
|
102
|
+
response with COOP/COEP injected, faking cross-origin isolation client-side. Caveats: it needs
|
|
103
|
+
HTTPS, adds a reload, and won't help resources it can't intercept. Good enough for GitHub Pages demos.
|
|
104
|
+
|
|
105
|
+
## COEP fallout — the second-order breakage
|
|
106
|
+
|
|
107
|
+
`COEP: require-corp` also applies to **every** subresource the page loads. Anything cross-origin
|
|
108
|
+
(web fonts, analytics scripts, remote images, iframes, a leaderboard API on another domain) is
|
|
109
|
+
**blocked** unless it sends `Cross-Origin-Resource-Policy: cross-origin` (or proper CORS). Symptoms:
|
|
110
|
+
fonts vanish, a third-party embed 404s in the network tab with a CORP error.
|
|
111
|
+
|
|
112
|
+
Fixes, in order of preference:
|
|
113
|
+
1. **Self-host** the asset (same origin ⇒ no CORP needed). Best for fonts and small scripts.
|
|
114
|
+
2. Ask the third party to send `Cross-Origin-Resource-Policy: cross-origin` (or use their CORS endpoint).
|
|
115
|
+
3. Switch that page to **`COEP: credentialless`** — a looser mode that loads cross-origin resources
|
|
116
|
+
without credentials and still grants isolation in supporting browsers. Narrower support than `require-corp`; test.
|
|
117
|
+
|
|
118
|
+
## Engine specifics
|
|
119
|
+
|
|
120
|
+
**Godot 4 web**
|
|
121
|
+
- `Editor ▸ Manage Export Templates` must match the editor build; then a **Web** export preset.
|
|
122
|
+
- **Thread Support** toggle drives whether you need the headers. **Extensions Support** enables GDExtension on web.
|
|
123
|
+
- Output: `index.html` + `.js` + `.wasm` + `.pck` (+ worker/audio files). Serve `.wasm` as `application/wasm` and enable gzip/brotli — the `.wasm`/`.pck` are the bulk of the download.
|
|
124
|
+
- **C#/.NET edition has no web export in Godot 4.x** (planned for a later release with .NET 10). If the project is C#, either port the web-facing build to GDScript or drop web as a target. Do not promise a C# web build.
|
|
125
|
+
- Headless CI export: `godot --headless --export-release "Web" build/index.html`.
|
|
126
|
+
|
|
127
|
+
**Unity WebGL**
|
|
128
|
+
- `File ▸ Build Profiles ▸ Web`; Player Settings ▸ Publishing: enable/disable threads, set the
|
|
129
|
+
**compression format** (Brotli/Gzip) and keep **Decompression Fallback** on if the host can't
|
|
130
|
+
send `Content-Encoding` — otherwise the browser can't inflate the payload.
|
|
131
|
+
- Threaded WebGL ⇒ same COOP/COEP requirement.
|
|
132
|
+
|
|
133
|
+
## Checklist
|
|
134
|
+
|
|
135
|
+
- [ ] Decide threads on/off; if on, both headers are mandatory.
|
|
136
|
+
- [ ] Headers set at the **host** (or itch's SAB checkbox, or the shim on GitHub Pages).
|
|
137
|
+
- [ ] `self.crossOriginIsolated === true` verified in the console on the deployed URL.
|
|
138
|
+
- [ ] `.wasm` served with `application/wasm` MIME and gzip/brotli.
|
|
139
|
+
- [ ] No cross-origin subresource silently blocked by COEP (check the network tab).
|
|
140
|
+
- [ ] Godot + web ⇒ confirmed GDScript, not C#/.NET.
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot
|
|
3
|
+
description: "Use when writing, reviewing, or debugging Godot 4.x games in GDScript (typed) or C# — nodes/scenes/custom Resources, autoload singletons and an EventBus, signals via Callable, @onready/@export/@tool, static typing, CharacterBody2D/3D + velocity + move_and_slide(), _process vs _physics_process, .tscn/.tres text formats, and headless GUT/gdUnit4 tests. Enforces Godot 4 APIs and rejects Godot 3 leftovers: yield→await, onready→@onready, .instance()→.instantiate(), KinematicBody→CharacterBody, Spatial→Node3D, string connect(\"sig\",obj,\"m\")→Callable. Triggers: 'write a Godot player controller', 'my move_and_slide does nothing', 'port this Godot 3 script to 4', 'connect a signal in Godot', 'GDScript won't type-check', 'CharacterBody2D won't move', 'add an autoload', 'escribe un script de Godot', 'por qué no se mueve mi personaje'. NOT Unity MonoBehaviour or Unreal (route to unity/unreal); NOT pure C++/CMake tooling unrelated to GDExtension (that is cpp); NOT shader code in .gdshader (that is gamedev-shaders)."
|
|
4
|
+
tags: [godot, gdscript, game-dev, csharp, godot4]
|
|
5
|
+
recommends: [gamedev-shaders, gamedev-multiplayer, gamedev-physics, gamedev-pathing, gamedev-shipping, game-design, cpp]
|
|
6
|
+
profiles: [full]
|
|
7
|
+
origin: risco
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Godot 4.x (GDScript + C#)
|
|
11
|
+
|
|
12
|
+
Write, review, and debug Godot **4.x** games — 2D and 3D — the way the engine is designed:
|
|
13
|
+
scenes as reusable units, composition over deep node trees, signals for decoupling, static
|
|
14
|
+
typing for speed and safety. GDScript (typed) is primary; C# parity snippets sit beside it.
|
|
15
|
+
|
|
16
|
+
## Version contract — read first
|
|
17
|
+
|
|
18
|
+
**This is Godot 4.x. Never emit Godot 3 APIs.** Godot 4 renamed core nodes, moved annotations
|
|
19
|
+
behind `@`, replaced `yield` with `await`, and switched signal/file/tween APIs. A Godot 3 snippet
|
|
20
|
+
will not even parse in 4.x. Before writing or accepting any line, check it against this ban-list:
|
|
21
|
+
|
|
22
|
+
| Never (Godot 3) | Always (Godot 4.x) |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `yield(timer, "timeout")` | `await timer.timeout` |
|
|
25
|
+
| `onready var x = ...` | `@onready var x = ...` |
|
|
26
|
+
| `export var hp = 3` | `@export var hp := 3` |
|
|
27
|
+
| `tool` (script mode line) | `@tool` (annotation, first line) |
|
|
28
|
+
| `KinematicBody` / `KinematicBody2D` | `CharacterBody3D` / `CharacterBody2D` |
|
|
29
|
+
| `Spatial` | `Node3D` |
|
|
30
|
+
| `Area` / `RigidBody` / `StaticBody` | `Area3D` / `RigidBody3D` / `StaticBody3D` |
|
|
31
|
+
| `Sprite` | `Sprite2D` |
|
|
32
|
+
| `scene.instance()` | `scene.instantiate()` |
|
|
33
|
+
| `move_and_slide(velocity, UP)` (positional) | set `velocity` property, then `move_and_slide()` — **no args** |
|
|
34
|
+
| `connect("hit", self, "_on_hit")` | `node.hit.connect(_on_hit)` (Callable) |
|
|
35
|
+
| `File.new()` / `Directory.new()` | `FileAccess.open(...)` / `DirAccess.open(...)` |
|
|
36
|
+
| standalone `Tween` node + `interpolate_property` | `create_tween()` → `tween.tween_property(...)` |
|
|
37
|
+
| `OS.get_ticks_msec` for gameplay timing | `Time.get_ticks_msec()` (OS timing moved to `Time`) |
|
|
38
|
+
| `PoolByteArray` / `PoolVector2Array` | `PackedByteArray` / `PackedVector2Array` |
|
|
39
|
+
|
|
40
|
+
**Lifecycle overrides must chain the parent** with `super()` (Godot 3 called it implicitly; Godot 4
|
|
41
|
+
does not). If you override `_ready`, `_process`, `_init`, etc. in a script that `extends` another
|
|
42
|
+
script defining them, call `super()` / `super._ready()` or the base logic silently never runs.
|
|
43
|
+
|
|
44
|
+
**Silent-breakers** (compile fine, behave wrong — the dangerous class):
|
|
45
|
+
|
|
46
|
+
- `Array.slice(begin, end)` — `end` is now **exclusive** (was inclusive in Godot 3). `[1,2,3,4].slice(1,3)` → `[2,3]`.
|
|
47
|
+
- `Camera2D.zoom` is **inverted** vs Godot 3: a **larger** zoom now means zoomed **in** (magnified). `Vector2(2,2)` = 2× magnification, not half.
|
|
48
|
+
- `TileMap` is **deprecated** → use one `TileMapLayer` node per layer (since 4.3).
|
|
49
|
+
- Angles are radians; `_process(delta)` delta is a `float` (GDScript) / `double` (C#) in **seconds**.
|
|
50
|
+
|
|
51
|
+
Full table with every rename → `references/godot3-to-4-traps.md`.
|
|
52
|
+
|
|
53
|
+
## Project & scene organization
|
|
54
|
+
|
|
55
|
+
- The **scene** (`.tscn`) is the reusable unit — a self-contained tree you instance many times
|
|
56
|
+
(a Player, a Bullet, a HUD). Prefer **composition**: small scenes/nodes assembled, not one
|
|
57
|
+
60-node monolith. If a subtree has its own behavior, make it its own scene.
|
|
58
|
+
- One responsibility per script. Attach behavior to the scene's **root**; child nodes are parts.
|
|
59
|
+
- `class_name Foo` registers a global type usable in the inspector and as `Foo.new()`. Use it for
|
|
60
|
+
reusable scripts and custom Resources; skip it for one-off scene scripts.
|
|
61
|
+
- **Files**: `snake_case.gd` / `snake_case.tscn` for scenes and scripts; `PascalCase` for node
|
|
62
|
+
names in the tree and for `class_name`. Group by feature (`player/`, `enemy/`, `ui/`), not by type.
|
|
63
|
+
|
|
64
|
+
## Nodes vs scenes vs scripts vs custom Resources
|
|
65
|
+
|
|
66
|
+
| You need | Use |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| A thing in the tree that renders / moves / collides / processes | a **Node** (typed subclass) |
|
|
69
|
+
| A reusable, instanceable bundle of nodes | a **scene** (`.tscn`) |
|
|
70
|
+
| Behavior attached to a node | a **script** (`.gd` / `.cs`) |
|
|
71
|
+
| Pure data (stats, items, dialogue, level config) with no place in the tree | a **custom `Resource`** (`.tres`) |
|
|
72
|
+
|
|
73
|
+
Custom `Resource`s are Godot's typed, savable, inspector-editable data objects — reach for them
|
|
74
|
+
instead of loose Dictionaries or JSON for game data. See `references/nodes-scenes-resources.md`.
|
|
75
|
+
|
|
76
|
+
```gdscript
|
|
77
|
+
class_name EnemyStats extends Resource
|
|
78
|
+
@export var max_health: int = 30
|
|
79
|
+
@export var speed: float = 120.0
|
|
80
|
+
@export var loot_table: Array[ItemDrop] = []
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Autoloads / singletons + EventBus
|
|
84
|
+
|
|
85
|
+
Register a script or scene as an **autoload** (Project → Project Settings → Globals/Autoload) to get
|
|
86
|
+
one always-present instance reachable by name from anywhere. Use it for cross-cutting state (save
|
|
87
|
+
game, audio, run config) — not as a dumping ground.
|
|
88
|
+
|
|
89
|
+
The **EventBus** pattern decouples unrelated systems: an autoload that owns only signals. Emitters
|
|
90
|
+
and listeners never reference each other, just the bus.
|
|
91
|
+
|
|
92
|
+
```gdscript
|
|
93
|
+
# event_bus.gd (autoload named "Events")
|
|
94
|
+
extends Node
|
|
95
|
+
signal enemy_died(position: Vector2, xp: int)
|
|
96
|
+
signal score_changed(new_score: int)
|
|
97
|
+
```
|
|
98
|
+
```gdscript
|
|
99
|
+
# emitter # listener (anywhere)
|
|
100
|
+
Events.enemy_died.emit(global_position, 10)
|
|
101
|
+
Events.enemy_died.connect(_on_enemy_died)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Keep gameplay logic in nodes; let the bus carry the *notification*, not the behavior.
|
|
105
|
+
|
|
106
|
+
## Node access & lifecycle
|
|
107
|
+
|
|
108
|
+
- `_init()` runs at construction (no tree, no `@onready` yet). `_ready()` runs once the node and
|
|
109
|
+
all children are in the tree — do node wiring here.
|
|
110
|
+
- `@onready var x = $Path` defers the assignment to `_ready`, so the child exists. Never grab
|
|
111
|
+
children in `_init`.
|
|
112
|
+
- **Prefer unique names**: mark a node **Unique Name in Owner** (`%`) and access `%HealthBar`
|
|
113
|
+
instead of the fragile, refactor-breaking `get_node("../../UI/HealthBar")`. `$Foo` is fine for a
|
|
114
|
+
direct child.
|
|
115
|
+
- Cache node lookups in `@onready` vars; don't call `get_node` every frame.
|
|
116
|
+
|
|
117
|
+
```gdscript
|
|
118
|
+
extends CharacterBody2D
|
|
119
|
+
@onready var sprite: Sprite2D = $Sprite2D
|
|
120
|
+
@onready var health_bar: ProgressBar = %HealthBar # unique name, position-independent
|
|
121
|
+
|
|
122
|
+
func _ready() -> void:
|
|
123
|
+
super() # chain the parent's _ready if the base defines one
|
|
124
|
+
health_bar.value = 100
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Signals
|
|
128
|
+
|
|
129
|
+
Signals are Godot's decoupling primitive. In Godot 4 you connect a **Callable**, not strings.
|
|
130
|
+
|
|
131
|
+
- **Declare** with typed params; **name in the past tense** for facts that happened
|
|
132
|
+
(`health_depleted`, `item_collected`), present-tense imperative only for requests.
|
|
133
|
+
- **Connect**: `node.signal_name.connect(_on_thing)` — a direct method reference, checked at parse
|
|
134
|
+
time. Add `CONNECT_ONE_SHOT` for auto-disconnect after one fire.
|
|
135
|
+
- **Disconnect discipline**: a connection to a node that gets freed is cleaned up automatically, but
|
|
136
|
+
connections you make to *long-lived* objects (autoloads, the bus) from a short-lived node must be
|
|
137
|
+
disconnected in `_exit_tree()`, or use `CONNECT_ONE_SHOT`, to avoid calls into freed instances.
|
|
138
|
+
|
|
139
|
+
```gdscript
|
|
140
|
+
signal health_depleted
|
|
141
|
+
signal health_changed(current: int, max: int)
|
|
142
|
+
|
|
143
|
+
func take_damage(amount: int) -> void:
|
|
144
|
+
_health -= amount
|
|
145
|
+
health_changed.emit(_health, _max_health)
|
|
146
|
+
if _health <= 0:
|
|
147
|
+
health_depleted.emit()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## @export / @tool (inspector config)
|
|
151
|
+
|
|
152
|
+
`@export` exposes a variable in the Inspector so designers tune it without touching code. Use ranges,
|
|
153
|
+
groups, and typed exports so the inspector gives real widgets and validation.
|
|
154
|
+
|
|
155
|
+
```gdscript
|
|
156
|
+
@export var title: String = "Level 1"
|
|
157
|
+
@export_range(0.0, 1.0, 0.05) var volume := 0.8
|
|
158
|
+
@export_group("Movement")
|
|
159
|
+
@export var speed: float = 300.0
|
|
160
|
+
@export var jump_velocity: float = -400.0
|
|
161
|
+
@export var projectile: PackedScene # drag a .tscn in the inspector
|
|
162
|
+
@export var stats: EnemyStats # a custom Resource slot
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`@tool` at the top of a script runs it **in the editor** too — for gizmos, procedural previews, or
|
|
166
|
+
validating exported data. Guard runtime-only code with `if Engine.is_editor_hint(): return`.
|
|
167
|
+
|
|
168
|
+
## Static (typed) GDScript
|
|
169
|
+
|
|
170
|
+
Type everything. Typed GDScript is faster (the VM skips dynamic dispatch) and catches errors at parse
|
|
171
|
+
time. Use `:=` when the type is inferable, `: Type` when it isn't, and avoid `Variant`/untyped.
|
|
172
|
+
|
|
173
|
+
```gdscript
|
|
174
|
+
var speed: float = 300.0 # explicit
|
|
175
|
+
var dir := Vector2.ZERO # inferred
|
|
176
|
+
var enemies: Array[Enemy] = [] # typed array
|
|
177
|
+
|
|
178
|
+
func distance_to(target: Node2D) -> float:
|
|
179
|
+
return global_position.distance_to(target.global_position)
|
|
180
|
+
|
|
181
|
+
func _on_body_entered(body: Node) -> void:
|
|
182
|
+
var enemy := body as Enemy # safe cast → null if wrong type, no crash
|
|
183
|
+
if enemy:
|
|
184
|
+
enemy.take_damage(10)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Naming: `snake_case` vars/funcs/signals, `PascalCase` types/`class_name`/nodes, `CONSTANT_CASE`
|
|
188
|
+
consts, tabs for indent, lines < 100 cols. Cheat-sheet → `references/gdscript-style.md`.
|
|
189
|
+
|
|
190
|
+
## `_process` vs `_physics_process`
|
|
191
|
+
|
|
192
|
+
- `_physics_process(delta)` — **fixed** tick (default 60 Hz), the same every step. All movement,
|
|
193
|
+
`move_and_slide()`, forces, and collision-dependent logic go here.
|
|
194
|
+
- `_process(delta)` — runs **once per rendered frame** (variable rate). Use for visuals, UI, and
|
|
195
|
+
non-physics polish.
|
|
196
|
+
- **Always scale rate-based change by `delta`** so behavior is framerate-independent. `move_and_slide()`
|
|
197
|
+
and `move_and_collide()` already fold in `delta` internally — do **not** multiply the velocity you
|
|
198
|
+
hand them by `delta` again.
|
|
199
|
+
|
|
200
|
+
## Resources & data — `.tres` / `.tscn` are strict text formats
|
|
201
|
+
|
|
202
|
+
`.tscn` and `.tres` are line-oriented text with a strict header/section grammar. **Do not hand-edit
|
|
203
|
+
them past trivial value tweaks, and never launch on a file you hand-authored without validating** —
|
|
204
|
+
one bad `ext_resource` id, `[node]` line, or `load_steps` count corrupts the whole scene and Godot
|
|
205
|
+
refuses to open it. Prefer editing through the editor or building Resources in code and `ResourceSaver.save()`.
|
|
206
|
+
|
|
207
|
+
- **`preload("res://x.tscn")`** resolves at **parse/compile** time — the dependency is baked in; use
|
|
208
|
+
for assets you always need.
|
|
209
|
+
- **`load("res://x.tscn")`** resolves at **runtime** — use for dynamic/optional paths (and to avoid
|
|
210
|
+
circular preloads). Both return a `PackedScene`; call `.instantiate()` to get a node.
|
|
211
|
+
|
|
212
|
+
## 2D / 3D bodies quickstart
|
|
213
|
+
|
|
214
|
+
The move-anything-controllable body is `CharacterBody2D` / `CharacterBody3D`. The Godot 4 flow is:
|
|
215
|
+
**write the `velocity` property, then call `move_and_slide()` with no arguments.**
|
|
216
|
+
|
|
217
|
+
```gdscript
|
|
218
|
+
extends CharacterBody2D
|
|
219
|
+
@export var speed: float = 300.0
|
|
220
|
+
@export var jump_velocity: float = -400.0
|
|
221
|
+
|
|
222
|
+
func _physics_process(delta: float) -> void:
|
|
223
|
+
if not is_on_floor():
|
|
224
|
+
velocity += get_gravity() * delta # get_gravity(): project-configured vector
|
|
225
|
+
if Input.is_action_just_pressed("jump") and is_on_floor():
|
|
226
|
+
velocity.y = jump_velocity
|
|
227
|
+
var dir := Input.get_axis("move_left", "move_right")
|
|
228
|
+
velocity.x = dir * speed
|
|
229
|
+
move_and_slide() # NO args in Godot 4 — reads the velocity property
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
3D is identical with `CharacterBody3D`, `Vector3`, and an X/Z input plane; body/area suffixes are
|
|
233
|
+
`3D`. Deeper body/physics tuning → `gamedev-physics`.
|
|
234
|
+
|
|
235
|
+
## Language parity — GDScript ↔ C#
|
|
236
|
+
|
|
237
|
+
Same engine, same nodes; C# uses PascalCase members, `partial` classes, and attributes. Signals
|
|
238
|
+
become C# `event`s (generated by source-gen). C# support requires the **.NET (Mono) build** of Godot.
|
|
239
|
+
|
|
240
|
+
```gdscript
|
|
241
|
+
# GDScript
|
|
242
|
+
extends Node
|
|
243
|
+
signal health_depleted
|
|
244
|
+
@export var speed: float = 300.0
|
|
245
|
+
|
|
246
|
+
func _ready() -> void:
|
|
247
|
+
health_depleted.connect(_on_depleted)
|
|
248
|
+
health_depleted.emit()
|
|
249
|
+
|
|
250
|
+
func _on_depleted() -> void:
|
|
251
|
+
print("dead")
|
|
252
|
+
```
|
|
253
|
+
```csharp
|
|
254
|
+
// C# — same node, .NET build
|
|
255
|
+
using Godot;
|
|
256
|
+
|
|
257
|
+
public partial class Player : Node
|
|
258
|
+
{
|
|
259
|
+
[Signal] public delegate void HealthDepletedEventHandler();
|
|
260
|
+
[Export] public float Speed { get; set; } = 300.0f;
|
|
261
|
+
|
|
262
|
+
public override void _Ready()
|
|
263
|
+
{
|
|
264
|
+
base._Ready(); // chain the parent (== super())
|
|
265
|
+
HealthDepleted += OnDepleted; // connect via the generated event
|
|
266
|
+
EmitSignal(SignalName.HealthDepleted);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private void OnDepleted() => GD.Print("dead");
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`move_and_slide()`→`MoveAndSlide()`, `$Node`→`GetNode<T>("Node")`, `%Node`→`GetNode<T>("%Node")`,
|
|
274
|
+
`preload`→`GD.Load<T>(...)`. Full parity table → `references/export-and-testing.md`.
|
|
275
|
+
|
|
276
|
+
**GDExtension / C++**: for hot native code, build a `godot-cpp` GDExtension (`.gdextension` file,
|
|
277
|
+
`GDREGISTER_CLASS`, `_bind_methods()`) rather than a Godot module — no engine recompile, and it loads
|
|
278
|
+
like any other library. That is native-tooling territory → pair with `cpp`.
|
|
279
|
+
|
|
280
|
+
## Export & testing
|
|
281
|
+
|
|
282
|
+
- **Test with GUT 9.x** (GDScript, `extends GutTest`) or **gdUnit4** (GDScript + C#). Put tests
|
|
283
|
+
under `test/` or `res://tests/`; assert behavior, not private state.
|
|
284
|
+
- **Run headless in CI**: `godot --headless -s addons/gut/gut_cmdln.gd -gdir=res://test -gexit`
|
|
285
|
+
(GUT). Export via templates: `godot --headless --export-release "Linux/X11" build/game.x86_64`.
|
|
286
|
+
- Validate a project before shipping: open in the editor once (catches broken `.tscn`/`.tres`),
|
|
287
|
+
then export-check per platform. Details → `references/export-and-testing.md`.
|
|
288
|
+
|
|
289
|
+
## Guardrails / gotchas
|
|
290
|
+
|
|
291
|
+
- Freeing: call `queue_free()` (safe, end of frame), not `free()` mid-signal. Guard reused refs with
|
|
292
|
+
`is_instance_valid(node)`.
|
|
293
|
+
- Never busy-wait; `await get_tree().create_timer(1.0).timeout` or a signal.
|
|
294
|
+
- `get_node`/`$` on a missing path returns `null` and errors — prefer `%UniqueName` and typed `@onready`.
|
|
295
|
+
- Don't multiply by `delta` twice (see `move_and_slide`). Don't put physics in `_process`.
|
|
296
|
+
- One `TileMapLayer` per layer; `TileMap` is deprecated.
|
|
297
|
+
- Editor-touching code must guard on `Engine.is_editor_hint()` under `@tool`.
|
|
298
|
+
|
|
299
|
+
## Related skills
|
|
300
|
+
|
|
301
|
+
- [`game-design`](../game-design/SKILL.md) — mechanics, loops, and feel *before* you script them.
|
|
302
|
+
- [`gamedev-shaders`](../gamedev-shaders/SKILL.md) — `.gdshader` / visual shaders (out of scope here).
|
|
303
|
+
- [`gamedev-physics`](../gamedev-physics/SKILL.md) — joints, RigidBody tuning, deep collision layers.
|
|
304
|
+
- [`gamedev-pathing`](../gamedev-pathing/SKILL.md) — NavigationAgent, A*, steering beyond the basics.
|
|
305
|
+
- [`gamedev-multiplayer`](../gamedev-multiplayer/SKILL.md) — `MultiplayerSynchronizer`/RPC/netcode.
|
|
306
|
+
- [`gamedev-shipping`](../gamedev-shipping/SKILL.md) — store builds, signing, platform export at scale.
|
|
307
|
+
- [`cpp`](../cpp/SKILL.md) — the GDExtension / `godot-cpp` native side and its CMake/build tooling.
|
|
308
|
+
|
|
309
|
+
## Checklist
|
|
310
|
+
|
|
311
|
+
- [ ] Zero Godot 3 APIs — checked every line against the ban-list (`yield`/`onready`/`instance()`/`KinematicBody`/`Spatial`/string `connect`/`File`).
|
|
312
|
+
- [ ] `move_and_slide()` called with **no args**; `velocity` set as a property; not multiplied by `delta` twice.
|
|
313
|
+
- [ ] Overridden lifecycle methods call `super()` where a base defines them.
|
|
314
|
+
- [ ] Signals connected via Callable (`x.sig.connect(_on_x)`), named past-tense, disconnected/one-shot where needed.
|
|
315
|
+
- [ ] Everything typed (`:` / `:=`, typed params/returns, typed Arrays); no stray `Variant`.
|
|
316
|
+
- [ ] Physics in `_physics_process`, visuals in `_process`; rate-based values scaled by `delta`.
|
|
317
|
+
- [ ] Node access via `@onready` + `%UniqueName`, not deep `get_node("../..")` chains.
|
|
318
|
+
- [ ] `.tscn`/`.tres` not hand-corrupted; project opens in-editor before export.
|
|
319
|
+
- [ ] C# snippets use `partial`, `[Export]`/`[Signal]`, `base._Ready()`, PascalCase (if C# requested).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Eval harness — `godot` skill
|
|
2
|
+
|
|
3
|
+
These cases are read by the rsc skill-eval harness (a Claude agent with the full skill catalog
|
|
4
|
+
loaded) or by hand. They assert two things. **Triggering**: the `godot` description fires on each
|
|
5
|
+
`should_trigger` prompt — including symptom-only and Spanish phrasings — and stays quiet on the
|
|
6
|
+
`should_not_trigger` near-misses, routing each to its named sibling (`unity`, `unreal`,
|
|
7
|
+
`gamedev-shaders`, `cpp`, `game-design`, `gamedev-multiplayer`) instead. **Capability**: with
|
|
8
|
+
`SKILL.md` and its references loaded, the generated Godot code satisfies every
|
|
9
|
+
`capability.must_include` bullet — the Godot 4 flow (`velocity` property + no-arg `move_and_slide()`),
|
|
10
|
+
typed GDScript, Callable signals, `@export`/`@onready`, zero Godot 3 leftovers, and a correct C#
|
|
11
|
+
parity block — and clearly beats the no-skill baseline. Because LLM routing is non-deterministic,
|
|
12
|
+
run 3–5 trials per prompt and pass on a majority. No network is required; the capability rubric is
|
|
13
|
+
judged by reading the output against the bullets.
|