# Developer FAQ

Quick answers to the questions that come up most when you start working in the Underroot codebase. Each one links to the page with the full story.

## What's the tech stack?

Godot 4 (targeting 4.6, CI pins 4.6.2) and GDScript, strictly typed, with no C#/.NET. Default viewport is 1280×720, Forward+ renderer. The whole game runs off ~33 autoload singletons that talk to each other through a single `EventBus`. See [Introduction and Tech Stack](/docs/underroot/introduction-amp-tech-stack) and [The Autoload Model](/docs/underroot/the-autoload-model).

## Where do I start reading?

Start with [Repository Layout and Boot Flow](/docs/underroot/repository-layout-and-boot-flow) for the directory map and the boot chain (IntroStory → TitleScreen → Main), then [The Scene Tree](/docs/underroot/the-scene-tree) for how the running game is wired. After that, jump to whichever system you're touching.

## Where does game balance live?

In the JSON files under `data/`, not in the scripts. `balance.json` holds the tuning sections (maw, survival, village, and so on), and the other files define materials, tools, recipes, machines, layers, and more. Scripts read these through `DataRegistry` and only fall back to hardcoded constants if a key is missing. Full map: [Data Schemas Reference](/docs/underroot/data-schemas-reference).

## How do I add a new material, tool, or recipe?

Edit the matching `data/*.json` file — no code needed for the common cases. There's a step-by-step (including the crafting-category rule that decides whether a recipe even shows up) in [Adding Materials Tools and Recipes](/docs/underroot/adding-materials-tools-and-recipes). Machines, challenges, cosmetics, and redemption codes each have their own guide in the Developer section too.

## Why isn't my new recipe showing up in the craft menu?

Almost always the `category`. `CraftMenu` only renders `tools`, `structures`, `machines`, and `components` — a recipe with any other category is silently invisible. The other gotcha is unlock gating: recipes unlock once every input material has been seen. Details in [Adding Materials Tools and Recipes](/docs/underroot/adding-materials-tools-and-recipes).

## How do systems talk to each other?

Through the `EventBus` autoload. Managers emit signals; they don't call each other directly, and scene nodes never connect signals to each other. If you're adding cross-system behavior, add or listen to an `EventBus` signal. See [EventBus and the Signal Convention](/docs/underroot/eventbus-and-the-signal-convention).

## How do I look up game data at runtime?

`DataRegistry.get_material(id)`, `get_tool(id)`, `get_recipe(id)`, `get_machine_def(type)`. They all return `{}` on a miss, so always read fields with `.get("key", default)`. More in [DataRegistry and the Data Files](/docs/underroot/dataregistry-and-the-data-files).

## Which way is down? What's the coordinate system?

Underground is **positive Y** — the surface is world Y 0 and tiles go down as Y increases. Tiles are 32 px (`GameConstants.TILE_SIZE`). The base sits near X 0, the forest is to the left (negative X), and the Maw comes from the right. Full rundown in [World Coordinate System and Camera](/docs/underroot/world-coordinate-system-and-camera).

## Why are there two HUDs? What's `hud_style`?

Underroot ships two HUD skins: the classic sidebar-and-bars layout and the talisman skin (corner instruments + TAB ledger). `HudShell.style()` (`scripts/ui/HudShell.gd`, static — not an autoload) says which is live; the choice persists as `hud_style` in `settings.json`. Fresh machines default to talismans; machines that predate the skin stay classic until they flip the Settings > "Legacy UI" toggle. A flip emits `EventBus.hud_style_changed` and the HUD rebuilds its chrome in place — no reload. Skin-dependent code asks `HudShell.style()`, never a sibling panel. See [The Talismans HUD](/docs/underroot/the-talismans-hud) and [HUD and Bars Reference](/docs/underroot/hud-and-bars-reference).

## What are player profiles? Who owns what?

Several people can share one machine: each **profile** owns the personal state (cosmetics ownership, lifetime stats, tips/nudges, redeemed codes, Top Runs, tutorial state), each **run** owns its look (`run.cosmetics_loadout`) and its owner tag (`run.profile_id`), and the **machine** owns `settings.json` (music, perf, `hud_style`). `config.json` is the profile store (`config_version = 2`, `profiles.pN` blobs); every SaveManager accessor is scoped to the active profile. The TitleScreen's "Playing as" chip switches; switching is a full title reload. See [Player Profiles](/docs/underroot/player-profiles) and [Save System and Migration](/docs/underroot/save-system-and-migration).

## Someone deleted a run they wanted — can it come back?

Usually, yes. Every delete path routes through `delete_slot()`, which renames the file to `slot_N.deleted.json` (a one-deep, per-slot trash that is never reaped on a timer). Close the game and rename it back to `slot_N.json` in `user://saves/` — any free index works, the index is just the filename. If the run belonged to a deleted profile, find it via the picker's "Show all" and reassign with ⇄. Full procedure in [Save System and Migration](/docs/underroot/save-system-and-migration). It's gone for real only if the same slot was deleted again or a factory reset ran.

## How are saves structured, and where do they live?

Twelve JSON slots at `user://saves/slot_N.json`, plus the profile-holding `config.json` and the machine-level `settings.json`. The slot format is versioned (`SAVE_VERSION = 4`) with an in-place migration path and corrupt-slot quarantine, so a bad file never blocks boot; `config.json` has its own versioned shape (`config_version = 2`) migrated on read. On Windows that's `%APPDATA%\Godot\app_userdata\Underroot\`. See [Save System and Migration](/docs/underroot/save-system-and-migration).

## How do I start a fresh run in code?

`GameManager.reset_for_new_run()`. It rebuilds `current_run` and calls each subsystem's `reset()` in dependency order — don't reach into managers' private fields yourself. A lineage carry-on after a death is *not* a new run and deliberately skips this. See [GameManager and the Run Lifecycle](/docs/underroot/gamemanager-and-the-run-lifecycle).

## How do I run the checks before I push?

Three headless gates, same as CI: the parse-check (`tools/parse_check.ps1`), the data validator (`validate_data.gd`, expects `DATA OK`), and the smoke test (`smoke_test.gd`, expects `SMOKE PASS`). The exact commands, including the Godot console-exe requirement, are in [Verification and CI](/docs/underroot/verification-and-ci). Run the data validator after any `data/*.json` edit.

## The UI is all in code — where are the .tscn files?

There mostly aren't any. Almost every UI node is built procedurally in `_ready()` via `add_child()`; the only scene files are the entry screens (IntroStory, TitleScreen) and the World/Main roots. There are a few Godot-specific gotchas around CanvasLayer sizing and modal centering — [UI Construction Conventions](/docs/underroot/ui-construction-conventions) covers them.

## What's the branch and commit workflow?

One branch and PR per item. Don't run `git checkout -- project.godot` — uncommitted local tweaks live there; back it up and restore by copy instead. Strip any dev/cheat codes before merging, and keep redemption codes out of commit messages and PR descriptions. See [Contributor Workflow](/docs/underroot/contributor-workflow).

## Related

- [Introduction and Tech Stack](/docs/underroot/introduction-amp-tech-stack)
- [The Autoload Model](/docs/underroot/the-autoload-model)
- [The Talismans HUD](/docs/underroot/the-talismans-hud)
- [Player Profiles](/docs/underroot/player-profiles)
- [Data Schemas Reference](/docs/underroot/data-schemas-reference)
- [Verification and CI](/docs/underroot/verification-and-ci)
- [Contributor Workflow](/docs/underroot/contributor-workflow)