# Forest and Surface

Aboveground and near-surface content: the forest ecosystem (trees, berry bushes, the pond, boulders) under `ForestManager`, the surface stone boulders `RockManager` scatters, and the discovery pockets `DiscoveryManager` resolves underground. These are the renewable and one-shot resources the player harvests outside of tile digging.

The forest sits left of world x 0 (negative X); underground is positive Y. All harvest yields flow through `Inventory`, `SurvivalManager`, and `EventBus.mined_yield` for floating-text feedback.

## ForestManager

`scripts/forest/ForestManager.gd` owns the forest ecosystem. It holds two dictionaries keyed by pixel position and a set of scalar pond/boulder amounts:

```gdscript
var nodes:  Dictionary = {}  # Vector2i(pixel_x, 0) -> ForestNode  (trees)
var bushes: Dictionary = {}  # Vector2i(pixel_x, 0) -> BerryBush
var pond_water: float = POND_MAX_WATER
```

`_ready` calls `_generate_forest()` and `_generate_bushes()`. `_process` runs the regeneration tick for bushes, trees, and the pond every frame.

### Trees

Trees are generated left of `TREE_RIGHT` (-820) out to `FOREST_LEFT` (-3200), with varied spacing (tight clusters, normal spread, open clearings) and a drifting height cluster so patches share a "feel". A tree's wood capacity scales with visual height via `wood_capacity(height)` — far-forest giants hold ~20–25 wood, village-edge trees ~8–9, rewarding the deeper trek. Generation skips positions near a boulder (`_near_boulder`) or inside the graveyard clearing (`_in_graveyard`).

Harvest and regen:

- `get_tree_near(world_x, radius)` returns the nearest non-depleted tree.
- `harvest_one(node)` removes one wood, marks the tree `depleted` at zero, adds `TerrainDigging.roll_yield("wood")` to inventory, and emits `tree_harvested` and `mined_yield`.
- Regen adds one wood per `TREE_REGEN_INTERVAL` (180 s); a fully depleted tree waits an extra `TREE_DORMANT_PERIOD` (300 s) before the first regrowth.

### Berry bushes

Bushes are generated from `FOREST_LEFT` to `FOREST_RIGHT` (-585) with varied spacing. `_generate_bushes` always writes a guaranteed bush at `TUTORIAL_BUSH_X` (-840), overwriting any random bush on that tile, so the forest tutorial beat is always reachable.

- `get_bush_near(world_x, radius)` returns the nearest non-depleted bush.
- `harvest_bush(bush)` takes `ChallengeManager.berry_harvest_amount()` berries (capped by stock), adds them as food via `SurvivalManager.add_supplies`, and sets a `DORMANT_PERIOD` rest timer when stripped.
- Regen (in `_process`) accumulates `BerryBush.REGEN_RATE` per second up to `MAX_BERRIES`, but only after any `dormant_timer` has elapsed.

### Pond

A single pond at `POND_X` (-1400) holds up to `POND_MAX_WATER` (100). `harvest_pond(amount)` returns the collected water (clamped to what remains) and emits `pond_changed`; the caller adds it to supplies. Regen adds `POND_REGEN_RATE * ChallengeManager.pond_regen_mult()` per second.

### Boulders

Three forest boulders sit at `BOULDER_POSITIONS` (-1500, -900, -2930) with max stone amounts `[50, 30, 42]`. Each boulder's stone is a separate variable (`boulder_stone`, `boulder_stone_2`, `boulder_stone_3`); index 0 maps to the legacy save key `boulder_stone`. Accessors `boulder_stone_at(idx)` / `_set_boulder_stone(idx, val)` bridge the index-based API to those fields.

- `get_boulder_near(world_x)` returns the X of the nearest harvestable boulder, or `NAN`.
- `harvest_boulder(bx)` removes 1–3 stone, adds it to inventory, and emits `boulder_harvested` / `mined_yield`.
- `devour_boulder(idx)` empties a boulder and flags `boulders_devoured[idx]` when a Maw front (the Two Fronts west crawl) grinds over it, so `ForestDisplay` stops drawing it.

### Determinism, reset, and load

