# Dawn Godot — Claude Code Guide ## Project Godot 4.4 RPG prototype, GDScript only. Viewport 480×270 (scaled 3× to 1440×810), GL Compatibility renderer. --- ## Code Style ### Naming - **camelCase** for variables and functions: `fighterMap`, `startBattle()`, `getFullParty()` - **PascalCase** for class names, enums, and enum values: `BattleFighter`, `FighterTeam`, `ALLY` - **SCREAMING_SNAKE_CASE** for constants and static data: `CUTSCENE_CONTINUE`, `ITEM_DATA`, `PARTY_JOHN` - **No** trailing underscores on private methods — use a leading underscore only for internal helpers that shouldn't be called externally: `_onConversationInteract()`, `_applyGravity()` ### Formatting - 2-space indent (not 4, not tabs) - Type annotations on all variable declarations and function signatures: `var health:int`, `func damage(amount:int, crit:bool) -> void:` - No space between variable name and type: `var foo:int` not `var foo : int` - Blank lines between logical sections within a file; comment headers (`# Health`, `# Signals`) to label groups ### General Rules - `assert()` for invariants and preconditions — prefer it over silent failures - `params:Dictionary` pattern for multi-argument constructors/callables; access with `.get('key', default)` or `.has('key')` guards - `match` for enum dispatch; `if/elif` chains for non-exhaustive checks - `continue` / early `return` to reduce nesting rather than deep else-branches - Avoid long inline lambdas; extract named static functions where logic is non-trivial --- ## Architecture ### Singletons (Autoloads) All globally accessible by their handle. Never instantiate these — access via the global name. | Handle | Role | |---|---| | `SCENE` | Current scene state; call `SCENE.setScene(SceneSingleton.SceneType.X)` to switch | | `TRANSITION` | Fade in/out; `TRANSITION.fade(FadeType, duration, color)` | | `BATTLE` | Battle state and fighter map | | `PARTY` | Party members (`PARTY.getFullParty()`) and `PARTY.BACKPACK` inventory | | `OVERWORLD` | Map switching with threaded loading | | `COOKING` | Cooking mini-game lifecycle | | `SAVE` | Persistence (stub) | | `QUEST` | Quest management (stub) | | `CUTSCENE` | Cutscene global (stub) | | `UI` | Root UI accessor — `UI.TEXTBOX`, `UI.DEBUG_MENU` | ### Scene Graph ``` RootScene (Node3D) └─ overworld / battle / cooking / initial ← one shown at a time RootUI (Control, always visible) └─ VNTextbox, DebugMenu ``` `RootScene` listens to `SCENE.sceneChanged` and shows/hides the appropriate sub-tree. ### Cutscene / Event Queue `Cutscene` is the universal sequencing engine. It holds an `Array[Dictionary]` queue; each entry has a `"function": Callable` plus arbitrary data keys. **Return codes from a callable:** - `Cutscene.CUTSCENE_CONTINUE` — advance to next item - `Cutscene.CUTSCENE_END` — stop the cutscene - An integer index — jump to that position **Position constants when adding:** - `Cutscene.CUTSCENE_ADD_END` — append (default) - `Cutscene.CUTSCENE_ADD_NEXT` — insert immediately after current **Callable pattern** — every action class exposes a pair: ```gdscript # The actual callable (static, takes params:Dictionary, returns int) static func myCallable(params:Dictionary) -> int: ... return Cutscene.CUTSCENE_CONTINUE # Factory that builds the dictionary for addCallable() static func getMyCallable(arg) -> Dictionary: return { "function": myCallable, "myArg": arg } ``` ### Data Registry Pattern Static registries (Item, Recipe) follow this pattern: 1. `enum Id { NULL, ... }` — typed identifier 2. `static var DATA:Array = []` — indexed by Id value 3. `static func define(params) -> Dictionary` — called at class load to populate `DATA` 4. `static var FOO = define({...})` — registers the entry as a static var 5. `static func get*(id) -> *` — typed accessors ### `_init(params:Dictionary)` Pattern Non-Node data classes (`BattleFighter`, `BattleDecision`, `ItemStack`, etc.) use a single `params` dictionary constructor with `.get('key', default)` for optional fields. --- ## System Conventions ### Battle - Fighters live in `BATTLE.fighterMap: Dictionary[BattlePosition, BattleFighter]` - `BattleFighter` is pure data (no Node); `BattleFighterScene` is the 3D visual - Battle flow is driven by `BattleCutsceneAction.playerDecisionCallable` which loops via `CUTSCENE_ADD_END` - New moves go in [battle/fighter/BattleMove.gd](battle/fighter/BattleMove.gd) as static presets; add corresponding `perform()` logic in [battle/action/BattleMove.gd](battle/action/BattleMove.gd) ### Entities (Overworld) - All interactable world objects extend `Entity` (CharacterBody3D) - Interaction type is set via `@export var interactType:InteractType` - Interaction routing lives in `EntityInteractableArea.onInteract()` — add new `InteractType` values there ### Items - Register new items in [item/Item.gd](item/Item.gd) — add to `Id` enum and call `itemDefine()` - Item ID order in the enum must match insertion order into `ITEM_DATA` ### UI - `UI.TEXTBOX.setTextAndWait(text)` — show dialogue and await player dismiss (use `await`) - Movement is blocked automatically when `UI.TEXTBOX` is visible (`EntityMovement._canMove()` checks this) - Menus extend `ClosableMenu` for open/close/toggle + `closed`/`opened` signals --- ## What's Incomplete / Stub - `Save.gd` — no actual serialization yet - `Quest.gd` — empty singleton - `CutsceneSingleton.gd` — minimal stub - `BattleFighter.getAIDecision()` — always returns `null` - `BattleDecision.execute()` — stub - `BattleItem.perform()` — stub - `CookingScene.tscn` — placeholder UI only - `Pause.gd` — commented-out logic