aboutsummaryrefslogtreecommitdiffstats
path: root/research
diff options
context:
space:
mode:
Diffstat (limited to 'research')
-rw-r--r--research/forts-data-reference.org193
-rw-r--r--research/forts-gameplay.org409
-rw-r--r--research/gameplay-gaps.org143
-rw-r--r--research/tech-stack.org487
4 files changed, 1232 insertions, 0 deletions
diff --git a/research/forts-data-reference.org b/research/forts-data-reference.org
new file mode 100644
index 0000000..5d4ecec
--- /dev/null
+++ b/research/forts-data-reference.org
@@ -0,0 +1,193 @@
+#+TITLE: Forts Data & Modding Reference (extracted from the shipped game)
+#+AUTHOR: Forts Clone Project
+#+DATE: 2026-06-30
+#+OPTIONS: toc:3 num:t
+
+* Source
+
+Extracted directly from a local Forts install (unpacked, NOT decompiled):
+- Base data: =SteamLibrary/steamapps/common/Forts/data/= (~2650 .lua files)
+- Workshop mods: =SteamLibrary/steamapps/workshop/content/410900/= (~80 mods)
+
+The game ships its game-logic data as plain Lua. This file records the bits
+that matter for our clone: the canonical physics/material/fire numbers, and the
+modding architecture. Numbers here are the GROUND TRUTH that validate (and in a
+few places correct) our research notes.
+
+* Canonical physics constants (=data/db/constants.lua=, table =Physics=/=Structure=)
+
+| Constant | Value | Meaning / our equivalent |
+|-----------------------------------+-----------+--------------------------------------------------|
+| Gravity | 981 | cm/s^2 (game works in cm; 1 grid = 37.5..150 cm) |
+| Oversamples | 14 | constraint solver iterations (we use 8) |
+| SpringDamping | 800 | global spring damping |
+| MinStiffness / MaxStiffness | 1e4 / 1e6 | per-material Stiffness clamps |
+| MinimumMass | 15 | |
+| Limits.AngleStressPrimaryThreshold| 30 | strut snaps if rotated >30 deg from built angle |
+| Limits.AngleStressSecondaryThreshold | 15 | secondary (softer) angle threshold |
+| Limits.MinimumStrutDivergenceAngle| 15 | min angle between two struts at a node |
+| StressWarning.CompressionThreshold| 0.2 | when the strut turns red (warning, not break) |
+| StressWarning.ExpansionThreshold | 0.2 | when the strut turns blue (warning, not break) |
+| TempBracing.Duration | 16 | temp rigid hold on fresh struts (our SETTLE_TIME)|
+| TempBracing.Scale | 1.1 | |
+| Break.Effect | structure_break.lua | strut snap FX |
+
+** IMPORTANT correction to our research
+Our research notes claimed "stress is axial only, there is no angular stress
+term." The shipped data DISPROVES that: =AngleStressPrimaryThreshold = 30= and
+=AngleStressSecondaryThreshold = 15= are real break conditions. So Forts has
+BOTH an axial deformation break (MaxCompression/MaxExpansion, see below) AND an
+angle-from-built-angle break (30/15 deg). We currently use a force-based axial
+break only — adding an angle-stress break is a faithful future option.
+
+** Stress colours (=Structure.Colours=) — matches the in-game strut colouring
+Compressed = red, Expanded = blue, AtRest = white, Braced = yellow.
+(Our build-grace radial is yellow = "Braced"; our snap is force-based.)
+
+* Material schema (=data/materials/building_materials.lua=)
+
+Materials inherit from base templates =Bracing= and =Armor= via
+=InheritMaterial(base, {overrides})=. Key per-material fields:
+
+** Structural / physics
+- =Stiffness= spring stiffness (bracing 200000, armor 250000)
+- =MaxCompression= / =MaxExpansion= axial break thresholds (bracing 0.90/1.10 = +/-10%!)
+- =MinLength= / =MaxLength= / =MaxLinkLength= / =MaxSegmentLength=
+- =Mass= (bracing 0.25, armor 0.50 -> 2x heavier, rope 0.001)
+- =AirDrag=, =SpringDamping=, =Pretension=
+- =AngleStressPrimaryThreshold= / =Secondary= (rope/fuse = 360 -> never break on angle)
+- =Node= = StandardNode | CableNode
+
+** Health / combat
+- =HitPoints= (bracing 150, backbracing 100, armor 400, rope 50, shield 60, solar 200)
+- =AbsorptionMomentumThreshold=, =ReflectionMomentumThreshold=, =PenetrationMomentumThreshold=
+- =CollidesWithFriendlyProjectiles= / =CollidesWithEnemyProjectiles=
+- =CollidesWithFriendlyBeams= / =CollidesWithEnemyBeams=
+- =ReflectsBeams=, =BeamPenetrationBlockDist=
+- =CatchesFire=, =DegreesPerSecondMin/Max= (fire spread speed ALONG the strut)
+
+** Economy
+- =MetalBuildCost=, =EnergyBuildCost=, =MetalRepairCost=, =EnergyRepairCost=
+- =MetalReclaim=, =EnergyReclaim=, =EnergyRunCost=, =BuildTime=, =ScrapTime=
+
+** Concrete values that confirm our model
+| Material | HP | Stiffness | Mass | MaxComp/Exp | Metal/Energy build | Fire deg/s |
+|---------------+-----+-----------+------+-------------+--------------------+------------|
+| bracing (wood)| 150 | 200000 | 0.25 | 0.90 / 1.10 | 0.1 / 0.5 | 20..32 |
+| backbracing | 100 | 200000 | 0.25 | 0.92 / 1.08 | 0.1 / 0.5 | 64..92 |
+| armor | 400 | 250000 | 0.50 | 0.90 / 1.10 | 1.8 / 1.5 | n/a |
+| rope | 50 | 50000 | 0.001| 0.60 / 1.50 | 0.1 / 0.5 | 20..100 |
+
+Notes:
+- Wood +/-10% deformation break == our axial snap rule. Armor 25% stiffer + 2x
+ mass == our research. 3 wood (450 HP) slightly > 1 armor (400 HP) == research.
+- *Background bracing* has =CollidesWith{Friendly,Enemy}Projectiles = false= and
+ =CollidesWithFriendlyBeams = false= but =CollidesWithEnemyBeams = true=:
+ projectiles pass straight through it; enemy LASERS still hit it. And it burns
+ 3x faster (64..92 vs 20..32 deg/s) — exactly why fire counters the
+ "hide supports as background bracing" trick.
+- Rope: =AngleStressPrimaryThreshold = 360= (no angle break), tension element.
+
+* Fire system (=data/db/constants.lua=, table =Fire=)
+
+| Field | Value | Meaning |
+|-------------------------------+-------+---------------------------------------|
+| DamageHitpointsPerSecond | 0.5 | burn DoT on struts |
+| DamageHitpointsPerSecondDevice| 2.5 | burn DoT on devices (5x faster) |
+| DegreesPerSecondMin/Max | 20/32 | base spread speed (per-material overrides) |
+| IgnitionTemp / MaxTemp | 100/150 | |
+| SegmentLength | 15 | fire is segmented along the strut |
+| Alarm.Delay | 7 | "fort on fire" alarm after 7s |
+
+Fire is a per-strut DoT that propagates strut-to-strut at a per-material rate,
+independent of the HP/stress systems. Background bracing burns fastest.
+
+* Extrusion (drag-build) constants (=data/db/constants.lua=, table =Extrusion=)
+
+| Field | Value | Our equivalent / note |
+|-----------------+-----------+------------------------------------------|
+| DefaultMaterial | "bracing" | |
+| MinAngle | 20 | min divergence angle of the new box |
+| MinOffset | 40 | min drag distance to start a box (cm) |
+| SnapAngle | 6 | drag direction snaps to 6-deg increments |
+| MaxLengthGrace | 500 | |
+
+Confirms drag-build is angle-flexible (not forced perpendicular) with a snap and
+a minimum size — matches what we built (free drag offset + MIN_BRACE_LENGTH).
+
+* Modding architecture
+
+** Layered loading
+1. Base =data/...= loads first, populating global tables (=Materials=,
+ =Weapons=, =Devices=, =Projectiles=, =Physics=, =Fire=, ...).
+2. Each active mod runs ON TOP, mutating those globals.
+3. Priority (=mod.lua=, 1..10, default 5; higher = loads later = final say),
+ then alphabetical. (Some workshop mods use larger numbers, e.g. 99.)
+4. =RegisterApplyMod(fn)= defers =fn= until AFTER all mods load, so a mod can
+ modify content added by later-loading mods.
+
+** Mod file layout (mirrors the base data tree)
+#+begin_src text
+<mod>/
+├── mod.lua # Selectable=true, Priority=N, Category="..." [, AIFortSpecialisation]
+├── displayname.lua # DisplayName = { ['English']=L"...", ... } (often UTF-16)
+├── publishedfileid.lua, itemversion.lua, preview.jpg # Steam Workshop metadata
+├── db/constants.lua # override physics/fire constants
+├── materials/building_materials.lua # add/modify materials
+├── weapons/weapon_list.lua, projectile_list.lua, <weapon>.lua
+├── devices/device_list.lua, <device>.lua, <device>/*.dds|png # + art
+└── ...
+#+end_src
+The loader injects a =path= variable = the mod's root, used for asset/file refs
+(e.g. =FileName = path.."/devices/ballast.lua"=).
+
+** mod.lua manifest (minimal)
+#+begin_src lua
+Selectable = true
+Priority = 6
+Category = "Devices" -- e.g. "Combat", "Physics", "Disable/Weapons"
+#+end_src
+
+** The mod API (global Lua functions exposed by the engine)
+| Function | Purpose |
+|--------------------------------------------+----------------------------------------|
+| =IndexOfMaterial/Weapon/Device(saveName)= | find an item's position in its list |
+| =FindMaterial/FindWeapon/FindProjectile(n)=| get an item table to mutate |
+| =InsertMaterialBefore/Behind(saveName, t)= | insert relative to an existing item |
+| =table.insert(Devices, IndexOfDevice(x)+1, t)= | the raw add pattern |
+| =MergeLists(t1, t2)= | concat list tables |
+| =RegisterApplyMod(fn)= / =DeregisterApplyMod=| run fn after all mods load |
+| =InheritMaterial(base, overrides)= | clone a base material with overrides |
+| =L"..."= | localized string literal |
+
+** Three concrete mod patterns observed
+1. *Disable* (=disable_weapon_cannon=): override file does
+ =local w = FindWeapon("cannon"); w.Enabled = false=.
+2. *Tweak* (commander mods): set globals + =RegisterApplyMod(fn)= where =fn=
+ scales the already-loaded weapon's fields (=FireStdDev=, =KickbackMean=...).
+3. *Add* (=fsballast= device mod): =table.insert(Devices, IndexOfDevice(
+ "repairstation")+1, { SaveName=..., FileName=path.."/devices/ballast.lua",
+ MetalCost=50, EnergyCost=250, ... })= plus the device's own definition file
+ (sets =Mass=, =HitPoints=, =Sprites=, =Root= scene-graph) and art.
+
+* What to adopt for our clone
+
+- *Data-driven materials/weapons/devices*: move the hard-coded material table in
+ =app.cpp= into Lua data (sol2), keyed by =SaveName=, with the field schema
+ above. Engine reads the tables at startup. (Tech-stack already plans sol2.)
+- *Numbers*: adopt the relative relationships (HP 150/100/400/50, armor 25%
+ stiffer + 2x mass, bg-brace burns ~3x faster, wood +/-10% break) even if our
+ absolute scale differs (we use world units, not cm).
+- *Layered loader*: base tables -> mods by (priority, name) -> =RegisterApplyMod=
+ pass. Inject a =path= per mod. Mirror the base directory tree.
+- *Reconsider an angle-stress break* (30/15 deg) alongside our force-based axial
+ break — it is in the real game and cheap to add.
+- *Fire (M4)*: per-strut DoT (0.5 hp/s, 2.5 for devices) propagating along
+ edges at a per-material deg/s rate; bg-brace fastest.
+- *Background bracing collision flags*: projectiles pass through, enemy beams
+ hit, fire reaches it — model these as per-material booleans, not a hard-coded
+ special case.
+#+begin_src text
+(Do NOT copy Forts' Lua/assets into the repo — reference only. Our own data
+ files should be written from scratch using this schema.)
+#+end_src
diff --git a/research/forts-gameplay.org b/research/forts-gameplay.org
new file mode 100644
index 0000000..549c0d1
--- /dev/null
+++ b/research/forts-gameplay.org
@@ -0,0 +1,409 @@
+#+TITLE: Forts — Gameplay Mechanics Research
+#+AUTHOR: Forts Clone Project
+#+DATE: 2025-06-19
+#+OPTIONS: toc:3 num:t
+
+* Overview
+
+Forts is a 2D physics-based real-time strategy game developed by EarthWork Games
+(Australia), released April 19, 2017 on Steam. Over 1 million copies sold, 91-92%
+positive reviews (22,000+). Described as "Worms meets bridge-building meets RTS."
+
+Core tagline: ~Build, research, and blast your opponent's fort to rubble.~
+
+* Core Game Loop
+
+1. Build resource structures (mines on metal deposits, wind turbines for energy)
+2. Construct your fortress from physics-simulated materials
+3. Research the tech tree to unlock weapons and devices
+4. Fortify defenses and position weapons
+5. Fire weapons manually at the enemy's weak points
+6. Destroy the enemy's Reactor (core) to win
+
+The loop is real-time, not turn-based. All construction happens under fire — you
+build and fight simultaneously. The single-pointer multitasking model means
+you're constantly switching between building, repairing, aiming, and managing
+resources.
+
+* Win Conditions
+
+- Primary: Destroy the opponent's Reactor (core)
+- Methods to destroy the reactor:
+ - Direct damage (lasers, cannons, missiles that hit the reactor)
+ - Indirect damage (fire spread, splash damage that reaches the reactor)
+ - Structural collapse (destroying supports so the reactor falls — falling
+ damage destroys it)
+- Some DLC/custom missions add capture-point objectives
+
+* Resources & Economy
+
+**Resources**
+
+| Resource | How to Obtain | Used For |
+|-------------+----------------------------------------------------------+-----------------------------------|
+| Metal | Mines placed on metal deposits scattered across the map | Building blocks, weapons, devices |
+| Energy | Wind turbines (higher placement = more energy) | Powering weapons, shields, devices|
+| Oil | Campaign narrative focus, not a direct gameplay resource | Story context |
+| Rare Metal | Moonshot DLC resource for reactor upgrades | Advanced tech |
+
+**Resource Mechanics**
+- Metal is a continuous trickle — more mines = more metal/sec
+- Energy scales with turbine height AND lack of obstruction (build tall, build
+ free of blockage for max efficiency)
+- Energy is stored in Battery devices
+- Metal is stored in Storage devices
+- All construction costs both metal and energy
+- Repairing damaged structures costs resources proportional to damage
+
+* Building & Construction
+
+**Core Building Philosophy**
+- Free-form, real-time construction — not grid-snapped, not pre-placed
+- Every plank, beam, rope, and panel is a separate physics body
+- Structures must be anchored to something stable or they collapse
+- You can build literally any shape and size
+- Construction is a skill — good builders create stable, defensible forts;
+ bad builders create structures that collapse under their own weight or from
+ a single well-placed shot
+
+**Known Building Materials**
+
+Categorized by function:
+
+| Category | Materials | Properties |
+|-----------------+----------------------------------------------+------------------------------------|
+| Structural | Wooden beams, Metal beams, Planks | Basic building blocks, have HP |
+| Reinforcement | Metal plates, Armor panels | Higher HP, resist damage types |
+| Tension | Ropes/Cables | Connect points, provide stability |
+| Defense | Sandbags, Doors (openable/closable) | Absorb/redirect damage |
+| Resource | Batteries, Metal Storage | Store energy/metal buffers |
+| Functional | Wind Turbines, Mines, Tech Buildings | Generate/process resources |
+
+**Rope/Cable Mechanics (from Chinese wiki)**
+- Cost: 5 metal + 25 energy per cell
+- HP: 50 per segment
+- Vulnerable to: fire, cutting weapons
+- Primary use: suspending cannons, reactors, and heavy structures
+- Provides tension stability — a rope under tension keeps structures upright
+- If a rope is destroyed, whatever it was supporting may collapse
+- Can catch fire and burn through
+
+**Structural Integrity**
+- Every piece has independent physics — not a unified "health bar" for the fort
+- Destroying key support pieces causes cascading collapse
+- A well-placed shot on a load-bearing beam can bring down an entire weapon
+ platform, reactor, or section of the fort
+- This is THE core strategic mechanic — you're not just depleting HP, you're
+ finding and exploiting structural weaknesses
+
+**Building Actions**
+- Build: Place a material piece, costs resources instantly
+- Repair: Fix damaged piece, cost proportional to damage amount
+- Recycle/Delete: Remove piece, recover partial resources
+
+**Physics Properties of Buildings**
+- Gravity applies to everything
+- Pieces have mass, friction, collision shapes
+- Weight distribution matters — top-heavy forts tip over
+- Pieces connected via joints (distance joints, revolute/weld equivalents)
+- Destruction of joints triggers cascading failure
+
+* Combat Systems
+
+**Weapons — 16+ base weapons across base game and DLCs**
+
+| Weapon | Type | Characteristics |
+|-------------------+-------------+----------------------------------------------------|
+| Machine Gun | Hitscan | High fire rate, low building damage, can shoot down|
+| | | incoming mortars/missiles (point-defense) |
+| Sniper Rifle | Hitscan | Precision, high single-point damage, used to guide |
+| | | Swarm Missiles to target |
+| Cannon | Projectile | Heavy damage, rips through armor layers |
+| Mortar | Projectile | High building damage, slow projectile, arcing shot |
+| Swarm Missiles | Guided | Self-aiming cluster, accurate, tracks targets |
+| Plasma Laser | Beam | Cuts through entire bases in a line, countered by |
+| | | energy shields |
+| Flak | Point-Def | Anti-projectile, shoots down incoming shells |
+| EMP Missile | Special | Disables devices/weapons temporarily |
+| Minigun | Hitscan | Extreme fire rate, suppression |
+| Howitzer | Projectile | Moonshot DLC — heavy artillery |
+| Smoke Bombs | Utility | Moonshot DLC — obscures vision |
+| Magnabeam | Beam | Moonshot DLC — magnetic manipulation |
+| Buzzsaw | Melee/Phys | Moonshot DLC — physical cutting |
+| Dome | Shield | High Seas DLC — protective energy dome |
+| Orbital Laser | Beam | High Seas DLC — orbital strike |
+| Deckguns | Projectile | High Seas DLC — naval cannons |
+| Naval Missiles | Projectile | High Seas DLC — ship-launched missiles |
+
+**Damage Types**
+- Kinetic (bullets, shells): splashes, penetration
+- Energy (lasers, plasma): cuts linearly, blocked by shields
+- Fire: spreads, burns ropes and wood, continuous damage over time
+- Explosive: area damage, knocks pieces loose, stress damage
+- EMP: disables without physical destruction
+- Physical/Falling: collision damage from collapsing structures
+
+**Aiming Model**
+- All weapons are manually aimed by the player
+- No auto-targeting (except Swarm Missiles and point-defense)
+- Aiming is skill-based — angle, power, and timing matter
+- The sniper uses a 2D scope view
+- Weapons have limited firing arcs based on mounting position
+
+**Defensive Systems**
+- Armor panels: absorb kinetic damage
+- Energy shields: block lasers and plasma
+- Flak: shoot down incoming projectiles
+- Sandbags: cheap damage absorption
+- Machine guns: can be set to point-defense mode
+- Structural redundancy: building with multiple load paths so one broken
+ connection doesn't collapse everything
+
+* Commanders & Factions
+
+**12+ Commanders (base game + DLCs)**
+
+Each commander has unique active abilities and passive traits:
+
+| Commander | Theme / Playstyle |
+|----------------+--------------------------------|
+| Warthog | Aggressive firepower |
+| Armourdillo | Defensive resilience |
+| Shockenaugh | Cunning/tactical |
+| Architect | Building mastery |
+| (8+ more) | Various hybrid styles |
+
+Commanders differentiate playstyles — they're like fighting game characters.
+One player's optimal fort design may be completely different from another's
+based on commander choice.
+
+**Campaign Factions (narrative only)**
+- Eagle Empire (fictional USA)
+- Dragon Army (fictional China)
+- Iron Bear Alliance (fictional Russia)
+- Black Penguin Oil Company (antagonist — private military)
+
+In multiplayer, factions are cosmetic/narrative — the mechanical differentiation
+comes from commander choice.
+
+* Tech Tree & Progression
+
+- Starts with basic weapons and materials
+- Research unlocks progressively more powerful weapons, devices, and materials
+- Two strategic paths:
+ 1. Rush tech — invest resources in research, get powerful weapons fast but
+ with a weaker fort
+ 2. Turtle — invest in fortification, outlast the enemy, tech up slowly
+- Tech buildings must be physically built on your fort and can be destroyed
+- Weapons Factory: unlocks weapon production
+- Upgrade Center: improves existing weapons (damage, fire rate, etc.)
+- Tech prerequisites create build-order strategy (like StarCraft tech tree)
+- Research costs both metal and energy
+
+* Game Modes
+
+| Mode | Players | Description |
+|------------------+--------------+-----------------------------------------------|
+| Campaign | 1 | 28 missions + tutorial, 3 factions, story |
+| Skirmish | 1 vs AI | 3 AI difficulty levels |
+| Multiplayer TDM | Up to 8 (2 teams) | Each player has own fort & resources |
+| Multiplayer Co-op| Up to 8 (2 teams) | Team shares base and resources |
+| Ranked 1v1 | 2 | Bi-monthly seasons, leaderboard, medals |
+| Sandbox | 1 | Free build, no enemies, experiment mode |
+
+- ~70 official maps included
+- Hundreds of community-made maps via Steam Workshop
+- Official tournaments (XXII+ held as of 2022) with prize pools
+- Community tournaments including AI vs AI tournaments
+
+* Maps & Environment
+
+**Map Features**
+- 2D side-view maps
+- Terrain with metal deposit nodes scattered throughout
+- Destructible terrain in some maps
+- Background environments with dynamic elements (17+ dynamic backgrounds in
+ High Seas DLC)
+- Water/physics in High Seas DLC — floating naval forts with buoyancy
+
+**Starting Conditions**
+- Each player starts with a Reactor (core) at a fixed position
+- Initial small fort structure around the reactor
+- Nearby metal deposits for initial mining
+- Symmetric or asymmetric map layouts depending on map design
+
+* Physics & Simulation
+
+**Structural Physics (the game's signature feature)**
+- Every building piece is an independent rigid body
+- Pieces connected via joints (distance joints for ropes, weld joints for
+ attached planks, revolute joints for pivoting structures)
+- Joint breaking triggers cascading destruction — this is the core of
+ the gameplay
+- Stress and strain accumulate on connections
+- Weight distribution matters — unbalanced loading causes tilt and collapse
+- Counterweights can stabilize structures (hanging heavy objects off the
+ opposite side)
+
+**Projectile Physics**
+- Real ballistic trajectories (not hitscan except for machine gun/sniper)
+- Gravity affects all projectiles
+- Splash damage has radial falloff
+- Impact force can knock pieces loose from joints
+- Penetration mechanics — some projectiles punch through multiple layers
+
+**Environmental Physics**
+- Water buoyancy (High Seas DLC) — structures float, ships have hulls
+- Fire propagation — spreads along connected flammable materials
+- Wind affects turbine efficiency
+
+**Determinism**
+- The game uses a deterministic physics simulation for multiplayer
+- Lockstep netcode (inferred — common for physics-based RTS games of this era)
+- Same initial state + same inputs = identical simulation on all peers
+
+* Multiplayer & Netcode
+
+**Multiplayer Structure**
+- Up to 8 players per lobby
+- Two teams (any split: 1v1, 2v2, 3v3, 4v4, 4v2, etc.)
+- Team Deathmatch: separate forts
+- Team Co-op: shared base and resource pool
+- Ranked 1v1 with bi-monthly season resets
+- Leaderboard ranks and cosmetic medal rewards
+
+**Inferred Netcode Architecture**
+- Likely deterministic lockstep: all clients run full simulation, only inputs
+ are transmitted
+- This is standard for physics-heavy RTS games (Age of Empires, Spring RTS,
+ Supreme Commander all use this approach)
+- Bandwidth scales with player count and input rate, NOT entity count — can
+ support massive numbers of physics bodies with tiny packets
+- Downside: one slow connection freezes the game for everyone
+
+**Observed Multiplayer Issues**
+- Community reports mention desyncs and lag in some conditions
+- No ranked matchmaking — new players can match against veterans
+- Bugs and balance issues noted in multiplayer reviews
+
+* Modding System
+
+Forts has a deliberate, first-class modding architecture. This is one of the
+most important sections for designing a clone — Forts' moddability is a major
+reason for its longevity.
+
+**Modding Architecture**
+
+- Layered Lua loading system:
+ 1. Base game Lua files load first
+ 2. Each active mod's equivalent files load on top
+ 3. Mods can override or extend variables and tables from earlier files
+
+- Priority system (1-10, default 5):
+ - Lower numbers load first
+ - Higher numbers load last, have "final say"
+ - Same priority: alphabetical order
+
+**Mod File Structure**
+#+begin_src text
+my_mod/
+├── mod.lua # Metadata (name, category, priority, selectable)
+├── db/
+│ └── constants.lua # Physics constants (gravity, drag, max angles)
+├── devices/
+│ ├── device_list.lua # Device definitions, costs, prerequisites
+│ └── my_device.lua # Individual device config
+├── weapons/
+│ ├── weapons_list.lua # Weapon definitions, fire rates, projectiles
+│ └── projectile_list.lua # Projectile damage, splash, multipliers
+├── materials/
+│ └── building_materials.lua # Material properties, HP, costs, build times
+└── ui/textures/
+ └── HUD/ # Custom HUD icons and sprites
+#+end_src
+
+**Key Lua API Functions**
+| Function | Purpose |
+|--------------------------------+--------------------------------------------|
+| =table.insert()= | Add new items to game tables |
+| =IndexOfWeapon()/IndexOfDevice()=| Find existing item position for insertion |
+| =FindProjectile()= | Look up projectile to modify |
+| =RegisterApplyMod()= | Schedule function to run after all mods load |
+| =DeregisterApplyMod()= | Remove previously registered ApplyMod |
+| =ButtonSprite()/DetailSprite()= | Create HUD sprites from textures |
+| =InterpolateTable()= | Interpolate time-keyed data (beam weapons) |
+
+**What Can Be Modded**
+- ✅ New devices (weapons, buildings, tools)
+- ✅ New weapons with custom projectiles
+- ✅ New building materials with custom properties
+- ✅ New projectiles with custom damage/splash/physics
+- ✅ Beam weapons with custom thickness/damage functions
+- ✅ Game balance (costs, damage, build times, production rates)
+- ✅ Physics constants (gravity, drag, max angles)
+- ✅ Visuals (sprites, textures, HUD elements)
+- ✅ Game rules, prerequisites, tech tree requirements
+- ✅ Maps (hundreds of community maps)
+- ✅ AI behavior (AI tournaments exist)
+- ✅ Training missions
+
+**What's NOT Currently Moddable**
+- ❌ Commanders & Factions (special mod type, not Workshop-supported)
+- ❌ Custom campaigns (not yet available)
+- ❌ Environments/backgrounds
+- ❌ Localization/languages
+
+**Modding Tools**
+- Built-in Map Editor
+- VS Code extension: "Forts API extension" (autocomplete, intellisense, docs)
+- Community tools: sprite pivot tools, HUD icon generators, mod starter scripts,
+ structure duplication tools (GitHub: SamsterBirdies/forts-modding-tools)
+
+**Design Lesson for Our Clone**
+The layered Lua loading with priority system is elegant. It allows:
+- Mods to extend (add to tables) or override (replace values)
+- Multiple mods to coexist (priority + alphabetical ordering)
+- Modders to reference other mods' content (RegisterApplyMod runs after all
+ mods load, letting mods modify content added by later-loading mods)
+
+* DLC Content Summary
+
+| DLC | Price | Key Additions |
+|------------------+---------+---------------------------------------------------|
+| Moonshot | $7.99 | 4 weapons, 3 commanders, portals, new gamemode |
+| High Seas | $9.99 | Naval forts, buoyancy physics, 6 weapons, campaign|
+| Pro HUD | $2.99 | UI reskins, new sounds |
+| Tons of Guns | Free | Large weapon pack |
+| Repair Station | Free | Repair robot pet device |
+
+* Key Design Takeaways for Our Clone
+
+1. **Physics is THE feature.** The structural integrity system is what makes
+ Forts special. Building isn't cosmetic — it IS the gameplay. Every support
+ beam matters. Every rope under tension is a potential weakness.
+
+2. **Modding from day one.** Forts shipped with Lua modding and Steam Workshop
+ integration. Our clone should design modding into the core architecture,
+ not bolt it on later.
+
+3. **Manual aiming creates skill expression.** RTS + manual aim is unusual and
+ creates a unique skill ceiling. Players who can build well AND aim well
+ dominate.
+
+4. **Resource nodes drive map strategy.** Metal deposits on maps force players
+ to expand and defend territory — prevents turtling being the only strategy.
+
+5. **Commanders create matchup variety.** 12+ commanders with unique abilities
+ means 144+ matchup permutations in 1v1. This is enormous for replayability.
+
+6. **The single-pointer model is a double-edged sword.** Players must multitask
+ building, repairing, aiming, and resource management with one cursor. Our
+ UI needs to make this feel fluid, not frustrating.
+
+7. **Reactor-as-win-condition creates tense moments.** The reactor is fragile
+ and central. Every battle becomes a desperate defense of your own reactor
+ while trying to expose theirs.
+
+8. **2D side-view simplifies the problem space.** You only need to worry about
+ structural physics in 2 dimensions, which makes determinism and netcode
+ much more tractable than a 3D equivalent.
diff --git a/research/gameplay-gaps.org b/research/gameplay-gaps.org
new file mode 100644
index 0000000..4974ef6
--- /dev/null
+++ b/research/gameplay-gaps.org
@@ -0,0 +1,143 @@
+#+TITLE: Forts Gameplay — What Our Clone Is Missing (sourced)
+#+AUTHOR: Forts Clone Project
+#+DATE: 2026-07-01
+#+OPTIONS: toc:3 num:t
+
+* Purpose
+
+An ENUMERATED, sourced list of gameplay systems Forts has that our clone does
+not — so scope is driven by facts, not vibes. Ground truth is the shipped game
+data (=SteamLibrary/.../Forts/data/=, device_list.lua / weapon_list.lua /
+constants.lua) cross-checked against community docs:
+
+- Steam guide "How to be a PRO at Vanilla Forts" (id 2035024654)
+- Steam guide "Forts Glossary" (id 1380793614) and "Base Building" (1308699887)
+- Forts RTS Wiki: Technology page
+- Wikipedia: Forts (video game)
+
+* What we HAVE (baseline)
+
+Building (wood / bg-brace / rope), mass-spring structural sim with stress +
+axial/angle breaking + cascading collapse, ground destroys debris, fire
+(DoT + spread), HP, two weapons (cannon = ballistic, laser = beam/ignite),
+a static enemy fort, Lua-data + mods, a dev UI. Resources are a placeholder
+(depleting pools, no generation).
+
+* MISSING — grouped by system, each with the in-game reference
+
+** 1. The Reactor + win/loss [CRITICAL — this is the actual game]
+The core the whole game is about. =devices/reactor.lua=: HitPoints 100 (fragile),
+and it is ALSO a generator (EnergyProductionRate 100, MetalProductionRate 5).
+Win = destroy the enemy reactor by (a) direct damage, (b) fire/splash, or
+(c) cutting its supports so it falls. We have no reactor, no win/loss, no restart.
+-> our M5.
+
+** 2. Real resource economy [CRITICAL for the core loop]
+Two resources, generated by devices (all values from =devices/*.lua=):
+- *Metal*: Mines on metal *deposits* (=mine.lua= MetalProductionRate 4,
+ EnergyProductionRate -5 — mining COSTS energy). Upgrade =mine2= (5 metal, -7 e).
+- *Energy*: Wind *Turbines* — output scales with HEIGHT and clearance
+ (v0.1-core.org; MinWindEfficiency / MaxWindHeight fields). Reactor also makes
+ 100 energy + 5 metal baseline.
+- *Storage caps*: Battery (=battery.lua= EnergyStorageCapacity 2000), Metal Store.
+ Resources are capped, not infinite pools.
+- *Costs*: building pieces, weapons, devices, AND FIRING all cost metal/energy
+ (cannon EnergyFireCost 2000, MetalFireCost 50). Repair costs too.
+We have placeholder pools only. -> deferred to our M5.
+
+** 3. Devices (whole category — we have ZERO)
+From =devices/device_list.lua=:
+- Resource: mine, mine2, turbine, turbine2, derrick, battery (energy store),
+ store (metal store), reactor, minireactor.
+- Tech buildings: workshop, armoury, munitions plant, factory, upgrade centre
+ (see #4).
+- Utility/defence: sandbags (HP 300, cheap absorber), explosive barrel (barrel),
+ target (mission objective, HP 30).
+Devices mount on structure, have HP, can be shot, and some need clearance/ground.
+
+** 4. Tech tree + Upgrades [large system, OUT of v0.1 scope but core to Forts]
+5 tech buildings gate weapons (wiki Technology; weapon_list Prerequisite fields):
+- Workshop -> Mortar, Swarm Missiles
+- Armoury -> Flak, EMP Rocket
+- Munitions Plant -> 20mm Cannon, Cannon (needs Armoury)
+- Factory -> Firebeam, Plasma Laser (needs Workshop)
+- Upgrade Centre -> unlocks UPGRADING existing weapons/devices (mine2, turbine2,
+ mortar2, sniper2, minigun … =Prerequisite = "upgrade"=).
+Research/prereqs create build-order strategy. We have neither tech nor upgrades.
+
+** 5. More weapons [M4 continues]
+We have cannon + laser. Full vanilla roster (=weapons/weapon_list.lua=):
+machinegun, minigun (point-defence / anti-projectile), sniper (+spotter
+mechanic, guides swarm missiles), mortar (high-arc, high building damage),
+swarm missiles (guided), missile launcher, 20mm cannon, cannon, flak
+(anti-projectile), EMP rocket (disables devices), firebeam / plasma laser
+(beam). Notable *mechanics* we lack: point-defence (shoot down incoming shells),
+guided/tracking projectiles, hitscan (machinegun/sniper), EMP disable.
+
+** 6. Building materials we lack
+=materials/building_materials.lua=: *armour* (HP 400, +25% stiffness, 2x mass —
+kinetic tank), *door* (openable/closable, blocks then opens for your own fire),
+*energy shield* (reflects beams/plasma, warmup), *metal beam*, *camo*, *solar*
+(energy on the strut), *fuse* (relays fire slowly). We have wood/bg/rope only.
+Doors and shields are real tactical mechanics (open to shoot, shield vs lasers).
+
+** 7. Fort actions: Repair + Recycle [our M7]
+- *Repair*: restore a damaged piece/device for resources proportional to damage
+ (Forts Repair.EnergyCostMultiplier; materials MetalRepairCost/EnergyRepairCost).
+- *Recycle/Scrap*: remove a piece, recover partial resources (MetalReclaim,
+ ScrapTime). We have neither.
+
+** 8. Weapon aiming/firing details we simplified
+- Cannon/weapons cost energy+metal PER SHOT (EnergyFireCost) — firing is
+ resource-limited, not just reload-limited.
+- Recoil/kickback pushes the fort (we apply knockback to targets only, not
+ recoil to the firing structure — Forts Recoil 600000, KickbackMean 40).
+- Spotters: sniper reveals targets and guides swarm missiles.
+- Fire-clearance: a weapon won't fire if its own structure blocks the muzzle
+ (MinFireClearance) — prevents shooting through your own fort.
+
+** 9. Commanders + Factions [OUT of v0.1 scope]
+15 commanders across 5 factions, each with a passive trait + active ability
+(e.g. Firebird halves fire spread & double-extinguishes on her forts). Huge
+matchup variety. Purely a post-v1 concern but defines Forts' identity.
+
+** 10. Map / world systems
+- Metal *deposits* embedded in terrain (mine targets) — we have none.
+- Terrain shape + foundation material chosen by ground *slope angle*
+ (StandardNode.Foundations brackets by angle). Ours is a flat half-plane.
+- No-build zones, destructible terrain, water/buoyancy (High Seas DLC).
+
+** 11. Meta / not-gameplay-but-expected
+Skirmish AI opponent, campaign/missions, multiplayer lockstep netcode,
+replays (trivial with deterministic lockstep), sandbox. All post-v0.1.
+
+* Priority for OUR roadmap
+
+Mapping the above onto the (already-reordered) milestones:
+
+| Gap | Milestone | Notes |
+|---------------------------------------+-----------+--------------------------------|
+| Reactor + win/loss + restart | M5 | the actual objective |
+| Resource economy (mine/turbine/store) | M5 | deposits, generation, caps |
+| Repair + Recycle | M7 | resource-cost actions |
+| Devices framework (mount on struts) | M5/M6 | reactor is the first device |
+| Armour + Door + Shield materials | M4/M7 | data-driven, small each |
+| More weapons (mortar/flak/mg/missile) | M4 | point-defence + guided are new |
+| Per-shot energy cost + recoil | M4 | small, faithful |
+| Tech tree + Upgrades | post-v0.1 | big; build-order strategy |
+| Commanders/Factions | post-v0.1 | defines identity, large |
+| Map deposits / terrain / foundations | M6 | needs a real map format |
+| AI / campaign / multiplayer | post-v1 | out of scope |
+
+* Smallest high-value next steps (concrete)
+
+1. *Reactor as a device + win/loss* (M5 kickoff): a mountable block with HP that,
+ at 0 HP or when disconnected from a foundation, ends the round. Gives the game
+ an actual goal to shoot for. (We already destroy struts and detect
+ foundation-connectivity, so both victory paths are within reach.)
+2. *Real resources*: mines on deposits (+metal, -energy), turbines (+energy by
+ height), storage caps, and make building/firing actually spend them.
+3. *Armour + Door materials*: data-only additions to the Lua material table
+ (armour = high HP/stiff/heavy; door = toggles blocking) — cheap, high value.
+4. *Per-shot energy cost + firing recoil* on the cannon/laser — a few lines,
+ makes combat feel like Forts.
diff --git a/research/tech-stack.org b/research/tech-stack.org
new file mode 100644
index 0000000..ce0a9c1
--- /dev/null
+++ b/research/tech-stack.org
@@ -0,0 +1,487 @@
+#+TITLE: Tech Stack — Locked Choices for Forts Clone
+#+AUTHOR: Forts Clone Project
+#+DATE: 2025-06-19
+#+OPTIONS: toc:3 num:t
+
+* Overview
+
+We are building a 2D physics-based RTS game (Forts-inspired). We explicitly
+reject Godot, Unity, and Unreal. We are building a custom engine. The language
+is C++20/23. This document locks the stack and explains each choice.
+
+* The Stack at a Glance
+
+| Layer | Choice | Link / Reference |
+|------------------+---------------+--------------------------------------|
+| Language | C++20/23 | ISO C++ |
+| Build system | Meson | https://mesonbuild.com |
+| Windowing/Input | SDL3 | https://libsdl.org |
+| Rendering | bgfx | https://github.com/bkaradzic/bgfx |
+| Physics | Box2D v3 | https://box2d.org |
+| ECS | EnTT | https://github.com/skypjack/entt |
+| Networking | ENet | http://enet.bespin.org |
+| | + custom lockstep | (protocol built on top) |
+| Audio | SoLoud | https://solhsa.com/soloud/ |
+| Scripting/Modding| Lua + sol2 | https://github.com/ThePhD/sol2 |
+| Debug UI | Dear ImGui | https://github.com/ocornut/imgui |
+| Math | glm | https://github.com/g-truc/glm |
+| Profiling | Tracy | https://github.com/wolfpld/tracy |
+
+* Why C++20/23
+
+- Maximum library ecosystem — every library we need has first-class C or C++
+ bindings
+- Operator overloading keeps math readable (=pos += vel * dt=)
+- Stable ISO standard — no breaking changes to chase every 6 months
+- Mature debugging and profiling tooling (Visual Studio, GDB, Tracy, perf,
+ VTune, RenderDoc)
+- sol2 (Lua binding) is C++ only — massive productivity win at the engine↔mod
+ boundary
+- EnTT ECS is idiomatic C++ — ergonomic, header-only, battle-tested
+- Zig was considered for its build system and cross-compilation, but the
+ C++ library advantage wins for a multi-year project. Zig's breaking changes
+ every 6 months are a real cost. If Zig reaches 1.0 and the project is mature,
+ migration is feasible since both languages consume the same C libraries.
+
+* Build System: Meson
+
+** Why Meson
+
+- Clean, readable syntax — not CMake's turing-tarpit DSL
+- Fast — Ninja backend by default
+- First-class dependency management via WrapDB and =meson wrap=
+- Excellent subproject support for vendoring libraries
+- Cross-compilation support via cross-files
+- Built-in support for precompiled headers, unity builds, sanitizers
+- Growing adoption in game industry (SDL, Mesa, systemd, GNOME)
+- Meson wraps for our dependencies (simplified — some may need manual setup):
+
+| Dependency | Wrap / Integration |
+|-------------+---------------------------------------------|
+| SDL3 | =dependency('sdl3')= — pkg-config or wrap |
+| bgfx | =subproject('bgfx')= — build from source |
+| Box2D v3 | =subproject('box2d')= — build from source |
+| EnTT | Header-only — =include_directories= |
+| ENet | =subproject('enet')= or =dependency('libenet')=|
+| SoLoud | =subproject('soloud')= — build from source |
+| Lua | =subproject('lua')= or system package |
+| sol2 | Header-only — =include_directories= |
+| Dear ImGui | =subproject('imgui')= — build from source |
+| glm | Header-only — =include_directories= |
+| Tracy | =subproject('tracy')= if desired |
+
+** meson.build sketch
+#+begin_src meson
+project('forts-clone', 'cpp',
+ version: '0.1.0',
+ default_options: [
+ 'cpp_std=c++20',
+ 'warning_level=3',
+ 'buildtype=debugoptimized',
+ ]
+)
+
+# Core dependencies
+sdl3_dep = dependency('sdl3')
+bgfx_dep = dependency('bgfx')
+box2d_dep = dependency('box2d')
+enet_dep = dependency('libenet')
+soloud_dep = dependency('soloud')
+lua_dep = dependency('lua5.4')
+entt_dep = declare_dependency(include_directories: 'subprojects/entt/include')
+sol2_dep = declare_dependency(include_directories: 'subprojects/sol2/include')
+imgui_dep = dependency('imgui')
+glm_dep = declare_dependency(include_directories: 'subprojects/glm')
+tracy_dep = dependency('tracy', required: get_option('tracy'))
+
+engine_lib = static_library('engine', ...)
+executable('forts-clone', 'src/main.cpp', link_with: engine_lib, ...)
+#+end_src
+
+* Rendering: bgfx
+
+** Why bgfx
+
+- Abstracts D3D11, D3D12, Vulkan, Metal, OpenGL, OpenGL ES, WebGPU behind a
+ single C++ API
+- Used in production: AAA game tools, editor tooling, shipped games
+- Shader compilation toolchain (=shaderc=) — write shaders once, compile to
+ all backends
+- Multi-threaded rendering support (submit from any thread)
+- Maintained by Branimir Karadžić (industry veteran, active on GitHub)
+- We will build our own 2D rendering layer on top: sprite batcher, debug
+ drawing, particle renderer, UI rendering
+- Debug rendering: integrate bgfx with Dear ImGui via bgfx's ImGui backend
+- Higher initial cost than raylib/SDL, but maximum portability and control —
+ we can target Vulkan/Metal/D3D12 for performance, or OpenGL for simplicity,
+ without changing render code
+
+** 2D Renderer Design (built on bgfx)
+#+begin_src text
+bgfx layer (GPU API abstraction)
+ ├── SpriteBatcher — batching 2D quads with texture atlases
+ ├── DebugDraw — lines, circles, boxes for physics debug
+ ├── ParticleRenderer — GPU particle system
+ ├── TextRenderer — bitmap font or SDF text
+ └── ImGuiRenderer — Dear ImGui integration (bgfx backend exists)
+#+end_src
+
+** bgfx Integration Notes
+- bgfx does not handle window creation — we use SDL3 for that
+- SDL3 creates the window, we pass the native window handle to bgfx
+- bgfx + SDL3 is a well-trodden path (bgfx ships SDL examples)
+- Shader workflow: write =.sc= shaders → =shaderc= compiles to all backends →
+ embed in binary or load from disk
+
+* Windowing & Input: SDL3
+
+** Why SDL3
+
+- SDL3 stable released January 2025 — modern API refresh
+- Provides: window creation, input (keyboard, mouse, gamepad), OpenGL/Vulkan
+ context creation, audio (basic), threading, file I/O
+- We use SDL3 for windowing and input; rendering goes through bgfx (SDL3 creates
+ the window, bgfx renders into it)
+- =SDL_GameController= API for gamepad support
+- Cross-platform: Windows, macOS, Linux, and beyond
+- Mature, battle-tested in thousands of games (Valheim, Factorio, etc.)
+- Can use SDL3's audio subsystem as fallback if SoLoud has issues on a
+ specific platform
+
+** Input Architecture
+#+begin_src text
+SDL3 event loop
+ ├── SDL_Event → InputSystem (buffered action map)
+ │ ├── Keyboard → action map (build, fire, select, etc.)
+ │ ├── Mouse → aim cursor, click-to-fire, drag-to-build
+ │ └── Gamepad → mapped to same actions
+ └── Raw SDL events also passed to ImGui for debug UI
+#+end_src
+
+* Physics: Box2D v3
+
+** Why Box2D v3
+
+- Author: Erin Catto (Blizzard physics lead, industry standard)
+- 2D rigid body physics — exactly our domain
+- Joint types map directly to building mechanics:
+ - =b2DistanceJoint= → ropes/cables (maintain fixed distance between two points)
+ - =b2WeldJoint= → rigid connections (planks attached to beams)
+ - =b2RevoluteJoint= → hinges, pivoting structures
+ - =b2PrismaticJoint= → sliding connections
+- Cross-platform deterministic (official, August 2024):
+ - Algorithmic determinism (no random numbers in library)
+ - Multi-threaded determinism (bit-array merging of worker results)
+ - Cross-platform determinism (no fast-math, no FMA, custom trig functions)
+- =b2DestructionListener= callback when joints/bodies destroyed → cascading
+ collapse mechanics
+- Bodies auto-destroy attached joints when destroyed
+- C API with C++ headers — clean integration
+
+** Structural Physics Mapping (Box2D → Game Concepts)
+| Box2D Feature | Game Mechanic |
+|----------------------------+--------------------------------------------|
+| =b2Body= (dynamic) | Individual building piece (beam, plank) |
+| =b2Body= (static) | Terrain, anchored foundations |
+| =b2DistanceJoint= | Rope/cable between two points |
+| =b2WeldJoint= | Rigidly connected planks/beams |
+| =b2RevoluteJoint= | Pivot/hinge for swinging structures |
+| =b2DestructionListener= | Detect when a support breaks → cascade |
+| =b2ContactListener= | Collision damage, projectile impacts |
+| =b2Body::ApplyForce()= | Explosion knockback, wind, impacts |
+
+** Determinism & Lockstep
+- Box2D v3's cross-platform determinism is the foundation for our lockstep
+ multiplayer
+- Use default Box2D settings: no fast-math compiler flags, no FMA
+ (-ffp-contract=off on Clang/GCC), Box2D's custom =sinf=, =cosf=, =atan2f=
+- NOTE: Box2D v3 does NOT yet support rollback determinism (for client-side
+ prediction). This is on Erin's roadmap. For v1, standard lockstep is sufficient.
+
+** Determinism Requirements (Box2D)
+- Fixed timestep: always step with constant =deltaTime= (e.g., 1/60)
+- No random forces in simulation
+- Bodies and joints processed in deterministic order (by creation ID)
+- All peers use identical Box2D compilation flags
+
+* ECS: EnTT
+
+** Why EnTT
+
+- Header-only, C++17/20 — drop it in, no build step
+- Sparse-set based storage — efficient for our entity counts (~1K-10K)
+- Excellent API: =registry.view<T>()=, =registry.group<T, U>()=, signals,
+ observers
+- Used in production: Minecraft Bedrock Edition uses EnTT
+- Good for dynamic entity creation/destruction (building pieces are created
+ and destroyed constantly)
+- simdjson's author also maintains EnTT — quality codebase
+- Type-safe, compile-time validated queries
+
+** Entity Plan for v0.1
+| Entity Type | Components | ~Count |
+|-----------------+--------------------------------------------+-----------|
+| BuildingPiece | Transform, BodyRef, Material, Health, Fire | ~500-2000 |
+| Projectile | Transform, BodyRef, Damage, Owner | ~10-50 |
+| Weapon | Transform, WeaponState, Owner, Cooldown | ~5-20 |
+| Reactor | Transform, BodyRef, Health, Owner | ~2 |
+| ResourceNode | Transform, ResourceType, Rate | ~5-10 |
+| Effect | Transform, EffectType, Lifetime | ~varies |
+
+** EnTT Integration Notes
+- Each entity in EnTT gets a =b2BodyId= component pointing to its Box2D body
+- Physics system iterates Box2D bodies, reads/writes transform components
+- EnTT is NOT the physics world — Box2D owns physics state, EnTT owns game
+ state. They reference each other by ID.
+- =entt::registry= is the central game state container
+- Line-of-sight queries, area queries, and other non-physics spatial lookups
+ can use a separate spatial index (simple grid or quadtree) keyed by entity ID
+
+* Networking: ENet + Custom Lockstep
+
+** Why ENet
+
+- Lightweight, battle-tested UDP library (Minecraft classic, many indie games)
+- C library, trivially linked
+- Provides reliable and unreliable channels over UDP
+- Simple API: ~10 functions to learn
+- Peer-to-peer and client-server modes
+- We use it as the transport layer; lockstep protocol is built on top
+
+** Lockstep Protocol (Built on ENet)
+
+#+begin_src text
+Tick N (at fixed rate, e.g., 60Hz):
+
+ 1. Collect local player input for tick N+K (K = buffer depth)
+ → Serialize input into command packet
+ → Send via ENet unreliable channel to all peers
+
+ 2. Input buffer: store incoming commands from peers
+ → Commands are tagged with the tick they apply to
+ → Commands for tick N were sent at tick N-K
+
+ 3. At tick N, extract all peer commands for tick N from buffer
+ → If any peer's command is missing: stall (show "Waiting for player X")
+ → With K=2 at 60Hz, you have ~33ms before stalling
+
+ 4. Execute all commands: apply to deterministic simulation
+ → Advance Box2D world by dt = 1/60
+ → Run all game systems in deterministic order
+
+ 5. (Every ~20 ticks) Compute state checksum, compare with host
+ → Mismatch → desync detected → resync or disconnect
+
+ 6. Render the current state (interpolated for smooth display)
+#+end_src
+
+** Input Buffer Design
+- Buffer depth K = 2 ticks at 60Hz = 33ms input latency
+- Acceptable for an RTS (not a fighting game)
+- Can increase K for laggier connections (configurable)
+- ENet unreliable channel for input (latest input supersedes lost packets,
+ no point retransmitting stale input)
+- ENet reliable channel for: join/leave, chat, checksum verification,
+ game setup
+
+** Deterministic Lockstep Requirements
+- Fixed tick rate (60Hz)
+- Box2D v3 with deterministic settings (see Physics section)
+- Deterministic entity spawn/despawn ordering (sorted by entity ID)
+- PCG family RNG with shared seed (not =rand()=)
+- No system calls in simulation path (no =malloc=, no =gettimeofday=,
+ no filesystem access during tick)
+- All peers must have identical game data (same Lua files, same mods loaded
+ in same order)
+
+** GGPO-Style Rollback (Future Enhancement)**
+- Requires: fast state serialization/deserialization + ability to simulate
+ many frames per tick
+- Benefit: no freezing when packets delayed — game stays responsive
+- Only pursue after v1.0 lockstep is stable
+
+* Audio: SoLoud
+
+** Why SoLoud
+
+- Designed specifically for games by a game developer (Jari Komppa)
+- Extremely simple API — play a sound in ~3 lines
+- Built-in DSP effects chain: filters, reverb, echo, chorus, distortion
+- 3D spatial audio available (though we mostly use 2D panning)
+- Bus system for sub-mixing (SFX bus, Music bus, Ambience bus)
+- No memory allocation in the audio thread — predictable performance
+- Multi-backend: WASAPI, XAudio2, ALSA, PulseAudio, Core Audio, and more
+- Zlib/libPNG license — completely free, no revenue limits
+- Format support: WAV, MP3, OGG (via bundled minimp3 and stb_vorbis)
+
+** Audio Architecture
+#+begin_src text
+SoLoud engine
+ ├── SFX bus
+ │ ├── Weapon fire sounds (cannon, laser, flak)
+ │ ├── Impact/hit sounds (projectile hits building)
+ │ ├── Building sounds (place, break, collapse)
+ │ └── UI sounds (click, hover, alert)
+ ├── Music bus
+ │ └── Background music (streamed OGG)
+ └── Ambience bus
+ └── Wind, fire crackle, environmental
+#+end_src
+
+* Scripting & Modding: Lua + sol2
+
+** Why Lua + sol2
+
+- Forts uses Lua for modding — modders already know the language
+- sol2: header-only C++ binding library — expose C++ classes/functions/tables
+ to Lua with minimal boilerplate
+- Used in production modding frameworks (ModEngine2 for Souls games, Leadwerks
+ Engine, many custom engines)
+- Standard Lua (PUC-Rio 5.4) is simple, well-documented, and fast enough
+- Option to use LuaJIT later for 10-50x performance if needed
+- Layered loading with priority system (Forts model) — documented below
+
+** Mod File Structure (Forts-Inspired)
+#+begin_src text
+my_mod/
+├── mod.lua # name, priority, category, selectable flag
+├── db/
+│ └── constants.lua # physics overrides (gravity, drag, etc.)
+├── devices/
+│ ├── device_list.lua # device definitions (costs, prereqs)
+│ └── my_device.lua # individual device config
+├── weapons/
+│ ├── weapons_list.lua # weapon definitions (fire rate, projectile)
+│ └── projectile_list.lua # projectile data (damage, splash, speed)
+├── materials/
+│ └── building_materials.lua # material properties (HP, cost, flammability)
+└── assets/
+ ├── textures/ # custom sprites, HUD icons
+ └── sounds/ # custom sound effects
+#+end_src
+
+** Layered Loading Protocol
+1. Base game Lua files load first
+2. Mods sorted by priority (1-10, default 5) then alphabetically
+3. Each mod's files load on top — can override values or =table.insert()= to
+ extend tables
+4. Higher priority loads later, has "final say"
+5. =RegisterApplyMod(callback)= — schedule a function to run after ALL mods
+ have loaded, letting mods modify content added by later-loading mods
+6. Virtual filesystem: check highest-priority mod first for assets, fall back
+ to base game
+
+** Engine ↔ Lua Boundary
+- Engine exposes C++ functions to Lua via sol2 (spawn entity, get resource,
+ fire weapon, etc.)
+- Lua defines DATA tables (weapon stats, material properties, device configs)
+ → engine reads these at startup
+- Lua can register game event callbacks (on_build, on_damage, on_projectile_hit)
+- Hot-reload: watch mod directories, reload Lua when files change during
+ development
+
+* Debug UI: Dear ImGui
+
+** Why Dear ImGui
+
+- Industry standard for game debug/editor UI
+- Immediate-mode: simple to add debug controls, inspectors, and data views
+- bgfx integration exists (=bgfx/examples/common/imgui=)
+- Used for: physics debug overlay (draw Box2D bodies/joints), ECS inspector,
+ network stats overlay, resource graph, weapon/material property editor,
+ console/command input, mod reload button
+
+* Math: glm
+
+** Why glm
+
+- Header-only C++ math library, OpenGL convention
+- =glm::vec2=, =glm::mat4=, =glm::rotate()=, etc.
+- Operator overloads: =pos += vel * dt= reads naturally
+- Box2D v3 uses its own =b2Vec2= — we bridge: =glm::vec2 ↔ b2Vec2= at the
+ boundary layer (ECS↔physics)
+- glm for rendering transforms, Box2D types for simulation
+
+* Profiling: Tracy
+
+** Why Tracy
+
+- Real-time C++ profiler with GUI client
+- Instrumentation macros: =ZoneScoped=, =FrameMark=, =TracyPlot=
+- View per-frame CPU usage, find bottlenecks in physics/render/simulation ticks
+- Works with bgfx GPU profiling
+- Lightweight when not connected (zones become no-ops)
+- Essential for maintaining 60fps with growing entity counts
+
+* Asset Pipeline
+
+** Source Assets
+- Sprite sheets / individual PNGs for building pieces, weapons, projectiles
+- Lua files for game data (weapons, materials, devices)
+- GLSL shaders → compiled via =shaderc= to bgfx format
+- Sound effects (WAV/OGG) and music (OGG)
+- Map data (custom binary or text format — TBD)
+
+** Build-Time Processing
+- Texture atlasing: pack individual sprites into atlas textures (tool TBD —
+ possibly =texturepacker= or custom)
+- Shader compilation: =shaderc= compiles =.sc= shaders to all GPU backends
+- Asset cooking: convert source assets to optimized runtime formats
+- Assets embedded or packed alongside binary
+
+** Runtime: Virtual Filesystem
+- Mount points: base game assets + active mods (priority-ordered)
+- File resolution: highest-priority mod first → fall back to base game
+- Mods override individual assets without copying entire base game
+
+* Deterministic Simulation Design
+
+For lockstep multiplayer, the simulation must be exactly identical on all
+machines.
+
+** Required Properties
+1. Same initial state + same inputs → same state after N ticks
+2. Box2D v3 cross-platform determinism (no fast-math, no FMA, custom trig)
+3. Entity update order must be deterministic (sort by ID)
+4. RNG must be synchronized (shared PCG seed sent at game start)
+5. No system calls or external state in simulation path (no =malloc=, no
+ =gettimeofday=, no filesystem access during tick)
+
+** Box2D v3 Determinism Settings
+- Compiler flags: = -ffp-contract=off= (disable fused multiply-add)
+- No fast-math optimizations (= -ffast-math= breaks determinism)
+- Use Box2D's built-in =sinf=, =cosf=, =atan2f= (cross-platform approximations)
+- Fixed timestep: always step with =dt = 1/60.0f=
+- Bodies and joints processed in deterministic order
+
+** State Checksums
+- Every ~20 ticks, hash the full game state (entity positions, velocities,
+ health, fire state, resource counts)
+- Host broadcasts its checksum → all peers compare
+- Mismatch → desync detected → log state for debugging, resync or disconnect
+
+* Reference Projects
+
+Projects to study during development:
+
+| Project | Why Relevant |
+|----------------+--------------------------------------------------------|
+| Spring RTS | C++ physics RTS, Lua scripting, lockstep netcode |
+| Factorio | Deterministic MP at scale, Lua modding, 2D |
+| Cortex Command | 2D physics combat, destructible structures |
+| Starbound | 2D, Lua modding ecosystem |
+| bgfx examples | Reference for bgfx + SDL3 + ImGui integration |
+
+* Open Questions
+
+- [ ] Fixed-point math fallback vs trusting Box2D v3 determinism
+- [ ] Exact rope simulation approach within Box2D (distance joints? soft
+ constraints? custom spring?)
+- [ ] Fire propagation model (graph traversal along connected flammable
+ bodies)
+- [ ] Map format design (heightmap? polygon soup? grid-based or freeform?)
+- [ ] Exact tick rate: 60Hz simulation? 30Hz with interpolation?
+- [ ] Replay system (trivial with lockstep — just record inputs)
+- [ ] Observer/spectator mode (passive peer, receives inputs only)
+- [ ] Package structure: =libengine= (static lib) + =forts-clone= (executable)
+ or header-only engine + thin =main.cpp=?