# GameManager and the Run Lifecycle

`GameManager` (`scripts/core/GameManager.gd`) owns the authoritative run state and everything that happens to it: start, pause, death, carry-on through a lineage, reset for a fresh run. The four pieces worth knowing are the `current_run` dictionary, the pause model, `reset_for_new_run()`, and the revive/lineage path.

## current_run: the run state blob

`current_run` is a plain `Dictionary` holding everything specific to the run in progress. It's the object serialized into a save slot and restored on load. The literal declared in `GameManager` shows its shape and defaults:

| Key | Type | Meaning |
|---|---|---|
| `days_survived` | int | Day count; bumped on each `new_day`. |
| `depth_reached` | int | Deepest tile Y mined this run. |
| `depth_reached_x` | int | X of the deepest tile (set alongside `depth_reached`). |
| `blocks_mined` | int | Lifetime-of-run tiles dug. |
| `gold_collected` | int | Run-cumulative gold *earned* (not spendable balance). |
| `astrolabe_uses` | int | Run-cumulative rituals fired. |
| `stats_flushed` | Dictionary | Per-run baseline of already-flushed lifetime totals (prevents double-counting across a lineage). |
| `total_wall_hp` | int | Sum of built wall HP. |
| `is_dead` | bool | Set true on death; guards double-death. |
| `start_timestamp` | int | Unix time the run began. |
| `cause_of_death` | String | Death cause string. |
| `death_look` | Dictionary | Transient: how the current digger looked when they fell (`cosmetics`, `class`). Written by `_on_player_died`, consumed and erased by `continue_as_relative`. |
| `generation` | int | Lineage generation (starts at 1, increments on carry-on). |
| `villager_name` | String | Current digger's name. |
| `discovery_types_found` | Dictionary | Set of discovery type ids found (completion metric). |
| `discoveries_found` | int | Run-total discovery pockets dug up. |
| `machines_built` | Dictionary | Set of machine types built (completion metric). |
| `challenges` | Array | Active Challenge ids for this run. |
| `class` | String | Chosen champion class (`""` = classless). Owned by `SkillManager`. |
| `skills` | Dictionary | Per-skill XP `{skill_id: xp}`. Owned by `SkillManager`. |
| `cosmetics_loadout` | Dictionary | Per-run equipped look (slot -> item + colour keys), incl. the `class` slot. Owned by `CosmeticManager`. |
| `tutorial_clock_frozen` | bool | Two-phase tutorial gate (freezes Maw + day clock). |
| `village_chain_done` | bool | Staged elder chain complete. |
| `villager_deaths` | int | Cumulative villager deaths (graveyard cross count). |
| `run_uuid` | String | Stable per-run id for idempotent community-stats submission. Minted **once per run** in `reset_for_new_run()` — it does *not* change on a carry-on, so every generation of a line shares it. |
| `peaks` | Dictionary | Per-material lifetime peaks (community stats). |
| `history` | Array | Per-day progression rows `[day, depth, blocks, pop, villager_deaths]`. |

Several keys are absent in older saves and back-filled on load in `_apply_save_data()` (for example `blocks_mined`, `gold_collected`, `run_uuid`, `peaks`, `history`), so the run blob can evolve without a save-version bump for additive fields. `GameManager` maintains these fields live off `EventBus` signals connected in `_ready()` — `tile_dug` bumps `blocks_mined`/`depth_reached`, `wall_built` adds to `total_wall_hp`, `discovery_found` bumps `discoveries_found`, `inventory_added` accumulates `gold_collected`, and `new_day` advances `days_survived` and records the day's history row. The `class`/`skills` keys are read and written through `SkillManager` (`get_class_id`/`set_class_id`, `_skills()`), and `cosmetics_loadout` through `CosmeticManager` — `GameManager` just carries them in the blob.

**Counters are run-cumulative, not per-generation.** `days_survived`, `depth_reached`, `blocks_mined` and `astrolabe_uses` all survive a generation death; only `generation` changes. Anything that wants a single digger's contribution has to difference two snapshots — see The lineage record below.

Alongside `current_run`, `GameManager` holds run-adjacent state as members: `lineage` (an `Array[Dictionary]` of fallen diggers), `appearance_seed` (per-save village skin seed), `crafts_underway`, and the tutorial flags (`tutorial_guided`, `tutorial_opening_active`, `show_tutorial_on_load`).

## Pause

Pausing routes through the scene tree's own pause flag and emits paired signals:

```gdscript
func pause_game() -> void:
    is_paused = true
    get_tree().paused = true
    game_paused.emit()

func resume_game() -> void:
    is_paused = false
    get_tree().paused = false
    game_resumed.emit()
```

Separately, full-screen menus (settings, help) register an *input block* rather than pausing the whole tree. `set_world_input_blocked(source, blocked)` keys entries in `_input_blockers` by source string, and `is_world_input_blocked()` returns true while any entry exists. `World.gd` consults this to ignore world hotkeys and manual camera panning behind an open overlay without freezing simulation.

## Death, lifetime stats, and lineage