Both generators seed their RNG with a fixed constant plus `WorldManager.world_seed`, so each fresh run's layout is unique but reproducible. The autoload-boot pass runs with seed 0 and is discarded by either `load_from_save` or `reset()`. `reset()` regenerates the forest and bushes and restores full boulder stock. `load_from_save` / `load_bushes_from_save` restore serialized state, skipping any legacy tree or bush that now falls inside the graveyard clearing.

## ForestNode and BerryBush

`scripts/forest/ForestNode.gd` (`class_name ForestNode`, `extends RefCounted`) is a single tree: `position` (pixel coords, y=0), `wood_amount`, `gather_time` (3.0 s default), visual `height`, `depleted`, and `regen_timer`. `create`, `to_dict`, and `from_dict` handle construction and persistence.

`scripts/forest/BerryBush.gd` (`class_name BerryBush`) is a single bush with `berry_amount`, a fractional `regen_accum` (not saved), and a `dormant_timer`. Constants define its economy:

| Constant | Value | Meaning |
|---|---|---|
| `MAX_BERRIES` | 20 | Full stock. |
| `HARVEST_MIN` / `HARVEST_MAX` | 1 / 5 | Handful range per pick. |
| `GATHER_TIME` | 1.5 | Seconds per harvest. |
| `REGEN_RATE` | 0.05 | Berries/sec — full in ~6.7 min. |
| `DORMANT_PERIOD` | 360.0 | Rest after full depletion (one day). |

`from_dict` clamps `berry_amount` to `MAX_BERRIES` so saves from before the 100 -> 20 capacity retune do not carry jumbo stocks.

## RockManager: surface stone

`scripts/world/RockManager.gd` scatters `ROCK_COUNT_MIN`..`ROCK_COUNT_MAX` (6–9) surface boulders between `ROCK_ZONE_LEFT` (-720) and `ROCK_ZONE_RIGHT` (210), enough stone to bootstrap a pickaxe before underground mining. Positions are randomised each run with a minimum separation, and each rock carries `ROCK_AMOUNT_MIN`..`ROCK_AMOUNT_MAX` (3–6) stone.

- `generate(rng_seed)` runs on `_ready` and again on `reset_for_new_run`.
- `restore_from_save(rock_data)` rebuilds rocks from saved `{x, amount, depleted}` dicts.
- `get_rock_near(world_x, radius)` returns the nearest non-depleted rock.
- `get_gather_time(rock, tool_id)` divides the rock's `gather_time` by the tool's `dig_speed_multiplier`.
- `harvest_one(rock)` adds one stone to inventory, marks the rock `depleted` at zero (emitting `rock_depleted`), and emits `rock_harvested` and `mined_yield`.

`SurfaceRock` (`scripts/world/SurfaceRock.gd`) is the per-rock record consumed by these methods; `RockManager` distinguishes these surface rocks from `ForestManager`'s three larger forest boulders.

## DiscoveryManager: underground pockets

`scripts/world/DiscoveryManager.gd` resolves discovery pockets — water, mushrooms, ore caches, treasure, and hazards — when a tile is dug. Definitions live in `data/discoveries.json`; each entry has `layers`, per-layer `layer_chances` (falling back to a flat `chance`), a `reward_type`, and a `reward`.

### Deterministic per-tile resolution

Every roll hashes the tile position with `_hash_salt()`, which mixes `WorldManager.world_seed` with `AstrolabeManager.get_uses_count() * _RESEED_EPOCH_PRIME`. So discoveries are stable per tile within a run, the world seed scatters them afresh each new run, and each Astrolabe ritual reshuffles them entirely. Legacy saves that never fired the ritual load with seed 0 and reproduce their original layout.

`get_tile_discovery(pos)` returns the discovery id at a tile (or `""`), backed by a `_disc_cache` that both dynamic inputs invalidate — the ritual hook and world-seed changes clear it. `DiscoveryLayer` queries this for every visible cell, so the cache matters; that render layer also caps its animated-marker count to an FPS-adaptive budget (the markers nearest the camera get the full pulsing/glowing art, the rest render as cheap static dots), so a dense post-ritual world can't sink the frame rate. Consumed tiles (`consumed_positions`) always return `""`.

