# Dig Build and Gather Controllers

Four input controllers turn mouse and keyboard events into world mutations: digging tiles, placing and binding walls, harvesting surface resources, setting explosive charges. Each is a `Node2D` under `World` that reads input in `_unhandled_input`, advances an in-progress action in `_process`, and draws its own preview or progress overlay in `_draw`.

All four bail early on `GameManager.is_world_input_blocked()`, so world input is suppressed while a modal or menu owns the screen. Coordinates convert with `GameConstants.TILE_SIZE` (32 px); underground is positive Y and the surface is Y 0.

## DigController

`scenes/world/DigController.gd` handles left-click digging with a timed progress bar.

### Input and start

A left click is ignored when a build material is selected (`_build_menu.selected_material` non-empty), so the same button can serve the build menu. Otherwise the mouse position converts to a tile and `_try_start_dig` runs:

1. Tiles above ground (`y < 0`) are rejected.
2. `TerrainDigging.can_dig(tile_pos, tool_id)` must pass; a failure routes to `_warn_cannot_dig`.
3. Underground tiles must satisfy `WorldManager.is_surface_connected` — otherwise the player gets `"No tunnel to the surface here."`.
4. Duration is captured once at start: `TerrainDigging.get_dig_time(...) / CosmeticManager.get_effect_mult("dig_speed_mult")`. Capturing it means equipping a faster tool mid-dig cannot retroactively rush the in-progress tile.

The controller sets `GameManager.is_working = true` and emits `EventBus.dig_started`. A right click cancels via `_cancel_dig`.

### Progress and completion

`_process` accumulates `delta` into `_progress` and calls `queue_redraw()` each frame; `_draw` renders a tile highlight and a bottom-aligned progress bar. When `_progress >= _duration`, `_complete_dig` runs:

```gdscript
_check_overshoot_warning(_target)
TerrainDigging.apply_dig(_target)
_check_world_bottom(_target)
GameManager.current_run["last_dig_x"] = _target.x
GameManager.current_run["last_dig_y"] = _target.y
```

`last_dig_x` / `last_dig_y` record where the player personally dug, so the "Dig Front" jump returns to their own hole rather than a machine's face. They are set here rather than off `EventBus.tile_dug`, which machines and the Astrolabe also fire.

### Warnings

- `_warn_cannot_dig` surfaces a toast only when the tile is exposed but too hard for the tool — it stays silent for open space and buried tiles, which the player cannot act on. It names the material and, via `TerrainDigging.required_tool_for`, the pickaxe needed, and throttles repeats of the same message to once per 2.5 s.
- `_check_overshoot_warning` fires once per run when the player digs a material exactly one tier above the tool's `max_material_tier` (the 4x-durability overreach case), warning that the pickaxe is wearing fast.
- `_check_world_bottom` sets `current_run.bottom_reached` and emits `EventBus.world_bottom_reached` the first time a dig reaches the deepest layer's `depth_end`.

## BuildController and BuildManager

`scenes/world/BuildController.gd` handles wall placement, upgrade, removal, and root binding on the surface line (`y = 0`), delegating all state changes to `BuildManager` (`scripts/building/BuildManager.gd`). Walls are built from raw materials — one unit per placed layer.

### Build zones and Maw bodies

Two guards decide where a wall may go:

- `_in_build_zone(world_x)` — the east zone is always open (`x >= PLACE_X_MIN`, i.e. `0`). A west zone (`[WEST_BUILD_OUTER_X, WEST_WALL_LINE_X]`) opens only while `MawController.has_west_front()` is true, so a normal run stays east-only and the village core between the lines is never buildable.
- `_in_maw_body(tile_x)` — a tile at or behind any front's mouth on that front's side is unbuildable.

### Input modes

`BuildController` runs in one of three transient modes, set from `BuildMenu` signals (`bind_requested`, `remove_requested`) or by holding modifiers:

| Interaction | Result |
|---|---|
| Right click (or left click with a material selected) | `_try_place_wall` |
| Shift + place | `_try_upgrade_wall` |
| Remove mode, left click | `_try_remove_wall` (Shift = whole column) |
| Bind mode, left click | `_try_bind_wall` |
| Escape / right click in a mode | Cancel the mode |

`_process` maintains a placement preview (`_show_preview`, `_preview_tile_x`) whenever a material is selected and the cursor is over a buildable surface tile. `_draw` renders the stacking preview, an upgrade preview when Shift is held over an existing wall (including a `+N% mix` bonus hint), and pulsing remove/bind overlays with labels.

### BuildManager: wall data operations

`WallData` (`scripts/building/WallData.gd`, `class_name WallData`) defines the wall dictionary and its math; `BuildManager` applies them and touches `Inventory`:

| BuildManager method | Behaviour |
|---|---|
| `can_build(position, material_id)` | Material must have `is_wall_material` and the player must hold at least 1 unit. |
| `build_wall(position, material_id)` | New column costs 1 unit via `WallData.create`; an existing column routes to `_add_layer_unit`. Emits `wall_built`. |
| `_add_layer_unit` | Adds one unit (up to `max_height_units`, default 9), merging into a matching layer or appending a new material layer. First mixed material fires `wall_mix_discovered`. |
| `can_upgrade` / `upgrade_wall` | Converts all foreign-material units in a column to one material, refunding the replaced units and charging for the new ones. |
| `remove_wall` | Destroys the whole column, no refund. Emits `wall_removed` (not `wall_destroyed`, so it skips Maw-attack shake). |
| `remove_wall_unit` | Peels one unit off the top; a column down to its last unit is removed outright (binding lost with it). |

