aboutsummaryrefslogtreecommitdiffstats
path: root/requirements/milestones.org
blob: 9aa5a5aef34f5bdd98edf2c6b77a87c4354d8886 (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
#+TITLE: Forts Clone v0.1 -- Milestones
#+AUTHOR: Forts Clone Project
#+DATE: 2025-06-19
#+OPTIONS: toc:3 num:t

* Overview

v0.1 is split into 7 milestones. Each milestone delivers something you can
run and test. Milestones are sequential -- each builds on the previous.

* M0: Scaffolding & Build Environment [DONE]
SCHEDULED: <2025-06-19>
CLOSED: <2025-06-19>

Goal: Container builds, SDL3 window opens, clears to a color.

- [X] Containerfile with all dependencies pre-built
- [X] meson.build at project root, compiles and links SDL3 + Box2D + all deps
- [X] =src/main.cpp= -- SDL3 creates a window, clears to dark blue-gray
- [X] Camera: 2D orthographic, pan (arrows/middle-drag), zoom (scroll)
- [X] Input system: polling keyboard/mouse state
- [X] Podman build + run workflow

Notes:
- Pivoted from bgfx to SDL3 renderer for M0. bgfx init fails silently on this
  system (EGL-only Linux backend, GLX removed upstream). SDL3 OpenGL renderer
  works perfectly. Revisit bgfx later in the project.
- Header collision: -I/usr/local/include/bx caused sys/types.h to find
  bx/endian.h instead of glibc endian.h. Fixed by using single
  -I/usr/local/include with qualified <bx/...> includes.

* M1: Rendering & Physics Foundations [DONE]
SCHEDULED: <2025-06-19>
CLOSED: <2025-06-19>

Goal: See physics bodies rendered as colored rectangles against terrain.

- [X] Box2D v3 integration -- world, gravity, fixed timestep at 60Hz
- [X] Static terrain body (ground plane + platform)
- [X] Dynamic bodies falling and landing (proof of physics->render)
- [X] SDL3 sprite rendering (colored quads, world-to-screen transform)
- [X] Fixed-timestep accumulator pattern (physics at 60Hz, render at display rate)
- [X] Camera pan/zoom with proper defaults (zoom 10, centered on action)

Notes:
- Box2D v3.0.0 has broken CMake (FetchContent pulls GLFW unconditionally).
  Built from v3.1.1 directly (gcc -c src/*.c + ar rcs).
- b2ShapeDef.friction moved to b2ShapeDef.material.friction in v3.
- b2Rot is { float c, s; } (cos/sin), angle via std::atan2(rot.s, rot.c).

* ARCHITECTURE NOTE: Building physics is a mass-spring system (the canon Forts model)

The locked stack specified Box2D (weld/distance joints, b2DestructionListener)
for the building structure. Instead the building system is a custom *stiff
damped mass-spring solver* (=src/game/build_graph.*=) — the same model the
shipped game uses (see =research/forts-data-reference.org=). It maps directly
onto Forts' node/edge graph and onto its data.

- Node = point mass with velocity; foundation nodes are pinned at the ground.
- Strut = a damped Hookean spring (F = k.dx + c.v), per-material stiffness.
  Integrated semi-implicit with OVERSAMPLES (14) substeps for stiff-spring
  stability (Forts uses the same oversampling).
- Rigidity is GEOMETRIC (triangulation); a lone strut is a free-hinging pin
  joint. It SNAPS on (a) axial deformation past per-material MaxCompression/
  MaxExpansion, or (b) the 30-degree angle-stress rule for un-triangulated
  struts — both taken from the shipped game's constants.
- Box2D is retained (=src/engine/physics.*=) but idle; foundation for M4
  projectiles / terrain collision.

Consequence: multiplayer determinism now rests on our own solver, not Box2D's
cross-platform guarantees (revisit before netcode work).

* LUA / MODDING: materials are now data-driven (sol2)  [DONE for materials]

Lua (sol2 3.5) is up. Materials are no longer hard-coded — they load at startup
from =data/materials.lua= via =load_materials()= (=src/game/data.*=), into a
runtime =BuildGraph::materials= vector. The layered loader works:

1. =ScriptEngine= (=src/engine/script.*=) wraps a sol2 state, runs files with
   errors caught (a bad mod can't crash the game).
2. Base =data/materials.lua= defines the global =Materials= array + helpers
   (=FindMaterial=, =IndexOfMaterial=).
3. Every mod under =data/mods/<name>/= with a =materials.lua= is applied on top
   in (Priority, name) order; mods mutate the shared =Materials= table.
   (=data/mods/example-recolour-wood/= ships as a live demo.)

Runtime data path is relative to CWD (run from the repo root). NEXT for Lua:
extend the same loader to weapons/devices/projectiles tables (lands with M4),
and a mod-selection step instead of "apply everything in data/mods/".

* M2: Building System [MOSTLY DONE]

Goal: Click to place struts on the map. They have physics, connect into a
graph, and form structures.

- [X] Build mode: number keys 1-3 select material (wood / bg-brace / rope)
- [X] Strut placed as a mass-spring node/edge + colored quad, stress-tinted
      (red under compression, blue under tension)
- [X] Anchored building only: a drag must START from an existing node or the
      ground (foundation) — no growing structure out of thin air
- [X] Click-drag single strut; endpoints snap onto existing nodes
- [X] Drag-build (extrude): sweep an edge into a box with node-merging and an
      auto diagonal brace; free (non-perpendicular) drag angle
- [X] Build-grace radial timer on un-triangulated struts (temporary rigid hold)
- [X] Building costs: placing a piece deducts metal + energy
- [X] Simple HUD: resource bars + current-material indicator
- [ ] Resource system: Metal (mines on deposits) + Energy (wind turbines)
      -- DEFERRED to M5 (needs the reactor / game-loop context). For now metal
      and energy are fixed starting pools that only deplete.

* M3: Structural Destruction  [MOSTLY DONE]
  (swapped with weapons -- destruction goes hand-in-hand with M2 building)

Goal: Overloaded structures snap and collapse; debris that falls is destroyed.
This is the purely-structural half of Forts destruction (no weapons yet).

- [X] Axial failure: deformation past per-material MaxCompression/MaxExpansion
      (wood +/-10%, asymmetric per material) — canon, read straight off the
      spring length (not a reconstructed force)
- [X] Angle-stress failure: an un-triangulated strut snaps once it rotates past
      ~30 deg from its built angle (Forts' rule; replaces the old dangle timer)
- [X] Cascading collapse: a snapped support redistributes load and overloads
      neighbours, which snap in turn (emergent, not a scripted event)
- [X] Ground destroys falling debris: any non-foundation node that sinks below
      the ground surface is removed (+ explosion puff), taking its edges with it
- [X] Disconnection -> fall -> destroy: a chunk that loses its path to a
      foundation simply falls under gravity and dies on the ground
- [X] Materials: wood, background bracing, ropes (tension-only)
- [ ] Per-strut HP pool (separate from stress) -- belongs with M4 weapon damage
- [ ] Fire (damage-over-time, strut-to-strut spread) -- M4

* M4: Weapons & Damage
  (swapped with destruction)

Goal: Place and fire all three weapons. They feed the M3 graph via two damage
models (per-strut HP and radius splash) plus fire.

- [ ] Per-strut HP pool, depleted by weapon hits (independent of stress)
- [ ] Cannon: click-drag angle/power, Box2D ballistic projectile, splash
      damage with linear radius falloff, knockback impulse, ~25% fire chance
- [ ] Laser: click-drag beam; stops at wood, passes bg-brace/ropes; 100% ignite
- [ ] Flak: auto-target incoming shells within an arc; hitscan/fast projectile
- [ ] Direct vs splash delivery: foreground struts block direct fire;
      background bracing is passed through by direct hits but takes splash + fire
- [ ] Fire system: ignition flag, damage-over-time, spread along the graph,
      burns background bracing faster (the counter to the bg-brace trick)

* M5: Reactor, Resources & Game Loop

Goal: There's a reactor to destroy, and a real economy to build it with.

- [ ] Reactor entity -- large block, HP bar
- [ ] Player reactor on left, dummy reactor on right
- [ ] Reactor takes damage from direct hits, fire, falling/collision
- [ ] Win condition: destroy reactor HP OR disconnect it from its foundation
      (collapse) -- two distinct victory paths
- [ ] Resource system (deferred from M2): mines on metal deposits, wind
      turbines for energy, generation rates, "can't build when broke"
- [ ] Win/lose condition + restart (R key)

* M6: Dummy Fort & Map

Goal: The dummy fort on the right is a hand-crafted target. The map has metal
deposits and a complete layout.

- [ ] Dummy fort pre-built structure (reactor, wood walls, bg-brace, ropes)
- [ ] Map layout: terrain with metal deposits on both sides
- [ ] Player starting area: reactor + small starter shell + nearby deposits
- [ ] Visual polish: distinct colors for materials, simple sky backdrop

* M7: Polish & Controls

Goal: Everything feels good. Controls are responsive. Visuals are clear.

- [ ] Repair tool: right-click damaged piece, pay resources to restore HP
- [ ] Recycle: shift+click to remove piece, recover partial resources
- [X] Camera feel: pan (arrows / middle-drag), scroll zoom (basic; edge-scroll TBD)
- [ ] Visual feedback: damage states, fire glow
- [X] Build UX: ghost/extrusion previews, hover highlights, build-grace timers
- [ ] Edge cases: zero resources enforcement, etc.

* Dependency Graph

#+begin_src text
M0 -- M1 -- M2 -- M3 -- M4 -- M5 -- M6 -- M7
#+end_src