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.

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:

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

Two conventions keep this decoupled:

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:

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 for the revive path.

Key files