When the player dies, `EventBus.player_died` fires (a Maw breach routes through `maw_breached_base`, which emits `player_died(CAUSE_MAW_BREACH)`). `_on_player_died()` guards against a double death, stamps `is_dead` and `cause_of_death`, **snapshots how the digger looked**, flushes lifetime stats, saves, and pauses:

```gdscript
func _on_player_died(cause: String) -> void:
    if current_run.is_dead:
        return
    current_run.is_dead = true
    current_run.cause_of_death = cause
    current_run["death_look"] = {
        "cosmetics": CosmeticManager.get_loadout().duplicate(true),
        "class": SkillManager.get_class_id(),
    }
    _flush_lifetime_stats()
    SaveManager.save_game()
    pause_game()
```

> The look is captured **here** and not in `continue_as_relative()`. `GameOverScreen._carry_on` applies a calling switch *before* calling it, and `switch_calling()` re-points the run's `class` cosmetic slot — so a snapshot taken at carry-on would bury the ancestor wearing their successor's clothes.

`_flush_lifetime_stats()` is delta-based: it adds only the growth since the last flush into the account-lifetime counters on `SaveManager`, using `current_run.stats_flushed` as the moving baseline. Because a lineage keeps accumulating per-run counters across generation deaths, the baseline is what prevents double-counting; each generation death flushes that generation's delta.

### The lineage record

`GameManager.lineage` is an `Array[Dictionary]`, appended to once per fallen digger in `continue_as_relative()`:

| Field | Meaning |
|---|---|
| `name` | The digger's name (falls back to `Villager #N`). |
| `gen` | Their generation. |
| `days` / `depth` / `tiles` / `rituals` | **Snapshots** of the run-cumulative counters at the moment they fell. |
| `cosmetics` / `class` | Their look and calling, from `death_look`. |
| `successor_relation` | Drawn at random from `RELATION_POOL` — which is *not* only family (neighbor, best friend, hunting partner, old rival). A lineage is a chain of succession, not a family tree. |

Because the four counters are snapshots, **a generation's own contribution is the gap between consecutive entries**. `LineageOverlay._rows()` is the only reader that cares; it differences them and reports the living digger against the last entry. `tiles`/`rituals`/`cosmetics`/`class` are additive fields — entries written before they existed simply lack the keys, and no `SAVE_VERSION` bump was needed.

For those older entries there is a second source: `SaveManager.save_score()` runs on **every** death screen and stores the fallen digger's `cosmetics`, `blocks` and `astrolabe_uses` in the profile's TOP RUNS rows. Since `run_uuid` is minted once per run rather than per generation, `run_uuid` + `gen` joins a score row back to its lineage entry exactly. Treat it as backfill, not storage: `_trim_scores()` keeps only `MAX_SCORES = 24` rows across buckets, so a middling generation can be evicted. A digger with neither source renders a plain headstone and "no tally kept".

The overlay itself is documented in [The Talismans HUD](/docs/underroot/the-talismans-hud).

### Carry on: continue_as_relative()

Death is not the end of a run — a successor from the village can take up the dig. `continue_as_relative(queue_tutorial)` is called by both CARRY ON (death screen) and SEND ANOTHER (title screen). The world, Maw, and machines persist; only the digger and a slice of inventory change. The sequence:

1. Record the fallen digger into `lineage` (fields above), then erase `death_look`.
2. Resolve the fallen digger's permanent graveyard stone X via `GameConstants.grave_slot_x(generation)`.
3. `VillageManager.send_off_successor()` — the successor is drawn from the village, one fewer soul at home.
4. Branch on cause of death:
   - **Maw breach** (`cause == CAUSE_MAW_BREACH`): collapse the tunnel, trim 30% of inventory (plus 3 strange root) into a gravestone, `MawController.breach_retreat()`, set food/water to 60, and apply a randomised village population toll (a third to two thirds lost, floored) with `VillageManager.apply_breach_losses()`.
   - **Otherwise**: trim 30% of inventory into a gravestone (if anything was lost) and restore food/water to 100.
5. Safety net: if `MawController.any_front_breached()`, retreat any still-breached front (starvation can win the death race the same frame the Maw is genuinely breached).
6. `SurvivalManager.revive()` — a *partial* revival, not a full `reset()`; food/water were set above.
7. Clear `is_dead`, increment `generation`, assign a fresh `SaveManager.random_god_name()`, reset the journal, mark played, emit `generation_started`, and resume.
8. Teleport the successor to spawn beside the gravestone via `EventBus.player_teleport_requested`.

`generation_started` (step 7) is what halves every skill for the heir — `SkillManager._on_generation_started` snaps each skill down by the `inherit_factor`. If the heir switched calling first (below), that halving reads against the new champion status. Carry-ons never re-run the champion head-start; the heir keeps its inherited (halved) XP.

