415 lines
12 KiB
Markdown
415 lines
12 KiB
Markdown
# AGENTS.md - AI Agent Guidelines for ai-game-2
|
|
|
|
## Project Overview
|
|
|
|
This is a **Godot 4.6** game project (King's Quest IV remake). The project uses:
|
|
- Godot's scene system (`.tscn` files)
|
|
- GDScript (`.gd` files) for game logic
|
|
- NavigationServer2D for pathfinding
|
|
- Custom script builder pattern for game scripts
|
|
|
|
## Tools
|
|
* Helper scripts (python) exist in ./tools
|
|
* a virtual env exists there, too. When you need to run one, you should source ./tools/venv/bin/activate
|
|
|
|
|
|
## Build & Development Commands
|
|
|
|
### Running the Game
|
|
```bash
|
|
# Open in Godot editor (headless for CI)
|
|
godot --headless --path .
|
|
|
|
# Or run the exported game (if built)
|
|
./export/linux/ai-game
|
|
```
|
|
|
|
### Single Test/Scene Testing
|
|
Godot doesn't have a traditional test framework. To test a single scene:
|
|
1. Open the project in Godot
|
|
2. Set the scene as the main scene in project.godot
|
|
3. Run with F5
|
|
|
|
### Code Quality
|
|
```bash
|
|
# Godot's built-in lint (via editor)
|
|
# Tools > Analyze > Check Code
|
|
|
|
# For CI, you can run headless:
|
|
godot --headless --script-check .
|
|
```
|
|
|
|
## Code Style Guidelines
|
|
|
|
*** CRITICAL ****
|
|
When you need to create a new uid, run python make_uid.py, and it will return a unique uid. When creating scenes, at a <scene>.tscn.uid with the contents uid://<uid>
|
|
|
|
### File Organization
|
|
- **Scenes**: `scenes/kq4_XXX_room_name/`
|
|
- **Scripts**: `scenes/kq4_XXX_room_name/kq4_XXX_room_name.gd`
|
|
- **Resources**: Visual assets copied to room folders
|
|
- **Template**: Use `scenes/kq4_placeholder_template/` when creating new rooms
|
|
|
|
### Naming Conventions
|
|
|
|
| Element | Convention | Example |
|
|
|---------|------------|---------|
|
|
| Scenes | `kq4_XXX_description` (snake_case) | `kq4_010_forest_path` |
|
|
| Nodes | PascalCase | `kq4_004_ogres_cottage` |
|
|
| Scripts | snake_case.gd | `kq4_010_forest_path.gd` |
|
|
| Variables | snake_case | `appear_at_node` |
|
|
| Constants | SCREAMING_SNAKE | `const MAX_SPEED = 100` |
|
|
| Classes | PascalCase | `class_name Scene` |
|
|
|
|
### GDScript Patterns
|
|
|
|
#### Class Definition
|
|
```gdscript
|
|
extends Node2D
|
|
class_name MyClass
|
|
|
|
# Use @export for editor-exposed variables
|
|
@export var my_var: int = 0
|
|
@export_file("*.tscn") var target_scene: String
|
|
```
|
|
|
|
#### Type Hints (Required)
|
|
```gdscript
|
|
var player: Node2D
|
|
var score: int = 0
|
|
var name: String = ""
|
|
|
|
func move_to(position: Vector2) -> void:
|
|
pass
|
|
```
|
|
|
|
#### Signals
|
|
```gdscript
|
|
signal my_signal(value: int)
|
|
|
|
# Connecting
|
|
some_node.my_signal.connect(_on_my_signal)
|
|
|
|
# Or in editor-created connections:
|
|
[connection signal="interacted" from="transition_piece" to="." method="_on_interacted"]
|
|
```
|
|
|
|
### Scene Structure (.tscn)
|
|
|
|
#### Header
|
|
```gdscript
|
|
[gd_scene format=3 uid="uid://xxxxx"]
|
|
|
|
[ext_resource type="Script" uid="uid://xxxxx" path="res://path/to/script.gd" id="1_abc"]
|
|
[ext_resource type="Texture2D" uid="uid://xxxxx" path="res://path/to/texture.png" id="2_abc"]
|
|
[ext_resource type="PackedScene" uid="uid://xxxxx" path="res://TransitionPiece.tscn" id="3_abc"]
|
|
```
|
|
|
|
#### Node Hierarchy
|
|
- Use meaningful node names
|
|
- Prefix private nodes with underscore when appropriate
|
|
- Use `unique_id=` for editor-managed nodes
|
|
|
|
#### Transition Pieces
|
|
When adding exits to rooms:
|
|
```gdscript
|
|
[node name="kq4_004_ogres_cottage" parent="." instance=ExtResource("4_abc")]
|
|
position = Vector2(910, -213)
|
|
polygon = PackedVector2Array(...)
|
|
appear_at_node = "kq4_010_forest_path" # Node name in target scene
|
|
target = "uid://xxxxxxxx" # Scene UID
|
|
label = "Ogre's Cottage"
|
|
|
|
[node name="entrance" parent="kq4_004_ogres_cottage" index="0"]
|
|
position = Vector2(163, 598)
|
|
|
|
[node name="exit" parent="kq4_004_ogres_cottage" index="1"]
|
|
position = Vector2(159, 459)
|
|
|
|
[connection signal="interacted" from="kq4_004_ogres_cottage" to="." method="_on_ogres_cottage_interacted"]
|
|
|
|
[editable path="kq4_004_ogres_cottage"]
|
|
```
|
|
|
|
### Error Handling
|
|
- Use `push_error()` for critical issues
|
|
- Use `push_warning()` for non-critical issues
|
|
- Always check `load()` returns with `if resource:` before using
|
|
- Use `@onready` for node references (handles load order)
|
|
|
|
### Room Creation Process
|
|
|
|
1. **Copy template**: `cp -r scenes/kq4_placeholder_template scenes/kq4_XXX_name`
|
|
2. **Rename files**: Update `.gd` and `.tscn` filenames
|
|
3. **Copy visual**: Copy `pic_XXX_visual.png` from decompile
|
|
4. **Update .tscn**:
|
|
- Remove hardcoded UIDs (let Godot generate)
|
|
- Add transition pieces for cardinal directions
|
|
- Set labels to human-readable names
|
|
5. **Update .gd**: Add signal handlers for connected rooms
|
|
6. **Generate UIDs**: Let Godot import, then create `.uid` files
|
|
7. **Wire transitions**: Update `target` fields with real UIDs
|
|
|
|
### Import Files
|
|
|
|
When creating new room textures, create `.import` files:
|
|
```ini
|
|
[remap]
|
|
importer="texture"
|
|
type="CompressedTexture2D"
|
|
uid="uid://xxxxxxxx"
|
|
path="res://.godot/imported/image.png-xxx.ctex"
|
|
|
|
[deps]
|
|
source_file="res://scenes/room_name/image.png"
|
|
dest_files=[...]
|
|
|
|
[params]
|
|
compress/mode=0
|
|
# ... standard Godot texture params
|
|
```
|
|
|
|
### What NOT to Do
|
|
|
|
- **Don't** commit secrets/keys (use `.gitignore`)
|
|
- **Don't** use `print()` for debugging in production (use `push_warning()`)
|
|
- **Don't** hardcode paths - use `res://` relative paths
|
|
- **Don't** modify `project.godot` unless necessary
|
|
- **Don't** delete the `meadow-2` scene (unrelated to KQ4 rooms)
|
|
|
|
### File Extensions to Know
|
|
|
|
| Extension | Purpose |
|
|
|-----------|---------|
|
|
| `.gd` | GDScript source |
|
|
| `.tscn` | Godot scene |
|
|
| `.tres` | Godot resource |
|
|
| `.import` | Import configuration |
|
|
| `.uid` | Resource UID cache |
|
|
| `.md` | Room documentation (in kq4-sierra-decompile) |
|
|
|
|
### Room Specs Reference
|
|
|
|
Room specifications are in `kq4-sierra-decompile/rooms/kq4-XXX-name/`.
|
|
Each room has:
|
|
- `kq4-XXX-name.md` - Technical documentation
|
|
- `pic_XXX_visual.png` - Background image
|
|
- `pic_XXX_control.png` - Control/hitbox areas
|
|
- `pic_XXX_priority.png` - Priority/z-ordering
|
|
|
|
### Key Classes
|
|
|
|
- `Scene` (Scene.gd) - Base room class with navigation
|
|
- `TransitionPiece` (TransitionPiece.gd) - Exit/entry points
|
|
- `GameScript` (GameScript.gd) - Script builder for cutscenes
|
|
- `Ego` (Ego.gd) - Player character
|
|
|
|
## Interaction System (Global)
|
|
|
|
The game uses a **cursor-based action system** with 4 modes that cycle via right-click:
|
|
|
|
| Cursor | Action | Icon | Purpose |
|
|
|--------|--------|------|---------|
|
|
| 0 | Walk | boot_icon.png | Move player to location |
|
|
| 1 | Look | eye_icon.png | Examine objects/room |
|
|
| 2 | Touch | hand_icon.png | Interact with objects |
|
|
| 3 | Talk | speech_icon.png | Talk to characters |
|
|
|
|
### Key Components
|
|
|
|
#### ActionState (Autoload)
|
|
Global singleton (`ActionState.gd`) that tracks the current cursor action:
|
|
```gdscript
|
|
ActionState.current_action # 0-3 (WALK, LOOK, TOUCH, TALK)
|
|
ActionState.Action.WALK # Enum values
|
|
```
|
|
|
|
#### SetPiece Nodes
|
|
Interactive areas in rooms use `SetPiece_.gd` (extends Polygon2D):
|
|
|
|
**Signals emitted:**
|
|
- `interacted` - Generic interaction (takes precedence over cursor actions)
|
|
- `walked` - When walk cursor clicks the piece
|
|
- `looked` - When look cursor clicks the piece
|
|
- `touched` - When touch cursor clicks the piece
|
|
- `talked` - When talk cursor clicks the piece
|
|
|
|
**Priority Rule:** If `interacted` signal has connections, it overrides cursor-specific actions.
|
|
|
|
### Room Setup for Interactions
|
|
|
|
1. **Add SetPiece nodes** to room scene for interactive objects:
|
|
```gdscript
|
|
[node name="pool" type="Polygon2D" parent="."]
|
|
polygon = PackedVector2Array(...)
|
|
script = ExtResource("SetPiece_.gd")
|
|
label = "Pool"
|
|
```
|
|
|
|
2. **Connect signals** in .tscn:
|
|
```gdscript
|
|
[connection signal="looked" from="pool" to="." method="_on_pool_looked"]
|
|
```
|
|
|
|
3. **Implement handlers** in room script:
|
|
```gdscript
|
|
func _on_pool_looked() -> void:
|
|
start_main_script(ScriptBuilder.init(
|
|
ScriptBuilder.say(ego, "The water looks inviting.")
|
|
).build(self, "_on_script_complete"))
|
|
```
|
|
|
|
### Room-Wide Look
|
|
|
|
Clicking empty space with look cursor triggers `_on_room_looked()`:
|
|
```gdscript
|
|
func _on_room_looked() -> void:
|
|
# Override in room script for room description
|
|
start_main_script(ScriptBuilder.init(
|
|
ScriptBuilder.say(ego, "Room description here...")
|
|
).build(self, "_on_script_complete"))
|
|
```
|
|
|
|
### Behavior Rules
|
|
|
|
1. **Precedence**: `interacted` signal always wins if connected
|
|
2. **Fallback**: If no handler for cursor action, do nothing (silent)
|
|
3. **SetPiece detection**: Mouse must be inside polygon
|
|
4. **Unhandled clicks**: Scene._unhandled_input handles empty space
|
|
|
|
### Common Patterns
|
|
|
|
#### Navigation
|
|
```gdscript
|
|
var path = NavigationServer2D.map_get_path(map, start, end, true)
|
|
```
|
|
|
|
#### Pathfinding Movement
|
|
```gdscript
|
|
scene.start_main_script(scene.ScriptBuilder.init(scene.ScriptBuilder.walk_path(ego, path)).build())
|
|
```
|
|
|
|
#### Scene Transitions
|
|
```gdscript
|
|
func _on_exit_interacted() -> void:
|
|
$target_scene.default_script(self)
|
|
```
|
|
|
|
## Branching strategy
|
|
|
|
Do all work in a branch. Squash the branch before merging into master.
|
|
|
|
|
|
<!-- BEGIN BEADS INTEGRATION -->
|
|
## Issue Tracking with bd (beads)
|
|
|
|
**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods.
|
|
|
|
### Why bd?
|
|
|
|
- Dependency-aware: Track blockers and relationships between issues
|
|
- Git-friendly: Dolt-powered version control with native sync
|
|
- Agent-optimized: JSON output, ready work detection, discovered-from links
|
|
- Prevents duplicate tracking systems and confusion
|
|
|
|
### Quick Start
|
|
|
|
**Check for ready work:**
|
|
|
|
```bash
|
|
bd ready --json
|
|
```
|
|
|
|
**Create new issues:**
|
|
|
|
```bash
|
|
bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json
|
|
bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json
|
|
```
|
|
|
|
**Claim and update:**
|
|
|
|
```bash
|
|
bd update <id> --claim --json
|
|
bd update bd-42 --priority 1 --json
|
|
```
|
|
|
|
**Complete work:**
|
|
|
|
```bash
|
|
bd close bd-42 --reason "Completed" --json
|
|
```
|
|
|
|
### Issue Types
|
|
|
|
- `bug` - Something broken
|
|
- `feature` - New functionality
|
|
- `task` - Work item (tests, docs, refactoring)
|
|
- `epic` - Large feature with subtasks
|
|
- `chore` - Maintenance (dependencies, tooling)
|
|
|
|
### Priorities
|
|
|
|
- `0` - Critical (security, data loss, broken builds)
|
|
- `1` - High (major features, important bugs)
|
|
- `2` - Medium (default, nice-to-have)
|
|
- `3` - Low (polish, optimization)
|
|
- `4` - Backlog (future ideas)
|
|
|
|
### Workflow for AI Agents
|
|
|
|
1. **Check ready work**: `bd ready` shows unblocked issues
|
|
2. **Claim your task atomically**: `bd update <id> --claim`
|
|
3. **Work on it**: Implement, test, document
|
|
4. **Discover new work?** Create linked issue:
|
|
- `bd create "Found bug" --description="Details about what was found" -p 1 --deps discovered-from:<parent-id>`
|
|
5. **Complete**: `bd close <id> --reason "Done"`
|
|
|
|
### Auto-Sync
|
|
|
|
bd automatically syncs via Dolt:
|
|
|
|
- Each write auto-commits to Dolt history
|
|
- Use `bd dolt push`/`bd dolt pull` for remote sync
|
|
- No manual export/import needed!
|
|
|
|
### Important Rules
|
|
|
|
- ✅ Use bd for ALL task tracking
|
|
- ✅ Always use `--json` flag for programmatic use
|
|
- ✅ Link discovered work with `discovered-from` dependencies
|
|
- ✅ Check `bd ready` before asking "what should I work on?"
|
|
- ❌ Do NOT create markdown TODO lists
|
|
- ❌ Do NOT use external issue trackers
|
|
- ❌ Do NOT duplicate tracking systems
|
|
|
|
For more details, see README.md and docs/QUICKSTART.md.
|
|
|
|
## Landing the Plane (Session Completion)
|
|
|
|
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
|
|
|
|
**MANDATORY WORKFLOW:**
|
|
|
|
1. **File issues for remaining work** - Create issues for anything that needs follow-up
|
|
2. **Run quality gates** (if code changed) - Tests, linters, builds
|
|
3. **Update issue status** - Close finished work, update in-progress items
|
|
4. **PUSH TO REMOTE** - This is MANDATORY:
|
|
```bash
|
|
git pull --rebase
|
|
bd sync
|
|
git push
|
|
git status # MUST show "up to date with origin"
|
|
```
|
|
5. **Clean up** - Clear stashes, prune remote branches
|
|
6. **Verify** - All changes committed AND pushed
|
|
7. **Hand off** - Provide context for next session
|
|
|
|
**CRITICAL RULES:**
|
|
- Work is NOT complete until `git push` succeeds
|
|
- NEVER stop before pushing - that leaves work stranded locally
|
|
- NEVER say "ready to push when you are" - YOU must push
|
|
- If push fails, resolve and retry until it succeeds
|
|
|
|
<!-- END BEADS INTEGRATION -->
|