Files
Dawn-Godot/CLAUDE.md
T
2026-06-08 22:50:09 -05:00

130 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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