# The Autoload Model

Underroot runs almost all of its game state through Godot autoload singletons. Below: every autoload in load order with a one-line role, and the `reset()` contract that returns the shared singletons to a clean state for a new run.

## What the autoloads are

Autoloads are registered in the `[autoload]` block of `project.godot`. Each entry is a script attached as a globally accessible singleton node; the `*` prefix means the singleton is a node added to the scene tree root. Because they are nodes at the tree root, they persist across scene changes (`IntroStory` to `TitleScreen` to `Main`) and hold the run's authoritative state while scenes come and go.

Load order matters: an autoload listed later can safely reference one listed earlier during `_ready()`, but not the reverse. For example `EventBus` loads before every system that emits on it, and `DataRegistry` loads before the managers that read material and recipe data at startup.

## All autoloads (load order)

The 36 autoloads, in the exact order they appear in `project.godot`:

| # | Name | Script | Role |
|---|---|---|---|
| 1 | `FontSetup` | `scripts/core/FontSetup.gd` | Attaches embedded fallback fonts (emoji subset, etc.) to the default font — the Web export has no OS fonts. |
| 2 | `PerfSetup` | `scripts/core/PerfSetup.gd` | Applies Low Performance Mode at startup, before any renderer reads `PerformanceMode.enabled`. |
| 3 | `TimeManager` | `scripts/core/TimeManager.gd` | Day cycle (`day_time` 0–1, 360 s/day) and unix time. |
| 4 | `AudioManager` | `scripts/core/AudioManager.gd` | Music play/stop/volume; songbook and surge-track playback. Also owns what a Black Hollow expedition restores on exit — the town rotation in-run, the title or death theme from the title screen. |
| 5 | `ToolState` | `scripts/core/ToolState.gd` | Equipped tools, durability, owned tools, processing machines. |
| 6 | `EventBus` | `scripts/core/EventBus.gd` | All cross-system signals. Nothing else lives here. |
| 7 | `DataRegistry` | `scripts/resources/DataRegistry.gd` | Loads every `data/*.json` at startup; material/tool/recipe/machine lookups. |
| 8 | `SaveManager` | `scripts/core/SaveManager.gd` | 12 save slots, `config.json`, save versioning and migration. |
| 9 | `Inventory` | `scripts/resources/Inventory.gd` | `resources` dict; `add` / `remove` / `get_amount` / `can_craft`. |
| 10 | `TaskQueue` | `scripts/idle/TaskQueue.gd` | Offline/idle task queue. |
| 11 | `WorldManager` | `scripts/world/WorldManager.gd` | Tile grid, walls, rooted walls, gravestone, surface connectivity. |
| 12 | `BuildManager` | `scripts/building/BuildManager.gd` | Wall placement and stacking logic (1 raw unit per layer). |
| 13 | `MawAdaptation` | `scripts/maw/MawAdaptation.gd` | Per-material familiarity multipliers, credited per unit eaten. |
| 14 | `MawController` | `scripts/maw/MawController.gd` | Maw fronts, chew rate, pressure escalation, breach detection. |
| 15 | `ForestManager` | `scripts/forest/ForestManager.gd` | Tree/bush/pond/boulder state. |
| 16 | `SurvivalManager` | `scripts/village/SurvivalManager.gd` | food/water/stored/well drain logic and idle multiplier. |
| 17 | `VillageManager` | `scripts/village/VillageManager.gd` | Population, support metrics, buildings, daily tick, villager visits. |
| 18 | `ProjectManager` | `scripts/village/ProjectManager.gd` | Villager task spawn/fulfil/expiry (`data/projects.json`). |
| 19 | `OfflineSimulator` | `scripts/idle/OfflineSimulator.gd` | Catch-up simulation on resume (tasks, survival, fuel, Maw, machines). |
| 20 | `MachineManager` | `scripts/machines/MachineManager.gd` | Active machines dict + spatial footprint index. |
| 21 | `GameManager` | `scripts/core/GameManager.gd` | Run state (`current_run`), pause, revive/lineage. |
| 22 | `SkillManager` | `scripts/core/SkillManager.gd` | Per-run skill XP + chosen class; credits use-based XP off action signals, applies back-loaded % bonuses (via `SkillMath`), halves every skill on lineage death. State rides `current_run` (`skills`/`class`). |
| 23 | `SkillNotify` | `scripts/core/SkillNotify.gd` | Turns `EventBus.skill_leveled` into a player toast (keeps `SkillManager` UI-free). |
| 24 | `CosmeticManager` | `scripts/core/CosmeticManager.gd` | Account-wide cosmetic ownership + equipped loadout; all unlocks route through `unlock()`. Owns the dedicated `class` cosmetic slot (auto-seeded per calling; node-7 prestige grant). |
| 25 | `RockManager` | `scripts/world/RockManager.gd` | Surface rock depletion state. |
| 26 | `DiscoveryManager` | `scripts/world/DiscoveryManager.gd` | Underground discovery pockets. |
| 27 | `CraftingUnlockManager` | `scripts/core/CraftingUnlockManager.gd` | Unlocks recipes once all input materials have been seen; also gates class-landmark recipes on champion class + skill node, and the Hollow Crucible on recovered schematics (`unlock_requires_schematics`). |
| 28 | `SpecialistManager` | `scripts/village/SpecialistManager.gd` | Four Astrolabe specialists; Artificer gated by village population. |
| 29 | `AstrolabeManager` | `scripts/core/AstrolabeManager.gd` | Ritual activation, permanent Maw escalation, exotic seeding. |
| 30 | `WeatherManager` | `scripts/core/WeatherManager.gd` | Rain/storm state machine (storms kill villagers and destroy stock); suspends during an in-run Black Hollow expedition. |
| 31 | `FireworksLayer` | `scenes/ui/FireworksLayer.gd` | Milestone fireworks overlay (layer 20). |
| 32 | `SessionTelemetry` | `scripts/core/SessionTelemetry.gd` | Local-only playtest telemetry — JSONL per session in `user://telemetry/`; nothing transmitted. |
| 33 | `CodeManager` | `scripts/core/CodeManager.gd` | Code redemption; `codes.json` keyed by SHA-256 of the uppercase code; one-time tracking in `config.json`. |
| 34 | `ChallengeManager` | `scripts/core/ChallengeManager.gd` | Opt-in run modifiers (Two Fronts, Black Rot, …); active set in `current_run.challenges`; effect multipliers. |
| 35 | `HarrowManager` | `scripts/core/HarrowManager.gd` | The Artificer's Harrow — custom-challenge designer (7th Challenge card); catalog, unlock gate, config hygiene. |
| 36 | `BlackHollowManager` | `scripts/core/BlackHollowManager.gd` | Wren's Expeditions: Black Hollow — expedition availability/cooldown, depth-unlock, entry-fee, depth-band/multiplier helpers, the standalone story mode's route chain (`is_story_unlocked` / `story_advance_depth` / `advance_story_chain`), and `resolve_expedition(haul)` (credits rewards + advances village time). The mini-game overlays talk only to this + `DataRegistry`. |

