# Godot 4 CLAUDE.md

> GDScript 2.0 rules that refuse Godot 3 APIs.

Pack: Godot 4 (`godot-4`)
Page: https://loadout-blush.vercel.app/packs/godot-4

## What it does

Stops agents writing Godot 3 into a Godot 4 project: KinematicBody2D, yield, export var, and move_and_slide(velocity). Pinned to Godot 4.7.2 with an explicit rename table from the official upgrade guide.

## Who it's for

People using Cursor, Claude Code, or Copilot on a Godot 4 game, especially anyone who has seen the agent emit Godot 3 code.

## Versions pinned

- Godot: 4.7.2-stable (18 August 2026)
- GDScript: 2.0

Last tested: 2026-10-15

## How it was checked

doc-verified; GDScript sample parsed by Godot 4.7.2 headless (--check-only), with a Godot 3 control script correctly rejected

## Sources

- https://godotengine.org/article/maintenance-release-godot-4-7-2/
- https://docs.godotengine.org/en/4.7/tutorials/physics/using_character_body_2d.html
- https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.html

## Install

### Cursor

Path: `.cursor/rules/godot-4.mdc`

1. Download the Cursor zip, or copy the .mdc file from this page.
2. Save it as .cursor/rules/godot-4.mdc.
3. Commit it. Cursor reads project rules from that folder.

### Claude Code

Path: `CLAUDE.md`

1. Download the Claude zip, or copy CLAUDE.md.
2. Put CLAUDE.md in the project root.
3. Claude Code reads it as project memory on the next session.

### AGENTS.md

Path: `AGENTS.md`

1. Download the Agents zip, or copy AGENTS.md.
2. Put AGENTS.md in the project root.
3. Agents that read AGENTS.md, including Codex and Cursor, pick it up there.

### GitHub Copilot

Path: `.github/copilot-instructions.md`

1. Download the Copilot zip, or copy the instructions file.
2. Save it as .github/copilot-instructions.md.
3. Copilot reads repository instructions from that path.

## Files

### Cursor

Install path: `.cursor/rules/godot-4.mdc`
Plain text: https://loadout-blush.vercel.app/packs/godot-4/files/cursor.mdc

````
---
description: Godot 4.7 GDScript 2.0. CharacterBody, move_and_slide(), @export, and a Godot 3 ban list.
globs: "**/*.gd"
alwaysApply: false
---
# Godot 4.7 GDScript 2.0

Pinned to Godot 4.7.2 stable (18 August 2026), GDScript 2.0. Write only Godot 4 code. The list at the bottom is banned even if an old tutorial or an older model suggests it.

## Scenes and scripts
- Do not delete `.godot/`, `project.godot`, `.import`, or `.uid` files. Do not hand-edit `.import` files.
- Put gameplay code on the node that owns the behavior. Use `@onready` to cache child nodes. Do not call `get_node()` every frame.
- `@export` is for designer-facing values only. Give it a type and a default.

```gdscript
extends CharacterBody2D

@export var speed: float = 200.0
@onready var sprite: Sprite2D = $Sprite2D

func _physics_process(_delta: float) -> void:
    var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    velocity = input * speed
    move_and_slide()
```

## Movement
- Bodies are `CharacterBody2D` and `CharacterBody3D`. They own a `velocity` property (`Vector2` or `Vector3`).
- Set `velocity`, then call `move_and_slide()` with no arguments. Do not pass the velocity, an up vector, or a delta into `move_and_slide`.
- Do not multiply horizontal `velocity` by `delta`. `move_and_slide()` applies the timestep. Gravity is the exception: `velocity.y += gravity * delta` before the call.
- Read input with `Input.get_vector` or `Input.is_action_pressed` using actions from the Input Map. Do not hardcode key scancodes in gameplay code.
- Run movement in `_physics_process`, not `_process`.

## Signals and loading
- Connect with a Callable: `button.pressed.connect(_on_pressed)`. Disconnect with `disconnect`. Emit with `hit.emit(amount)`, not `emit_signal("hit", amount)`.
- Declare a signal as `signal hit(amount: int)`.
- Load a scene with `preload("res://player.tscn")` when the path is fixed, and instance it with `instantiate()`, not `instance()`.
- Change scene with `get_tree().change_scene_to_file("res://main.tscn")`.
- Read and write files with `FileAccess.open`. List directories with `DirAccess`. Both are static. There is no `File.new()`.

## Never write these (Godot 3, or removed)