Each unit stacks HP: a layer contributes `wall_hp * units`, and `recalc_max_hp` applies a mix bonus of `1.0 + mix_bonus_per_type * (layer_count - 1)`. All wall HP is scaled by `ChallengeManager.wall_hp_mult()`.

### Root binding

`_try_bind_wall` binds a wall with strange root so it resists the Maw. It requires `ROOTS_REQUIRED` strange roots (from `balance.json -> walls.roots_required`, default 5), a wall at that column, no existing binding, and the column not on cooldown (`MawController.is_column_on_cooldown`). On success it removes the roots, calls `WorldManager.bind_wall`, and — if the owning front is already chewing that exact column — primes an immediate trigger via `MawController.prime_root_trigger`.

## GatherController

`scenes/world/GatherController.gd` handles surface harvesting: berry bushes, trees, surface rocks, forest boulders, the pond, and gravestone collection. It sits at `z_index = 7` so its progress bars draw above the player marker.

A left click routes to `_try_start_gather`, which checks targets in order. Surface rocks are checked first because they straddle `y = 0`; everything else requires `world_pos.y < 0`. The gravestone is an instant collect when clicked within 24 px. Each remaining target (boulder, pond, bush, tree) starts a timed gather whose duration is captured at start:

| Target | Manager call on completion | Notes |
|---|---|---|
| Berry bush | `ForestManager.harvest_bush` | Time divided by `CosmeticManager.get_berry_speed_mult()` (gloves). |
| Tree | `ForestManager.harvest_one` + `ToolState.use_gather_tool()` | Time from `ForestManager.get_gather_time`. |
| Surface rock | `RockManager.harvest_one` + `ToolState.use_gather_tool()` | |
| Boulder | `ForestManager.harvest_boulder` | Time = `BOULDER_BASE_GATHER_TIME / dig_speed_multiplier`. |
| Pond | `ForestManager.harvest_pond` then `SurvivalManager.add_supplies` | Fixed `POND_GATHER_TIME` / `POND_GATHER_AMOUNT`. |

`_process` advances whichever single gather is active and calls the manager on completion; `_draw` renders a small progress bar at the target's position. A right click cancels all active gathers via `_cancel_all`.

## DynamiteController

`scenes/world/DynamiteController.gd` places two kinds of explosive charge: **dynamite** (a compact radial blast) and the **mining charge** (a directional 2x7 corridor blast). Tuning is read from `balance.json -> dynamite` and `-> mining_charge` in `_ready`.

### Entering placement and aiming

Placement mode starts from `EventBus.machine_placement_requested` (for `dynamite` or `mining_charge`) or the `T` key for dynamite, and requires at least one of the item in inventory. While placing the mining charge, `R` rotates through four directions (right / down / left / up); `Escape` cancels. `_draw` shows an orange tile preview for dynamite and a blue footprint plus direction arrow for the mining charge, tinted by validity.

### Placement rules

`_try_place_dynamite` / `_try_place_mc` share the same guards: the tile must be underground (`y > 0`), solid, exposed (`TerrainDigging.is_exposed`), and free of an existing fuse. On success the item is consumed and a random fuse time (`FUSE_MIN`..`FUSE_MAX`) is stored in `_fuses`, with the charge type in `_fuses_type` and the mining charge's direction in `_fuses_dir`.

### Detonation

`_process` counts each fuse down and calls `_explode` at zero. Dynamite carves a diamond of radius `BLAST_REACH` plus the four diagonals; the mining charge carves the `_mc_blast_tiles` footprint (`MC_BLAST_LENGTH` tiles long, two wide, in the aimed direction). Both call `_dig_blast_tiles`, which for each solid tile removes it, adds `TerrainDigging.roll_yield` units to inventory, and emits `tile_dug` and `mined_yield` — the same yield path as a manual dig.

If the player's tile falls within the blast (radial `DAMAGE_RADIUS` for dynamite, the footprint for the mining charge), they take `BLAST_FOOD_DMG` / `BLAST_WATER_DMG` (or the `MC_` variants) off food and water and get a `"Caught in the blast!"` toast; otherwise a `"BOOM"`. Each blast emits `EventBus.dynamite_exploded` and draws a fading flash ring.

## Key files

| File | Role |
|---|---|
| `scenes/world/DigController.gd` | Left-click dig, progress bar, overreach/too-hard warnings, dig-front tracking. |
| `scenes/world/BuildController.gd` | Wall placement, upgrade, removal, root binding, previews; build-zone and Maw-body guards. |
| `scripts/building/BuildManager.gd` | Wall state changes and inventory cost; stacking, mixing, upgrade, removal. |
| `scripts/building/WallData.gd` | Wall dictionary shape and HP/stacking math. |
| `scenes/world/GatherController.gd` | Surface harvest input for bushes, trees, rocks, boulders, pond, gravestone. |
| `scenes/world/DynamiteController.gd` | Dynamite and mining-charge placement, fuses, and detonation. |

## Related

- [World and Terrain](/docs/underroot/world-and-terrain)
- [Forest and Surface](/docs/underroot/forest-and-surface)
- [The Maw](/docs/underroot/the-maw)
- [HUD and Bars Reference](/docs/underroot/hud-and-bars-reference)
- [Challenges](/docs/underroot/challenges)