#+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 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//= 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