> `HarrowManager` is a real autoload (registered in `project.godot`) but is absent from the architecture guide's autoload table. Effect math for a Harrow config lives in `ChallengeManager`; `HarrowManager` owns the dial catalog and encode/decode.

The skill/class system spans two of these autoloads plus one non-autoload helper: `SkillManager` is the numeric spine (XP crediting, `get_mult`, handicaps, inheritance), `SkillNotify` is its UI adapter, and `SkillMath` (below) is the pure math. See [Skill Tree and Classes](/docs/underroot/skill-tree-and-classes).

## The singleton pattern

Every autoload script `extends Node`. State lives in plain member variables (`current_run`, `resources`, `walls`, `fronts`, …), and behaviour is exposed as public methods. Callers reference the singleton by its registered name from anywhere:

```gdscript
Inventory.get_amount("iron")            # → int
DataRegistry.get_material(mat_id)       # → Dictionary ({} on miss)
MawController.refresh_threat_state()
```

Two conventions keep this decoupled:

- **Cross-system events go through `EventBus`.** Managers emit signals rather than calling each other's methods directly. See [EventBus and the Signal Convention](/docs/underroot/eventbus-and-the-signal-convention).
- **Data lookups go through `DataRegistry`** and return `{}` on a miss, so callers always use `.get("key", default)`.

