Files
ai-game-2/AGENTS.md
2026-03-09 09:22:21 -07:00

8.6 KiB

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

# 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

# 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

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)

var player: Node2D
var score: int = 0
var name: String = ""

func move_to(position: Vector2) -> void:
    pass

Signals

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

[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:

[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:

[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:

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:
[node name="pool" type="Polygon2D" parent="."]
polygon = PackedVector2Array(...)
script = ExtResource("SetPiece_.gd")
label = "Pool"
  1. Connect signals in .tscn:
[connection signal="looked" from="pool" to="." method="_on_pool_looked"]
  1. Implement handlers in room script:
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():

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

var path = NavigationServer2D.map_get_path(map, start, end, true)

Pathfinding Movement

scene.start_main_script(scene.ScriptBuilder.init(scene.ScriptBuilder.walk_path(ego, path)).build())

Scene Transitions

func _on_exit_interacted() -> void:
    $target_scene.default_script(self)