aboutsummaryrefslogtreecommitdiffstats
path: root/CLAUDE.md
blob: cec8e39f5f1ddb35ee9f0fa175d1e5ebd866684e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# 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, (b) parsing the Lua data and (c) settling every map in
the headless soak test (`tools/sim_check.cpp` — the solver needs no display, so
it catches "the fort collapses on load"). `make check` does all three;
`make sim-check SOAK=20` for a longer settle.
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`, `maps.lua`.
Each defines a global table (`Materials`, `Weapons`, `DeviceDefs`, `Maps`) plus
`Find*`/`IndexOf*` helpers.

**Maps** (`data/maps.lua`) are the selectable scenarios: each entry bundles
`ground_level`, `deposits`, `devices`, the player's starting `weapons`, and the
prebuilt `structures` (lists of `{x1,y1,x2,y2,material}` beams). `Maps[1]` is the
default; the Dev panel's **Map** section switches between them at runtime, which
tears the graph down and rebuilds it via `build_map()`. Beams must be within the
material's `max_length` (they are welded raw, **not** auto-subdivided) and must
be triangulated, or the fort hinges apart on the 30° angle rule.

> **Gotcha:** clearing `graph.nodes`/`graph.edges` does **not** clear the private
> `adj_`/`edge_set_` caches. Call `graph.rebuild()` straight after clearing, or
> the stale edge keys make `add_edge` reject the next map's beams as duplicates
> and the fort silently loads half-built. `build_map` warns on every rejected
> beam; `make sim-check` catches the collapse.

**Layered loader** (`data.cpp::run_layered`): base file runs first, then every
`data/mods/<name>/<file>.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/maps.lua` uses `FindMap()` to bolt
extra armour plating onto the Proving Ground enemy fort — the worked example of
a mod mutating base data rather than replacing it.

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
  `<root>/REL`, where `<root>` 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 done:** reactors + win/loss + restart (R), real resource economy
  (mines on deposits, turbines by height, storage caps, firing costs energy),
  device framework + placement on strut graph (collapse victory path works),
  fire damages devices on burning struts.
- **M6 partial:** three selectable scenarios in `data/maps.lua` (Proving Ground,
  Iron Bastion, The Narrows), each with its own forts, deposits, player starter
  structure and weapon spawns, switchable from the Dev panel. Terrain is still a
  flat half-plane — maps can move the ground up and down but not change its
  shape — 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/<thing>`, `fix/<thing>` (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.