### Resolving on dig

`_on_tile_dug` (connected to `EventBus.tile_dug`) walks the discovery defs in order, each consuming a probability slice, and calls `_apply_discovery` on a hit. A fixed tutorial exception: `GameConstants.TUTORIAL_POCKET_TILE` always resolves as a `root_cache` holding exactly `TUTORIAL_POCKET_ROOTS` strange roots, before any hash roll.

`_apply_discovery` records the type for the run completion metric, resolves the reward (depth-scaled via `layer_rewards` when present, with array values decoded by `_resolve_reward_value` — `[min, max]` uniform or `[min, max, exponent]` power curve), then branches on `reward_type`:

| `reward_type` | Effect |
|---|---|
| `survival` | Adds food/water via `SurvivalManager.add_supplies`. Toxic sub-types drain instead (see below). |
| `inventory` | Adds materials to `Inventory`. Gold hoards scale by the Axel's `chest_gold_mult`. |
| `hazard` | Drains current/stored food and water via `_apply_hazard_reward`. |
| `maw_resonance` | Triggers `MawController.resonance_surge` (3–5 walls torn through). |
| `plague_spore` | Queues 2–4 staggered villager deaths over real time. |

Each discovery's effective spawn chance is computed by `_discovery_chance`: the layer base chance times the Astrolabe density multiplier (`AstrolabeManager.discovery_mult_for`), with water pockets additionally scaled by `WeatherManager.water_pocket_mult()`.

### Sub-hazards

Several survival discoveries carry a second, independently-hashed sub-roll:

- **Mushrooms** resolve a variety in two steps — a layer-gated toxic/safe pick, then a hash within the pool (uniform for toxic, depth-weighted quality bands for good varieties). Toxic varieties skip food and apply an illness via `_apply_mushroom_hazard`; Death Cap and Destroying Angel are additionally depth-gated (they degrade to milder effects in shallow layers, only turning lethal deep). Mushroom spawn chance is boosted near a Water Pocket (`_mushroom_effective_chance`, 1- and 2-tile Chebyshev radius); that boost is a flat designed rate (`0.15` within one tile, `0.08` within two), so `_discovery_chance` applies it as a **floor** — `max(base × mult, boost)` — and never re-multiplies it by the Astrolabe discovery-density mult (which would otherwise triple it to `0.45` and, since water density also scales, blanket the mid layers with mushrooms).
- **Water Pockets and Mineral Springs** can resolve to `poison` (drains current water) or `brine` (drains stored water first, then current); springs are more likely saline. `get_tile_water_hazard` exposes the same deterministic verdict for display.
- The **Plague Mask** cosmetic (`ward_bad_ingest_chance`) can shrug off any tainted ingest.

Plague deaths are staggered in `_process` at `PLAGUE_DEATH_INTERVAL` (120 s), each intercepted by `MachineManager.apothecary_try_save()` before emitting `villager_died`. `reset()` clears session state, consumed positions, the cache, and the plague queue.

## Key files

| File | Role |
|---|---|
| `scripts/forest/ForestManager.gd` | Trees, bushes, pond, and forest boulders — generation, harvest, regen, persistence. |
| `scripts/forest/ForestNode.gd` | Single-tree record (`ForestNode`). |
| `scripts/forest/BerryBush.gd` | Single-bush record and berry economy constants (`BerryBush`). |
| `scripts/world/RockManager.gd` | Surface stone boulders — scatter, harvest, save/restore. |
| `scripts/world/DiscoveryManager.gd` | Underground discovery pockets — deterministic resolution, rewards, sub-hazards. |
| `data/discoveries.json` | Discovery definitions: layers, chances, reward types, rewards. |

## Related

- [World and Terrain](/docs/underroot/world-and-terrain)
- [Dig, Build, and Gather Controllers](/docs/underroot/dig-build-and-gather-controllers)
- [Survival and Village](/docs/underroot/survival-and-village)
- [The Astrolabe](/docs/underroot/the-astrolabe-and-harrow)
- [DataRegistry and the Data Files](/docs/underroot/dataregistry-and-the-data-files)