# 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
* keep the tone professional, brief and to the point — brevity applies to prose and
  preamble, not to the substance of an objection or a design rationale
* be critical: where there is a concrete technical reason to disagree, name it once;
  if the user reaffirms, proceed with their call without re-litigating

## 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-<AREA>-<NAME>` 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
* when planning a change, weigh the long-term maintainability of the codebase instead of
  defaulting to the lowest-effort patch — but no speculative generality: never build or
  prepare for functionality we may never need. If the maintainable solution is much larger
  than the request, say so and let the user decide the scope.
* class layout: static members first, then non-static; within each, public, then protected,
  then private (Qt `slots:`/`signals:` are ordinary non-static access groups). Inside an
  access group the order is: nested types, static constants, aliases, methods, fields,
  friends. Out-of-line method definitions follow the declaration order. Applies to new
  classes and to files being edited anyway — don't reorder existing headers just to comply.
* do not use the "auto" keyword, with two exceptions:
  * **named local lambdas** — a lambda's type is unnameable, and `std::function`
    is not an acceptable substitute in per-tick code because it adds a heap
    allocation and an indirect call
  * **iterator types** — `auto it = m_buildings.find(id)` is allowed where
    spelling the iterator out adds length without adding information
  * everywhere else the type is written out; in particular `auto` is not used
    for plain values, return values, or range-for element types
* 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
* keep source files ASCII-only. A non-ASCII character needed at runtime (a glyph in a
  UI string, a symbol drawn on a widget) is written as its code point with a comment
  naming it (`const QChar shiftGlyph(0x21E7);  // U+21E7 UPWARDS WHITE ARROW`), never
  as a literal character: MSVC 2017 does not read the sources as UTF-8 by default and
  silently mangles them. Never round-trip a source file through
  `Get-Content`/`Set-Content` either: Windows PowerShell reads it as ANSI and writes
  it back double-encoded with a BOM. Use the Edit/Write tools.

## 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/<Config>/{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 <file>` for view-only playback of a recorded run.

**Visual verification is the user's job.** Screen-capturing the app window does not
work here: `CopyFromScreen` and `PrintWindow` both return a blank white client area
even while the app is running and rendering normally, because the capture cannot read
the composited surface of the `QOpenGLWidget`-backed window. A blank capture therefore
says nothing about whether the UI works, so do not read one as a regression and do not
try to work around it. To check a UI change: build, run the tests, launch the app, and
ask the user to look at it. Redirecting the process's stdout/stderr to a file does
work and is worth checking for Qt warnings.

## 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<Ts...>` 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<Component>` 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.
