Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when the user wants to learn Godot while building — teaching mode that explains the concept, the editor setup, and what to verify, instead of just delivering code. Triggers on "teach me", "explain as we go", "I'm learning Godot", "guide me", "walk me through", "help me understand".
.claude/skills/jame581-godot-mentor/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 112% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 144% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 81% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 544% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 218% | 0% |
> Related skills: godot-brainstorming for design exploration before building, godot-project-setup for scaffolding, godot-code-review for reviewing finished work, godot-debugging for diagnosing runtime issues.
Mentor mode changes the shape of an answer, not its source of truth. It wraps the domain skills — it never replaces them.
Always load the matching domain skill first, then deliver it through the contract below.
"add double jump" + mentor mode
1. invoke godot-prompter:player-controller <- still authoritative
2. deliver its guidance through the 5 beats <- what mentor mode addsAnswering from general Godot knowledge because "it's just teaching" is the primary failure of this mode. The routing table in using-godot-prompter applies in full.
<!-- MENTOR-CARD-START --> Mentor mode is ACTIVE for this project. Load the matching godot-prompter:* domain skill first, then deliver it through these five beats:
Editor beat boundary (v1.13.0): describe node trees, Inspector property names and values, exported-variable wiring, resource assignment, and autoload registration by path. Do not give menu paths, dock layouts, toolbar positions, or other version-specific UI chrome — those move between Godot versions and are the most-hallucinated part of any answer. If a click-path is genuinely required, name the panel it lives in and let the user find it.
Explanation, not scope. Explain what was asked for. Do not add features in order to have more to teach.
Off-ramp. "just give me the code" / "skip the explanation" → drop to normal delivery and set "mode": "normal" in the state file. <!-- MENTOR-CARD-END -->
State lives in the user's home directory, keyed by project path — never in the user's game repository:
~/.godot-prompter/state/<first-16-hex-of-sha256(CANONICAL project path)>.jsonCanonical path form — get this exactly right or the hook will not find the file. Absolute, forward slashes, native drive letter:
| Platform | Canonical form | |---|---| | Windows | C:/Users/you/game — not C:\Users\you\game, and not /c/Users/you/game | | macOS / Linux | /home/you/game |
The same directory has two spellings on Windows: bash sees /c/Users/you/game, most tools see C:\Users\you\game. Hashing the raw path yields a different key on each side and mentor mode silently never restores. The hook normalizes with cygpath -m; match that form when you write.
Compute the key exactly like the hook does:
bash# $P is the canonical project path, e.g. C:/Users/you/game printf '%s' "$P" | sha256sum | cut -c1-16 # or: shasum -a 256
The failure mode is silent — a wrong key just means mentor mode never comes back — so verify the file lands where the hook looks before relying on it.
json{ "project": "C:/Users/you/game", "mode": "mentor", "level": "beginner", "language": "gdscript" }
> project is shown in the Windows canonical form because that is the platform where getting it > wrong fails silently. On macOS / Linux it is just the absolute path, e.g. /home/you/game.
| Key | Values | Meaning | |---|---|---| | project | absolute path | Which project this state belongs to; guards hash collisions | | mode | mentor \| normal | Whether the five beats apply | | level | beginner \| intermediate | How much of Beat 1 and Beat 2 to spell out | | language | gdscript \| csharp | Which example leads | | section_offer | declined | Set when the user refuses the ## GodotPrompter instructions section, so the hook stops offering it |
Why not in the project? A teaching level is a per-developer preference. In-repo it would be committed by default (Godot's .gitignore does not cover it), conflict on every pull between developers at different levels, and require asking permission to create a directory in someone's game repo. None of that arises in $HOME.
Godot version and renderer are deliberately NOT stored. They are read live from project.godot on every session start, so they cannot go stale after an engine upgrade.
Ask for level once, on activation, then remember it. Never re-ask each turn. Merge into an existing state file rather than clobbering unrelated keys.
The SessionStart hook re-reads this file, so mentor mode survives /clear and compaction. It does not reach subagents — SessionStart does not fire on subagent dispatch; a ## GodotPrompter section in the project's agent instructions file (CLAUDE.md, or AGENTS.md / GEMINI.md in an agent-agnostic repo) is what subagents read. On Codex and Antigravity (no hooks) this skill still works; it just does not self-restore after a reset.
| level | Beat 1 (Concept) | Beat 2 (Editor) | |---|---|---| | beginner | Define the Godot term, contrast with the alternative, say why it matters here | Name every node and property explicitly | | intermediate | One or two sentences on the trade-off only | Only the non-obvious wiring |
level controls how much of each beat appears — never whether it appears.
| Anti-pattern | Why it is wrong | Instead | |---|---|---| | Answering without loading the domain skill | Loses verified, version-checked guidance | Invoke the skill, then teach it | | Inventing menu paths so Beat 2 feels complete | Godot's UI moved between 4.3 and 4.7; wrong click-paths are worse than none | Node/Inspector level only | | Growing the feature to create teaching material | The user asked for one thing | Explain that thing; put the rest in Beat 5 | | Re-asking level every turn | Feels like the agent has amnesia | Read it from state | | Five "next steps" | Paralyses a learner | Exactly one | | Skipping the C# example because it is a teaching answer | C# parity is a repo-wide promise | GDScript first, then C# | | Writing state into the user's game repo | Personal preference in a shared file; gets committed | ~/.godot-prompter/state/ |
User: "teach me as we go — I want the player to double jump"
Beat 1 — Concept. Godot's CharacterBody2D has no built-in jump counter. Track jumps used, not a can_double_jump bool: a counter generalises to triple jumps and reads honestly against is_on_floor(), which is your reset signal.
Beat 2 — Editor. No new nodes. On your player script, add an exported max_jumps so you can tune it in the Inspector without editing code.
Beat 3 — Code.
gdscriptextends CharacterBody2D @export var max_jumps: int = 2 @export var jump_velocity: float = -400.0 var _jumps_used: int = 0 func _physics_process(delta: float) -> void: if not is_on_floor(): velocity += get_gravity() * delta else: _jumps_used = 0 # landing is the only reset if Input.is_action_just_pressed("jump") and _jumps_used < max_jumps: velocity.y = jump_velocity _jumps_used += 1 move_and_slide()
csharpusing Godot; public partial class Player : CharacterBody2D { [Export] public int MaxJumps { get; set; } = 2; [Export] public float JumpVelocity { get; set; } = -400.0f; private int _jumpsUsed; public override void _PhysicsProcess(double delta) { Vector2 velocity = Velocity; if (!IsOnFloor()) velocity += GetGravity() * (float)delta; else _jumpsUsed = 0; // landing is the only reset if (Input.IsActionJustPressed("jump") && _jumpsUsed < MaxJumps) { velocity.Y = JumpVelocity; _jumpsUsed++; } Velocity = velocity; MoveAndSlide(); } }
Beat 4 — Verify. Run the scene: two jumps, then nothing until you land. Infinite jumps means is_on_floor() is true mid-air — your collision shape is probably overlapping the floor. A weak second jump means you are adding to a falling velocity.y; assign it rather than accumulating (this code already assigns).
Beat 5 — Next. Add coyote time — a short grace period after leaving a ledge where the first jump still counts as grounded.
godot-prompter:* domain skill before answeringlevel read from state, not re-asked~/.godot-prompter/state/, never into the user's game repo| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | pass→pass | 17,584 | 11,372 | -35% | 1 | 1 | 0% | 2,884 | 4,395 | +52% | 0 | 0 | — |
case-01 | fail→pass | 10,696 | 11,527 | +8% | 1 | 1 | 0% | 1,588 | 3,367 | +112% | 0 | 0 | — |
case-02 | fail→pass | 9,239 | 8,840 | -4% | 1 | 1 | 0% | 1,751 | 4,280 | +144% | 0 | 0 | — |
case-03 | fail→fail | 6,095 | 13,723 | +125% | 1 | 1 | 0% | 860 | 4,817 | +460% | 0 | 0 | — |
case-04 | pass→fail | 11,924 | 14,108 | +18% | 1 | 1 | 0% | 1,934 | 4,778 | +147% | 0 | 0 | — |
case-05 | pass→fail | 19,010 | 14,741 | -22% | 1 | 1 | 0% | 2,737 | 5,042 | +84% | 0 | 0 | — |
case-06 | fail→pass | 13,013 | 7,552 | -42% | 1 | 1 | 0% | 2,146 | 3,883 | +81% | 0 | 0 | — |
case-08 | fail→pass | 6,037 | 17,208 | +185% | 1 | 1 | 0% | 856 | 5,510 | +544% | 0 | 0 | — |
case-09 | fail→pass | 10,575 | 13,193 | +25% | 1 | 1 | 0% | 1,476 | 4,697 | +218% | 0 | 0 | — |
case-10 | fail→pass | 8,994 | 5,933 | -34% | 1 | 1 | 0% | 1,499 | 3,566 | +138% | 0 | 0 | — |
case-11 | fail→pass | 16,678 | 6,902 | -59% | 1 | 1 | 0% | 2,530 | 3,562 | +41% | 0 | 0 | — |
case-12 | pass→pass | 12,730 | 7,712 | -39% | 1 | 1 | 0% | 2,061 | 3,615 | +75% | 0 | 0 | — |
case-13 | pass→pass | 17,259 | 13,987 | -19% | 1 | 1 | 0% | 2,797 | 4,841 | +73% | 0 | 0 | — |
case-14 | pass→pass | 11,525 | 10,842 | -6% | 1 | 1 | 0% | 1,975 | 4,172 | +111% | 0 | 0 | — |
case-15 | fail→pass | 11,026 | 7,821 | -29% | 1 | 1 | 0% | 1,789 | 3,739 | +109% | 0 | 0 | — |
case-16 | fail→pass | 14,581 | 4,995 | -66% | 1 | 1 | 0% | 2,337 | 3,370 | +44% | 0 | 0 | — |
case-17 | pass→pass | 11,123 | 15,459 | +39% | 1 | 1 | 0% | 1,589 | 5,387 | +239% | 0 | 0 | — |
case-18 | fail→pass | 7,169 | 18,922 | +164% | 1 | 1 | 0% | 1,003 | 5,945 | +493% | 0 | 0 | — |
case-19 | pass→pass | 16,545 | 15,114 | -9% | 1 | 1 | 0% | 2,658 | 5,025 | +89% | 0 | 0 | — |
case-20 | pass→pass | 5,270 | 13,476 | +156% | 1 | 1 | 0% | 751 | 4,759 | +534% | 0 | 0 | — |
case-21 | pass→pass | 7,693 | 9,369 | +22% | 1 | 1 | 0% | 1,316 | 4,015 | +205% | 0 | 0 | — |
case-22 | fail→pass | 13,558 | 4,027 | -70% | 1 | 1 | 0% | 2,254 | 3,012 | +34% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +41 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.