# Forts Clone — developer guide An open-source, from-scratch clone of **Forts** (2D physics-based artillery RTS): build a fort out of struts, place weapons, and blast the enemy reactor to bits. C++20, custom engine, data-driven (Lua), no game engine. This file is the orientation for anyone (human or agent) picking up the project. Read `research/` and `requirements/` for the deep dives; this is the map. --- ## 1. Build & run **Everything builds inside a Podman container** (Fedora 41 with all deps pre-built in `/usr/local`, defined by `Containerfile`). The **binary runs on the host** (needs the host's SDL3 + a display). **Use the `Makefile` — don't type raw `podman` commands.** It wraps the whole workflow and puts each half in the right place (compile in the container, run on the host). Run everything from the repo root. ```bash make image # one-time, and after editing Containerfile make build # compile in the container (default target; configures on first use) make run # compile, then run the game on the host make check # compile + validate the Lua data <- do this before claiming done make help # every target ``` Also available: `make reconfigure` (after editing `meson.build`, since meson refuses a plain re-setup), `make shell` (interactive container), `make clean`, `make clean-image`. For a from-scratch rebuild, `make clean && make build`. - Lua is **statically linked** (Containerfile builds `liblua.a`) so the container-built binary runs on any host regardless of its lua soname. - If you add a dependency: add it to `Containerfile` **at the end** (so cached layers don't rebuild) unless it must come earlier. **Verify without a display:** you can't run the GUI in the container, so validate by (a) compiling clean and (b) parsing the Lua data. `make check` does both. Note mods must be validated **layered** (base file first) because they `table.insert` into globals the base defines — `make check-lua` already does this; running a mod file standalone always fails. --- ## 2. Architecture Two layers under `src/`: - **`src/engine/`** — reusable: windowing/input (SDL3), 2D renderer (SDL_Renderer + textures + ImGui), camera, Lua host (sol2), Box2D wrapper (idle, kept for future projectiles). - **`src/game/`** — the game: the building graph + solver, and the data loaders. ### Key files | File | Role | |---|---| | `game/build_graph.{hpp,cpp}` | **The heart.** Node/strut graph + stiff mass-spring solver, stress/fire/breaking, splash & beam damage, materials table. | | `game/data.{hpp,cpp}` | Lua loaders: materials, weapons, devices, prebuilt structures, map. Layered mod loading. | | `engine/script.{hpp,cpp}` | sol2 wrapper (`ScriptEngine`); runs Lua files with errors caught. | | `engine/renderer.{hpp,cpp}` | Draw boxes/sprites/arcs/HUD, texture cache (+ `forts:` BYO paths), ImGui lifecycle. | | `engine/app.cpp` | The whole game loop: input, build/fire/place, fixed-step sim, rendering, dev UI. Big; most gameplay glue lives here. | | `engine/physics.*` | Box2D wrapper — currently **idle**, reserved for M4+ projectiles/terrain. | > `src/game/build_system.hpp` and `src/engine/verlet.*` are legacy/removed — the > solver is now the mass-spring in `build_graph`. If you see references, they're stale. --- ## 3. The physics model (this is what makes it Forts) Canon Forts is a **stiff damped mass-spring system**, and so is ours (see `research/forts-data-reference.org` for the extracted numbers). - **Node** = point mass with velocity; foundation nodes are pinned to the ground. - **Strut** = a damped Hookean spring (`F = k·Δx + c·v`), integrated semi-implicit with `OVERSAMPLES` (14) substeps for stiff-spring stability, inside a fixed 60 Hz timestep. - **Rigidity is geometric** — from triangulation, not a per-strut property. A lone strut is a free-hinging pin joint. You can only *start* a build from an existing node or the ground (no floating structure). - **Breaking:** (a) axial deformation past per-material `max_compression` / `max_expansion` (wood ±10%), and (b) the **30° angle-stress** rule on un-triangulated struts. Both from the shipped game data. - **Build grace:** a fresh strut is held rigid briefly (`SETTLE_TIME`, Forts' "TempBracing") so you can triangulate it before it hinges. - **Fire:** per-strut HP-over-time that **spreads along the graph** to flammable neighbours; bg-brace burns ~3× faster (the counter to the "hide supports as bg-brace" trick). - **Ground destroys** any debris that falls below the surface (our choice; Forts bounces it). Collapse is emergent: cut supports → chunk falls → ground kills it. - **Damage model:** splash = pure radius falloff (ignores structure, like Forts AoE); cannon shells **pass through bg-brace/rope** and detonate on wood; the laser beam passes through bg-brace/rope, damages+ignites everything it crosses, and **stops at wood**. **Tunables** live at the top of `build_graph.hpp` (`OVERSAMPLES`, `SETTLE_TIME`, `gravity`, `air_drag`, strengths) and in the Lua data. Most are exposed **live in the ImGui Dev panel** — use it; the constants were tuned by eye, not proven. ### Gotchas - Removing a node/edge **shifts indices**. Never cache node/edge indices across a frame where destruction can happen — re-query (see the `hover_node` re-validation in `app.cpp`). Use `break_edge`/`break_node`/`apply_splash` which keep everything consistent. - **Determinism:** the solver is order-deterministic but uses `sin/cos/sqrt`, so it is **not** cross-platform deterministic. Revisit before any lockstep netcode. - The **data path is relative to CWD** — run from the repo root. --- ## 4. Data-driven + modding (Lua / sol2) Game content is **Lua data**, loaded at startup. This is the seam mods hook into. Base files in `data/`: `materials.lua`, `weapons.lua`, `devices.lua`, `structures.lua`, `map.lua`. Each defines a global table (`Materials`, `Weapons`, `DeviceDefs`, `Structures`, `Deposits`/`MapDevices`) plus `Find*`/`IndexOf*` helpers. **Layered loader** (`data.cpp::run_layered`): base file runs first, then every `data/mods//.lua` runs on top in **(priority, name)** order, mutating or extending the global tables — exactly like Forts. A mod's `mod.lua` sets `Priority`. Example: `data/mods/enemy-fort/` adds the target fort + enemy reactor. To add content, prefer **editing Lua data** over hard-coding C++. When you must add a field, add it to the `*Def` struct, read it in `data.cpp` (`get_or` with a default), and document it in the Lua file. --- ## 5. Assets policy — IMPORTANT **Never commit Forts' proprietary art/audio/Lua to this repo.** It's EarthWork Games' copyrighted work. - The repo ships **CC0 placeholder art** only (Kenney — see `data/textures/CREDITS.md`) plus public-domain code (`third_party/stb`, MIT `third_party/imgui`). - To debug with the *real* game art, we use **bring-your-own game files** (like a Doom source port + WAD): a texture path `forts:REL` resolves at runtime to `/REL`, where `` is the first that exists of some known dev install paths then `$FORTS_DATA` (see `forts_data_root()` in `renderer.cpp`). Nothing copyrighted ever enters the repo. - **Two-field fallback:** material/weapon/device defs carry both `texture` (a CC0 placeholder committed to the repo) and `texture_forts` (a `forts:` path to the real art). `Renderer::load_texture_or` prefers the Forts art, falls back to the CC0 placeholder, then to the flat `color`. So a user without Forts always gets the placeholder; a user with it gets the real sprite. **stb_image only decodes TGA/PNG/JPG, not DDS** (most Forts art), so only TGA/PNG/JPG paths will load. - Keep personal/BYO override mods under `data/mods/local-*/` (git-ignored). --- ## 6. Status (milestones) See `requirements/milestones.org` for the live checklist. Roughly: - **M0–M3 done:** scaffolding, rendering/physics, building system, structural destruction (stress/collapse/fire). - **M4 mostly done:** per-strut HP, cannon (ballistic) + laser (beam/fire) with the Forts select-a-weapon-and-aim-in-its-arc UX, direct-vs-splash delivery, fire + spread, firing recoil, and the armour material (build mode 4). Missing: mortar, flak/point-defence, machinegun/sniper (hitscan), guided missiles, EMP, metal-per-shot. - **M5 mostly done:** reactors + win/loss + restart (R), real resource economy (mines on deposits, turbines by height, storage caps, firing costs energy), device framework + placement. Two gaps: fire does **not** damage devices, and devices are not mounted on the strut graph, so the "cut its supports and the reactor falls" victory path does not exist. - **M6 partial:** enemy fort (`data/mods/enemy-fort/`) + deposits both sides; terrain is still a flat half-plane and there is no sky backdrop. - **Not started / later:** repair & recycle (M7), armour/door/shield materials, tech tree + upgrades, commanders, real map/terrain, AI, netcode. `research/gameplay-gaps.org` is the **sourced** list of what's missing and why — consult it before deciding what to build; don't guess from vibes. --- ## 7. Working conventions - **Branch for work.** `main` is the baseline. Do features and fixes on their own branches: `feat/`, `fix/` (e.g. `feat/mortar`, `fix/beam-passthru`). Don't commit feature work straight to `main`; open a branch, then merge. - **Commit/push only when asked.** End commit messages with the `Co-Authored-By: Claude ...` trailer. - **Build before you claim done:** run `make check` (compiles in the container and parses the Lua data). Be honest about what you couldn't verify — the GUI can't run headless here, so anything visual needs a human to look at it. - **Keep the status docs in sync — same commit as the change.** When you land, extend or remove a feature, update all three in the commit that changes the behaviour, not later: 1. `requirements/milestones.org` — tick the boxes you actually delivered, and fix any item whose *description* no longer matches what was built. 2. `research/gameplay-gaps.org` — the "What we HAVE" baseline, the affected `**` section, the roadmap table, and the "smallest next steps" queue. 3. `CLAUDE.md` §6 — the milestone summary, plus §2/§3/§4 if the architecture, the physics model or the data schema moved. Rules that make this worth doing: - **Verify against the code, not memory.** Grep for the thing before ticking it. These docs drove a real prioritisation error once: every M4/M5 box sat unchecked and the gaps doc still claimed "we have ZERO devices" long after reactors, mines, turbines, the economy and win/loss had shipped. - **Record the gap, not just the win.** Half-done is `[~]` plus a one-line note on exactly what is missing (e.g. M5's reactor exists, but nothing mounts it on the strut graph, so there is no collapse-victory path). A `[X]` that hides a caveat is worse than an unticked box. - §6 tells everyone to consult `gameplay-gaps.org` before choosing work, not to guess from vibes. That holds only while the doc is true. - **Don't commit game assets** (§5), `builddir/`, or `imgui.ini`. - Match surrounding code style; keep the engine/game split; prefer data (Lua) over hard-coded constants. --- ## 8. Reference docs - `requirements/v0.1-core.org` — the v0.1 design spec. - `requirements/milestones.org` — milestone checklist + architecture notes. - `research/forts-gameplay.org` — gameplay research. - `research/forts-data-reference.org` — **exact numbers** extracted from the game data (physics constants, material/weapon/fire values, modding API). - `research/forts-data-map.org` — index of the shipped Forts data directory (layout, file inventory, what each area holds). Use it to find where a reference value or mechanic lives before diving into the game's Lua. - `research/gameplay-gaps.org` — sourced list of missing systems, prioritized. - `research/tech-stack.org` — locked tech choices and rationale. - `research/deterministic-physics.org` — the plan to make the mass-spring solver deterministic (strict-float approach) for future lockstep netcode.