# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Interaction * ONLY modify code or other files if explicitly asked to do so ## Project Overview Dota Factory is a single-player game that blends a Factorio-style factory builder with DOTA-style wave defence. The player builds a factory on an asteroid — mining ores, transporting materials over belts and splitters, and crafting through a config-defined production tree — to supply shipyards that produce autonomous combat ships. Those ships fight off endless enemy waves advancing from the right. See `docs/concept.md` for the full design. ## Project Structure * the project root and the git repository root are the same directory * project requirements can be found at `docs/requirements.md` * architecture decisions can be found at `docs/architecture.md` * game content design (ship/module roster, layout grids, footprint gating) can be found at `docs/content_design.md` * replay/determinism design can be found at `docs/replay_design.md` * balancing rules, targets, tuned numbers, process, and history live under `docs/balancing/` Requirements carry stable `REQ--` ids. They are cited throughout the code in comments — when changing behavior, find the governing REQ id first and keep the citation accurate. ## Coding Guidelines * avoid duplicate code * do not use the "auto" keyword * use Qt utility data types (like QPoint, QVector3D, QString, etc.) * wrap strings that appear in the UI with Qt's "tr()" * use the EventManager/EventHandler instead of defining own signals and slots * use std::optional if a variable can be "not set" * start the name of a getter method with "get" * don't use abbreviations, except very common ones ("s" for seconds, "min", "max", etc.) * if a variable holds a value that has a unit or if a function returns a value that has a unit, append that unit to the name (e.g. "m_shipVelocity_mps", "getAcceleration_mpss()") * always enclose scopes in braces ## Build Requires CMake 3.14.4+, a C++17 compiler, and Qt 5 (developed against Qt 5.12.3, MSVC 2017 x64; `Qt5_DIR` is cached in `build/CMakeCache.txt`). Needs Qt components Widgets, Network, Multimedia, Charts, Svg, plus OpenGL. External dependencies vendored under `src/external/`: * **toml++** — reading TOML config files * **tinyexpr** — evaluating formula strings from config files * **EnTT** — entity registry backing the ship/station/debris simulation * **Catch2** — test framework Configure and build (a configured `build/` tree already exists): ```sh cmake -S . -B build # configure (multi-config VS generator) cmake --build build --config Debug # all targets cmake --build build --config Debug --target DotaFactory_test ``` Targets: `DotaFactory` (app), `DotaFactory_lib`, `DotaFactory_ui`, `DotaFactory_test`, `DotaFactory_balancing`. Executables land in `build/DotaFactory//{app,balancing}/`. **Adding a source file requires editing CMake.** Every directory under `src/` has its own `CMakeLists.txt` listing files explicitly in `HDRS`/`SRCS` (or `TEST_FILES` for tests) — there is no globbing. A new file that is not registered simply will not compile. Config data is not copied: `CONFIG_DIR` is a compile definition pointing at `bin/app/data/config` for the app and balancing tool, and `bin/test/data/config` for tests (a separate fixture set). On Windows the build also junctions `bin/*/data` into the output directories and copies the Qt DLLs. Run the app: `build/DotaFactory/Debug/app/DotaFactory.exe`, optionally `--replay ` for view-only playback of a recorded run. ## Tests Catch2, single executable, links `lib` only — no QApplication, no display. ```sh build/DotaFactory/Debug/app/DotaFactory_test.exe # all build/DotaFactory/Debug/app/DotaFactory_test.exe "[belt],[building]" # by tag build/DotaFactory/Debug/app/DotaFactory_test.exe "BeltSystem: *" # by name pattern build/DotaFactory/Debug/app/DotaFactory_test.exe --reporter compact ``` Common tags: `[building] [belt] [behavior] [blueprint] [modules] [config] [wave] [combat] [replay] [determinism] [ship] [debris] [threat] [unlock]`. `src/test/SimulationTestAccess.h` is a friend-struct backdoor to `Simulation`'s private mutators; tests use it instead of duplicating the command path. It lives under `src/test` and is deliberately off the lib/ui/app include path. ## Verification Tools Python scripts in `tools/` read the real configs and are the first check after config edits (see `docs/balancing/process.md`): * `verify_recipes.py` — recipe-tree closure, visuals coverage, orphan items * `verify_layouts.py` — module footprint gating per hull layout * `threat_report.py` — item/module/ship threat values, ratios, belt feasibility The `DotaFactory_balancing` target runs parallel arena simulations from `bin/balancing/data/balancing.toml` for combat-stat tuning. ## Architecture See `docs/architecture.md` for the full write-up. Highlights and the invariants that are easy to break: * Strict simulation/presentation split, enforced at the CMake target level: `lib` (sim + config, Qt Core/Gui only — no QtWidgets), `ui` (QtWidgets + QOpenGLWidget), `app` (thin main), `test` (Catch2 against `lib`). * Fixed 30 Hz tick simulation, 60 FPS render, accumulator-driven; game speed is a tick-rate multiplier. All sim quantities are in ticks, never wall-clock seconds. * The tick order in `Simulation::tick()` is load-bearing for determinism — see the Tick Order section of `architecture.md` before reordering systems. * **Command chokepoint:** every sim mutation during play flows through `Simulation::apply(const Command&)` (see `sim/Command.h`, `CommandManager`), so runs can be recorded and replayed. Commands reference stable ids (`BuildingId`, tile coords, choice indices) — never raw `entt::entity` handles. UI code must not call sim mutators directly. Determinism is checksummed (`StateChecksum`) and covered by `DeterminismTest` / `ReplayPlaybackTest`. * Config is loaded once at startup, formulas compiled once via tinyexpr, immutable afterwards; malformed config aborts startup rather than failing mid-game. Restart reloads config from disk (REQ-CFG-RELOAD). * **The sim uses EnTT for ships, stations, debris, and module child entities**, wrapped by `core/EntityAdmin` (registry, factory methods, `forEach` views). Components live in `lib/ecs/component/`, systems in `lib/ecs/system/`. Note: `architecture.md`'s "Ships" and "Why Not ECS" sections still describe the earlier `std::optional` design and are stale on this point; the code is authoritative. Buildings and the belt subsystem stay outside the entity model. * Ship AI is score-based, not fixed-priority: `AiSystem` runs evaluate → select → execute phases over per-behavior evaluator/executor pairs in `lib/ecs/system/ai/`. Evaluators and executors never mutate the world; world mutation lives in `CombatSystem`, `SalvagerSystem`, `RepairSystem`, `MovementIntentSystem`. * Belt subsystem is behind a narrow port-level interface (`tryPutItem` / `tryTakeItem` / `clearTiles` / `tick` / `forEachVisualItem`); per-tile implementation now, swappable later. No other system asks "what is on tile X". * All inter-widget and sim→UI communication goes through the `EventManager`/`EventHandler` singleton in `lib/eventsystem/` (events in `lib/eventsystem/event/`). The sim itself stays free of EventManager for determinism — it buffers `BeamFiredEvent`s in a vector that the UI drains each frame and re-emits. * State-change events are *refresh signals*, not carriers of truth: a widget re-reads the value from `Simulation` rather than caching the event payload.