`queue_tutorial` decides whether the successor re-enters the guided spine: `true` sets `show_tutorial_on_load` (which `TutorialOverlay._ready()` consumes to start the spine directly), `false` emits `tutorial_skip_requested`. The death screen's CARRY ON passes the default `false` — the tutorial state is already resolved in-session. The title screen's SEND ANOTHER rebuilds the world from scratch, so it passes `not SaveManager.has_played_before()`: a new player who fell mid-tutorial resumes guidance, a veteran never does. Queueing it unconditionally used to strand veterans — the spine's gather beats wait on `rock_harvested`, and surface rocks never respawn, so on an established world the beat could not complete. The rock beats now also skip themselves when no unstripped rock remains.

> Carry-on deliberately does **not** call `reset_for_new_run()`. The world and its progress survive; the lineage continues in the same slot.

### Switch calling: switch_calling()

At the CARRY ON screen the heir may change champion class before continuing (a "Change calling" button opens the class picker in switch mode; classless lineages don't see it). The pick runs `switch_calling(new_class_id)` **before** `continue_as_relative`, so the death-halving in step 7 uses the new champion status. It is a no-op when `new_class_id` equals the current class. The sequence:

1. Look up the old calling's landmark type (`_LANDMARK_TYPE[old_class]`) and remove every placed machine of that type from `MachineManager` (toast: "The old calling's landmark is left behind."). The new calling's landmark must be re-earned and re-built.
2. `SkillManager.set_class_id(new_class_id)` — moves the champion. The new home skill can now pass node 5 (toward 7); the old one caps at 5. Skill XP itself is untouched here (the halving happens later in the carry-on).
3. `CosmeticManager.reseed_class_signature()` — re-point only the run's `class` cosmetic slot to the new calling's signature; every other slot is left alone.
4. `CraftingUnlockManager._check_unlocks()` — re-gate recipes so the new calling's landmark blueprint appears (and the old one's disappears) per the champion-skill node gate.

See [Skill Tree and Classes](/docs/underroot/skill-tree-and-classes).

## reset_for_new_run()

A true new run wipes the slot and returns every shared singleton to a clean baseline. `reset_for_new_run()` resumes the tree, clears input blockers and in-flight crafts, deletes the save, rebuilds `current_run` from defaults (assigning a fresh `run_uuid` via `CommunityStats.uuid4()`, pulling in any pending `ChallengeManager`/`HarrowManager` selection, and applying `SkillManager.pending_class` as the chosen class), then resets each subsystem in order:

```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()
CosmeticManager.reset_run_equipment()   # bares headwear/extra/form, seeds the class signature
MachineManager.reset()
WorldManager.reset_world(randi())
...
SkillManager.reset()
SkillManager.seed_champion_headstart()   # champion starts at home-skill node 1 (+4% from turn one)
...
ChallengeManager.apply_run_start()   # applied last so challenge start conditions win
```

The `reset()`/`clear()` sequence and the reasoning behind the ordering are detailed in [The Autoload Model](/docs/underroot/the-autoload-model#the-reset-contract). The key contract: challenge starting conditions are applied last so they override the normal manager resets.

`SkillManager.reset()` wipes per-run XP, then `seed_champion_headstart()` pre-fills the chosen champion's home skill to node 1 — a readable **+4% from turn one**. It's a new-game-only step: a classless run is a no-op, and lineage carry-ons (which go through `continue_as_relative`, not this function) are never re-seeded, so an heir keeps its inherited, halved XP. See [Skill Tree and Classes](/docs/underroot/skill-tree-and-classes).

## Loading a run

`trigger_load()` reads the slot via `SaveManager.load_game()` and, if non-empty, hands it to `_apply_save_data()`. That function is defensive: it drops any top-level section whose JSON type does not match what its loader expects (so a hand-edited or imported save degrades to "missing" rather than crashing mid-load), restores `current_run` from the `run` section, back-fills newly added fields, and then hands each section to its owning singleton's loader (`Inventory.load_save_data`, `ToolState.load_save_data`, `SurvivalManager.load_save_data`, `MawController.load_save_data`, and so on). It finishes with `MawController.refresh_threat_state()` so the UI opens in the correct threat colour.

## Key files

- `scripts/core/GameManager.gd` — `current_run`, pause, death, lineage, `switch_calling()`, `reset_for_new_run()`, load.
- `scripts/core/GameConstants.gd` — `STARTER_*` stores and `grave_slot_x()`.
- `scripts/core/SaveManager.gd` — slot persistence, lifetime-stat counters, TOP RUNS rows.
- `scripts/core/EventBus.gd` — the signals that keep `current_run` current.
- `scenes/ui/LineageOverlay.gd` — the only reader that differences the lineage snapshots.

## Related

- [The Autoload Model](/docs/underroot/the-autoload-model)
- [EventBus and the Signal Convention](/docs/underroot/eventbus-and-the-signal-convention)
- [The Talismans HUD](/docs/underroot/the-talismans-hud)
- [Skill Tree and Classes](/docs/underroot/skill-tree-and-classes)
- [Save System and Migration](/docs/underroot/save-system-and-migration)
- [World Coordinate System and Camera](/docs/underroot/world-coordinate-system-and-camera)
- [Challenges](/docs/underroot/challenges)