| Do not write | Write instead |
| --- | --- |
| `KinematicBody2D`, `KinematicBody` | `CharacterBody2D`, `CharacterBody3D` |
| `yield(...)`, `yield(self, "completed")` | `await` |
| `export var`, `export(int)`, `onready var` | `@export var`, `@onready var` |
| `move_and_slide(velocity)` | set `velocity`, then `move_and_slide()` |
| `connect("pressed", self, "_on_pressed")` | `pressed.connect(_on_pressed)` |
| `emit_signal("hit", n)` | `hit.emit(n)` |
| `packed.instance()` | `packed.instantiate()` |
| `File.new()`, `Directory.new()` | `FileAccess`, `DirAccess` |
| `Spatial`, `SpatialMaterial` | `Node3D`, `StandardMaterial3D` |
| `rand_range(a, b)` | `randf_range(a, b)` |
| `PoolStringArray`, `PoolIntArray` | `PackedStringArray`, `PackedInt32Array` |
| `change_scene("res://x.tscn")` | `change_scene_to_file("res://x.tscn")` |
| `OS.get_window_size()`, `OS.window_fullscreen` | `DisplayServer` window methods |

If you are about to type any name in the left column, stop and use the right column.

## Typing and timing
- Use static types on variables, parameters, and return values (`var hp: int = 10`, `func hit(n: int) -> void`). Use `:=` only when the type is obvious from the right side.
- Wait for a timer with `await get_tree().create_timer(1.0).timeout`.
- Animate with `var tween := create_tween()`. Do not create a `Tween` node with `Tween.new()`.
- Mark editor scripts with `@tool` on the first line, and check `Engine.is_editor_hint()` inside them.
- Property accessors use the GDScript 2.0 `get:` / `set(value):` blocks, not `setget`.

## Done checklist
1. The Godot editor shows no parser errors on the edited scripts.
2. Nothing from the ban list appears in the diff.
3. You said whether the scene was actually run.
````

### Claude

Install path: `CLAUDE.md`
Plain text: https://loadout-blush.vercel.app/packs/godot-4/files/CLAUDE.md

````
# Godot 4.7 GDScript 2.0

Pinned to Godot 4.7.2 stable (18 August 2026), GDScript 2.0. Write only Godot 4 code. The list at the bottom is banned even if an old tutorial or an older model suggests it.

## Scenes and scripts
- Do not delete `.godot/`, `project.godot`, `.import`, or `.uid` files. Do not hand-edit `.import` files.
- Put gameplay code on the node that owns the behavior. Use `@onready` to cache child nodes. Do not call `get_node()` every frame.
- `@export` is for designer-facing values only. Give it a type and a default.

```gdscript
extends CharacterBody2D

@export var speed: float = 200.0
@onready var sprite: Sprite2D = $Sprite2D

func _physics_process(_delta: float) -> void:
    var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    velocity = input * speed
    move_and_slide()
```

## Movement
- Bodies are `CharacterBody2D` and `CharacterBody3D`. They own a `velocity` property (`Vector2` or `Vector3`).
- Set `velocity`, then call `move_and_slide()` with no arguments. Do not pass the velocity, an up vector, or a delta into `move_and_slide`.
- Do not multiply horizontal `velocity` by `delta`. `move_and_slide()` applies the timestep. Gravity is the exception: `velocity.y += gravity * delta` before the call.
- Read input with `Input.get_vector` or `Input.is_action_pressed` using actions from the Input Map. Do not hardcode key scancodes in gameplay code.
- Run movement in `_physics_process`, not `_process`.

## Signals and loading
- Connect with a Callable: `button.pressed.connect(_on_pressed)`. Disconnect with `disconnect`. Emit with `hit.emit(amount)`, not `emit_signal("hit", amount)`.
- Declare a signal as `signal hit(amount: int)`.
- Load a scene with `preload("res://player.tscn")` when the path is fixed, and instance it with `instantiate()`, not `instance()`.
- Change scene with `get_tree().change_scene_to_file("res://main.tscn")`.
- Read and write files with `FileAccess.open`. List directories with `DirAccess`. Both are static. There is no `File.new()`.

## Never write these (Godot 3, or removed)

| Do not write | Write instead |
| --- | --- |
| `KinematicBody2D`, `KinematicBody` | `CharacterBody2D`, `CharacterBody3D` |
| `yield(...)`, `yield(self, "completed")` | `await` |
| `export var`, `export(int)`, `onready var` | `@export var`, `@onready var` |
| `move_and_slide(velocity)` | set `velocity`, then `move_and_slide()` |
| `connect("pressed", self, "_on_pressed")` | `pressed.connect(_on_pressed)` |
| `emit_signal("hit", n)` | `hit.emit(n)` |
| `packed.instance()` | `packed.instantiate()` |
| `File.new()`, `Directory.new()` | `FileAccess`, `DirAccess` |
| `Spatial`, `SpatialMaterial` | `Node3D`, `StandardMaterial3D` |
| `rand_range(a, b)` | `randf_range(a, b)` |
| `PoolStringArray`, `PoolIntArray` | `PackedStringArray`, `PackedInt32Array` |
| `change_scene("res://x.tscn")` | `change_scene_to_file("res://x.tscn")` |
| `OS.get_window_size()`, `OS.window_fullscreen` | `DisplayServer` window methods |

