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

5.5 KiB
Raw Blame History

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:

# 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 as static presets; add corresponding perform() logic in 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 — 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