# 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 .tscn.uid with the contents 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. ## 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 --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 --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:` 5. **Complete**: `bd close --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