If you are about to type any name in the left column, stop and use the right column.

## Typing and timing
- Use static types on variables, parameters, and return values (`var hp: int = 10`, `func hit(n: int) -> void`). Use `:=` only when the type is obvious from the right side.
- Wait for a timer with `await get_tree().create_timer(1.0).timeout`.
- Animate with `var tween := create_tween()`. Do not create a `Tween` node with `Tween.new()`.
- Mark editor scripts with `@tool` on the first line, and check `Engine.is_editor_hint()` inside them.
- Property accessors use the GDScript 2.0 `get:` / `set(value):` blocks, not `setget`.

## Done checklist
1. The Godot editor shows no parser errors on the edited scripts.
2. Nothing from the ban list appears in the diff.
3. You said whether the scene was actually run.

## Claude Code
- If a Godot MCP bridge is connected, use it to inspect the scene tree instead of guessing node paths.
- Do not claim a scene runs unless Godot was actually launched. Say "not run" when it was not.
````

### Agents

Install path: `AGENTS.md`
Plain text: https://loadout-blush.vercel.app/packs/godot-4/files/AGENTS.md

````
# Godot 4.7 GDScript 2.0

Pinned to Godot 4.7.2 stable (18 August 2026), GDScript 2.0. Write only Godot 4 code. The list at the bottom is banned even if an old tutorial or an older model suggests it.

## Scenes and scripts
- Do not delete `.godot/`, `project.godot`, `.import`, or `.uid` files. Do not hand-edit `.import` files.
- Put gameplay code on the node that owns the behavior. Use `@onready` to cache child nodes. Do not call `get_node()` every frame.
- `@export` is for designer-facing values only. Give it a type and a default.

```gdscript
extends CharacterBody2D

@export var speed: float = 200.0
@onready var sprite: Sprite2D = $Sprite2D

func _physics_process(_delta: float) -> void:
    var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    velocity = input * speed
    move_and_slide()
```

## Movement
- Bodies are `CharacterBody2D` and `CharacterBody3D`. They own a `velocity` property (`Vector2` or `Vector3`).
- Set `velocity`, then call `move_and_slide()` with no arguments. Do not pass the velocity, an up vector, or a delta into `move_and_slide`.
- Do not multiply horizontal `velocity` by `delta`. `move_and_slide()` applies the timestep. Gravity is the exception: `velocity.y += gravity * delta` before the call.
- Read input with `Input.get_vector` or `Input.is_action_pressed` using actions from the Input Map. Do not hardcode key scancodes in gameplay code.
- Run movement in `_physics_process`, not `_process`.

## Signals and loading
- Connect with a Callable: `button.pressed.connect(_on_pressed)`. Disconnect with `disconnect`. Emit with `hit.emit(amount)`, not `emit_signal("hit", amount)`.
- Declare a signal as `signal hit(amount: int)`.
- Load a scene with `preload("res://player.tscn")` when the path is fixed, and instance it with `instantiate()`, not `instance()`.
- Change scene with `get_tree().change_scene_to_file("res://main.tscn")`.
- Read and write files with `FileAccess.open`. List directories with `DirAccess`. Both are static. There is no `File.new()`.

## Never write these (Godot 3, or removed)

| Do not write | Write instead |
| --- | --- |
| `KinematicBody2D`, `KinematicBody` | `CharacterBody2D`, `CharacterBody3D` |
| `yield(...)`, `yield(self, "completed")` | `await` |
| `export var`, `export(int)`, `onready var` | `@export var`, `@onready var` |
| `move_and_slide(velocity)` | set `velocity`, then `move_and_slide()` |
| `connect("pressed", self, "_on_pressed")` | `pressed.connect(_on_pressed)` |
| `emit_signal("hit", n)` | `hit.emit(n)` |
| `packed.instance()` | `packed.instantiate()` |
| `File.new()`, `Directory.new()` | `FileAccess`, `DirAccess` |
| `Spatial`, `SpatialMaterial` | `Node3D`, `StandardMaterial3D` |
| `rand_range(a, b)` | `randf_range(a, b)` |
| `PoolStringArray`, `PoolIntArray` | `PackedStringArray`, `PackedInt32Array` |
| `change_scene("res://x.tscn")` | `change_scene_to_file("res://x.tscn")` |
| `OS.get_window_size()`, `OS.window_fullscreen` | `DisplayServer` window methods |

If you are about to type any name in the left column, stop and use the right column.

