# Architecture

The prototype deliberately uses a shallow scene structure. `GameState` is the only autoload because it owns data that must survive scene transitions. The restaurant and dungeon own their local actors and coordinate only scene-specific rules.

## Runtime flow

`Restaurant → Dungeon → Enemy death → IngredientPickup → GameState inventory → active cooking → physical Dish → player carries plate → Customer → coins → upgrade → upgraded Player stats`

Published ingredient, attack-pattern, enemy, and recipe definitions live in `data/game_data_master.json`. `GameState` asks `DataCatalog` to load and validate that file before it loads player progress or any gameplay scene starts. The loader converts web-friendly color strings into Godot `Color` values and keeps complete built-in defaults as a safe fallback. Upgrades still live directly in `DataCatalog` until their editor tab is added. Runtime systems use stable IDs, making visuals and scenes replaceable without changing saved data.

`tools/game-data-manager/` is deliberately outside the runtime layer. Its PHP endpoint autosaves a validated draft, creates a timestamped backup on publication, and writes only the fixed master path. The browser UI edits monsters, reusable attack patterns, dishes, and materials and validates all cross-references. Godot independently validates the published result, so a malformed manual edit cannot crash startup.

`Player` discovers nearby objects through the `interactable` group; interactive props forward their simple action ID to the current `level`. This avoids deep node paths while keeping interactions easy to inspect.

Combat uses physics layers: player `1`, enemies/walls `2`, pickups `4`. Player melee performs a broad physics query and then filters targets by range and facing dot product, producing a true forward fan rather than a circular hit. Damage APIs accept direction and force so both player and enemy hits apply decaying knockback.

Enemies test both distance and facing angle before detecting the player. Their undetected and detected movement behaviors are independently selected from linear, patrol, big leap, small hop, and static motion; detected movement is oriented by chase, avoid, or ignore response.

Attack geometry lives in reusable patterns rather than enemy records. `direct_hit` resolves contact and knockback immediately. `burst` captures a fixed origin/direction and treats 360° as a circle. `sticky` updates its origin from the hero until impact. `swipe` rotates its sector continuously during `active_duration` and resolves hits on `tick_interval`. Every warning or active state has an explicit finish/interruption path, so visuals cannot remain attached after resolution. Receiving knockback cancels any state and restores 65% of cooldown.

The dungeon does not contain a hardcoded enemy-ID list. It interleaves every catalog entry according to its `spawn_count` and assigns deterministic arena slots, so publishing a new enemy definition with a positive count makes the generic `Enemy` shell appear automatically on the next run.

Enemy bodies and material pickups use the catalog's `shape` field. `ShapeFactory` maps the supported IDs (`circle`, `square`, `triangle`, `diamond`, `hexagon`, and `oval`) to both draw polygons and matching Godot physics shapes. This keeps browser previews, runtime silhouettes, and collisions based on one setting.

On dungeon entry, the level snapshots the persistent ingredient inventory. Collected drops appear immediately in the HUD. Being knocked out restores the snapshot, while stamina exhaustion ends the round and keeps every collected material. Using the exit also commits the current inventory. Stamina is activity-driven: idle costs nothing, walking and running drain at different rates, and attacks/dashes spend an immediate amount. The `stamina` upgrade effect increases the run's maximum stamina.

Player balance and enemy movement are data-driven. `active_player_config` selects a record from `player_configs`, applied by `player.gd` when the player is instantiated and by `dungeon.gd` for starting stamina. Enemy detected/undetected movement fields reference reusable `movement_patterns`; `enemy.gd` interprets their type, speed multipliers, hop timing, patrol direction timing, and idle chance.

The HUD observes `GameState.state_changed`, while short feedback uses `toast_requested`. Scene-specific objective text remains owned by each level.

The single `action` input is routed by the active level: dungeon actions interact with a nearby exit or attack otherwise, while restaurant actions only interact. Restaurant cooking consumes ingredients when a recipe card is selected, but its timer advances only while the player holds Action inside the cooking-station radius. Completion spawns a `Dish` in world space. The player owns at most one carried dish; Action toggles pickup/drop unless a nearby customer can receive it. Customers inspect the carried dish ID and only consume the matching physical plate.

`second_table` and `third_table` are persistent ordered upgrades. The restaurant listens for state changes and instantiates the additional customer scenes immediately after purchase. Display mode is also persisted in `GameState`; the shared HUD owns the resolution menu so it remains available in both levels.