Note that some frequently referenced globals are **not** autoloads. `GameConstants` (`scripts/core/GameConstants.gd`), `SkillMath` (`scripts/core/SkillMath.gd` — pure static leveling math for the skill system; takes its config explicitly so `tools/smoke_test.gd` can exercise it deterministically), `CommunityStats` (`scripts/core/CommunityStats.gd`), `UITheme` (`scripts/ui/UITheme.gd`), `HudShell` (`scripts/ui/HudShell.gd` — HUD-skin state for the two-skin HUD), and `LayerNav` (`scripts/ui/LayerNav.gd`) are `class_name` types used statically — they are not in the `[autoload]` block and hold no per-run state.

## The reset() contract

A fresh run must return the shared singletons to a clean baseline without reloading the whole game. `GameManager.reset_for_new_run()` is the single orchestration point: it rebuilds `current_run` and then calls each subsystem's reset entry point in dependency order.

The architecture guide highlights three singletons that expose an explicit `reset()` and should be reset through it rather than by poking `_`-prefixed fields:

| Singleton | Reset entry point |
|---|---|
| `SurvivalManager` | `reset(p_food, p_water, p_stored_food = 0.0, p_stored_water = 0.0)` |
| `MawController` | `reset()` |
| `ToolState` | `reset()` |

`SurvivalManager.reset()` takes the starter stores explicitly; `reset_for_new_run()` passes the canonical `GameConstants.STARTER_*` values:

```gdscript
SurvivalManager.reset(GameConstants.STARTER_FOOD, GameConstants.STARTER_WATER,
        GameConstants.STARTER_STORED_FOOD, GameConstants.STARTER_STORED_WATER)
MawController.reset()
MawAdaptation.familiarity.clear()
Inventory.resources.clear()
ToolState.reset()
```

The same `reset_for_new_run()` continues through the rest of the world: `MachineManager.reset()`, `WorldManager.reset_world(randi())`, `RockManager.generate(randi())`, `ForestManager.reset()`, `VillageManager.reset_village()`, `ProjectManager.reset()`, `TaskQueue.clear()`, `DiscoveryManager.reset()`, `CraftingUnlockManager.reset()`, `AstrolabeManager.reset()`, and finally `ChallengeManager.apply_run_start()` (applied last so challenge starting conditions win over the normal resets). Several other autoloads expose their own `reset()` for this purpose, including `WeatherManager`, `MachineManager`, `ForestManager`, `ProjectManager`, `AstrolabeManager`, `CraftingUnlockManager`, and `DiscoveryManager`. `SkillManager.reset()` is a no-op — the run's `skills`/`class` are rebuilt with `current_run` by `GameManager`, and the chosen class is applied from `SkillManager.pending_class`. `BlackHollowManager` needs no `reset()`: its only per-run state is `current_run.black_hollow_cooldown`, rebuilt with `current_run`; everything else it owns — the chronicle, schematic count, best depths, and the story mode's chain and best scores — is account-level in `config.json` and deliberately survives a new run.

> A **lineage continuation** (carry-on after a death) is not a new run: it keeps the same world and never calls `reset_for_new_run()`. See [GameManager and the Run Lifecycle](/docs/underroot/gamemanager-and-the-run-lifecycle) for the revive path.

## Key files

- `project.godot` — the `[autoload]` block; authoritative load order.
- `scripts/core/GameManager.gd` — owns `reset_for_new_run()` and the run lifecycle.
- `scripts/core/EventBus.gd` — the signal hub every autoload emits through.
- `scripts/resources/DataRegistry.gd` — startup data load consumed by the manager autoloads.
- `scripts/core/GameConstants.gd` — non-autoload constants (`STARTER_*`, `TILE_SIZE`) used during reset.

## Related

- [EventBus and the Signal Convention](/docs/underroot/eventbus-and-the-signal-convention)
- [GameManager and the Run Lifecycle](/docs/underroot/gamemanager-and-the-run-lifecycle)
- [Skill Tree and Classes](/docs/underroot/skill-tree-and-classes)
- [Black Hollow](/docs/underroot/black-hollow)
- [World Coordinate System and Camera](/docs/underroot/world-coordinate-system-and-camera)
- [The Scene Tree](/docs/underroot/the-scene-tree)
- [DataRegistry and the Data Files](/docs/underroot/dataregistry-and-the-data-files)