## Typing and timing
- Use static types on variables, parameters, and return values (`var hp: int = 10`, `func hit(n: int) -> void`). Use `:=` only when the type is obvious from the right side.
- Wait for a timer with `await get_tree().create_timer(1.0).timeout`.
- Animate with `var tween := create_tween()`. Do not create a `Tween` node with `Tween.new()`.
- Mark editor scripts with `@tool` on the first line, and check `Engine.is_editor_hint()` inside them.
- Property accessors use the GDScript 2.0 `get:` / `set(value):` blocks, not `setget`.

## Done checklist
1. The Godot editor shows no parser errors on the edited scripts.
2. Nothing from the ban list appears in the diff.
3. You said whether the scene was actually run.
````

### Copilot

Install path: `.github/copilot-instructions.md`
Plain text: https://loadout-blush.vercel.app/packs/godot-4/files/copilot-instructions.md

````
# Godot 4.7 GDScript 2.0

Pinned to Godot 4.7.2 stable (18 August 2026), GDScript 2.0. Write only Godot 4 code. The list at the bottom is banned even if an old tutorial or an older model suggests it.

## Scenes and scripts
- Do not delete `.godot/`, `project.godot`, `.import`, or `.uid` files. Do not hand-edit `.import` files.
- Put gameplay code on the node that owns the behavior. Use `@onready` to cache child nodes. Do not call `get_node()` every frame.
- `@export` is for designer-facing values only. Give it a type and a default.

```gdscript
extends CharacterBody2D

@export var speed: float = 200.0
@onready var sprite: Sprite2D = $Sprite2D

func _physics_process(_delta: float) -> void:
    var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    velocity = input * speed
    move_and_slide()
```

## Movement
- Bodies are `CharacterBody2D` and `CharacterBody3D`. They own a `velocity` property (`Vector2` or `Vector3`).
- Set `velocity`, then call `move_and_slide()` with no arguments. Do not pass the velocity, an up vector, or a delta into `move_and_slide`.
- Do not multiply horizontal `velocity` by `delta`. `move_and_slide()` applies the timestep. Gravity is the exception: `velocity.y += gravity * delta` before the call.
- Read input with `Input.get_vector` or `Input.is_action_pressed` using actions from the Input Map. Do not hardcode key scancodes in gameplay code.
- Run movement in `_physics_process`, not `_process`.

## Signals and loading
- Connect with a Callable: `button.pressed.connect(_on_pressed)`. Disconnect with `disconnect`. Emit with `hit.emit(amount)`, not `emit_signal("hit", amount)`.
- Declare a signal as `signal hit(amount: int)`.
- Load a scene with `preload("res://player.tscn")` when the path is fixed, and instance it with `instantiate()`, not `instance()`.
- Change scene with `get_tree().change_scene_to_file("res://main.tscn")`.
- Read and write files with `FileAccess.open`. List directories with `DirAccess`. Both are static. There is no `File.new()`.

## Never write these (Godot 3, or removed)

| Do not write | Write instead |
| --- | --- |
| `KinematicBody2D`, `KinematicBody` | `CharacterBody2D`, `CharacterBody3D` |
| `yield(...)`, `yield(self, "completed")` | `await` |
| `export var`, `export(int)`, `onready var` | `@export var`, `@onready var` |
| `move_and_slide(velocity)` | set `velocity`, then `move_and_slide()` |
| `connect("pressed", self, "_on_pressed")` | `pressed.connect(_on_pressed)` |
| `emit_signal("hit", n)` | `hit.emit(n)` |
| `packed.instance()` | `packed.instantiate()` |
| `File.new()`, `Directory.new()` | `FileAccess`, `DirAccess` |
| `Spatial`, `SpatialMaterial` | `Node3D`, `StandardMaterial3D` |
| `rand_range(a, b)` | `randf_range(a, b)` |
| `PoolStringArray`, `PoolIntArray` | `PackedStringArray`, `PackedInt32Array` |
| `change_scene("res://x.tscn")` | `change_scene_to_file("res://x.tscn")` |
| `OS.get_window_size()`, `OS.window_fullscreen` | `DisplayServer` window methods |

If you are about to type any name in the left column, stop and use the right column.

## Typing and timing
- Use static types on variables, parameters, and return values (`var hp: int = 10`, `func hit(n: int) -> void`). Use `:=` only when the type is obvious from the right side.
- Wait for a timer with `await get_tree().create_timer(1.0).timeout`.
- Animate with `var tween := create_tween()`. Do not create a `Tween` node with `Tween.new()`.
- Mark editor scripts with `@tool` on the first line, and check `Engine.is_editor_hint()` inside them.
- Property accessors use the GDScript 2.0 `get:` / `set(value):` blocks, not `setget`.

## Done checklist
1. The Godot editor shows no parser errors on the edited scripts.
2. Nothing from the ban list appears in the diff.
3. You said whether the scene was actually run.
````
