#+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. - Follow-up: no source file =#include=s bgfx, SoLoud, ENet or EnTT, so =meson.build= now treats them as OPTIONAL (linked only if a source build has put them in /usr/local) and falls back to system packages otherwise. That is what makes a native (non-container) build possible -- see README. Promote each back to a required dependency when it is actually wired up. - 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 - [X] Resource system: Metal (mines on deposits) + Energy (wind turbines) -- was deferred to M5 and DELIVERED there (generation, storage caps, build + firing costs). * 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) - [X] Per-strut HP pool (separate from stress) -- delivered in M4 - [X] Fire (damage-over-time, strut-to-strut spread) -- delivered in M4 * M4: Weapons & Damage [MOSTLY DONE] (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. - [X] Per-strut HP pool, depleted by weapon hits (independent of stress) - [X] Cannon: ballistic projectile, splash damage with linear radius falloff, knockback impulse, ~25% fire chance, per-shot energy cost. NOTE: the UX is Forts' select-a-weapon-and-aim-within-its-arc, not the click-drag angle/power originally sketched here. The projectile uses our own integrator; Box2D is still idle. - [X] Laser: beam; stops at wood, passes bg-brace/ropes; 100% ignite - [ ] Flak: auto-target incoming shells within an arc; hitscan/fast projectile - [X] Direct vs splash delivery: foreground struts block direct fire; background bracing is passed through by direct hits but takes splash + fire - [X] Fire system: ignition flag, damage-over-time, spread along the graph, burns background bracing faster (the counter to the bg-brace trick) Still missing from the vanilla roster / mechanics (see gameplay-gaps.org #5, #8): mortar, flak + point-defence, machinegun/sniper (hitscan), guided swarm missiles, EMP, and per-shot RECOIL on the firing structure (we apply knockback to targets only). * M5: Reactor, Resources & Game Loop [MOSTLY DONE] Goal: There's a reactor to destroy, and a real economy to build it with. - [X] Reactor entity -- large block, HP bar (a device with =is_core=) - [X] Player reactor on left, enemy reactor on right (via the enemy-fort mod) - [~] Reactor takes damage from direct hits, fire, falling/collision -- splash and beam damage hit devices; FIRE does not damage devices, and devices are not attached to the strut graph, so they never fall. - [~] Win condition: destroy reactor HP OR disconnect it from its foundation (collapse) -- only the HP path exists. The round ends when a team has no surviving =is_core= device. The collapse path needs devices mounted on the graph (see M6 / gameplay-gaps.org #3). - [X] Resource system (deferred from M2): mines on metal deposits, wind turbines for energy, generation rates, storage caps, build/fire costs - [X] Win/lose condition + restart (R key) * M6: Dummy Fort & Map [PARTIAL] Goal: The dummy fort on the right is a hand-crafted target. The map has metal deposits and a complete layout. - [X] Dummy fort pre-built structure -- =data/mods/enemy-fort/= builds a triangulated fort at x=25..29 with the enemy reactor inside. Wood only; no bg-brace or ropes in it yet. - [~] Map layout: metal deposits on both sides exist (=data/map.lua=), but the terrain is still a flat half-plane, not a real heightmap. - [~] Player starting area: player reactor + nearby deposits on the left; there is no pre-built starter shell. - [~] Visual polish: materials are visually distinct and can load real Forts art (see CLAUDE.md section 5); no sky backdrop is drawn yet. * 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: fire glow done (burning struts flicker orange); no per-damage-state art yet. Forts ships damage-state textures (=materials/wood_panel_damaged_1..3.tga=, =bracing-d1/d2=, =armor-d1/d2=) that a =texture_forts=-style field could pick up. - [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