diff options
| author | Vaino Kauppila <vaino@vke.fi> | 2026-07-02 22:58:25 +0300 |
|---|---|---|
| committer | Vaino Kauppila <vaino@vke.fi> | 2026-07-02 23:47:07 +0300 |
| commit | cd0c08dc7ce754473178ad3f32f7740a1dddc1eb (patch) | |
| tree | 589c676b41d6f3d322ea5ce339ddb0811f29ec31 /research | |
| download | forts_clone-cd0c08dc7ce754473178ad3f32f7740a1dddc1eb.tar.gz forts_clone-cd0c08dc7ce754473178ad3f32f7740a1dddc1eb.zip | |
Initial import: LibreForts (M0-M5 in progress)
Open-source Forts clone: custom C++20 engine + data-driven (Lua) 2D physics
artillery RTS.
- Building: node/strut graph on a stiff mass-spring solver (canon Forts model),
triangulation rigidity, axial + 30-degree angle-stress breaking, cascading
collapse, fire (DoT + spread), ground destroys debris.
- Weapons (M4): cannon (ballistic) + laser (beam/ignite) with select-and-aim-in-
arc UX; splash / beam damage; bg-brace passthrough.
- Devices + economy (M5): reactors + win/loss + restart, mines/turbines/battery,
metal deposits, storage caps, per-shot energy cost.
- Data-driven via Lua/sol2 with a layered mod loader; enemy-fort scenario mod.
- Renderer: SDL3 + textures + ImGui dev UI. stb_image + ImGui vendored.
- Assets: CC0 placeholders only; real game art loaded at runtime from the user's
own install via forts: paths (bring-your-own; nothing copyrighted committed).
See README.md / CLAUDE.md and research/ + requirements/ for detail.
Diffstat (limited to 'research')
| -rw-r--r-- | research/forts-data-reference.org | 193 | ||||
| -rw-r--r-- | research/forts-gameplay.org | 409 | ||||
| -rw-r--r-- | research/gameplay-gaps.org | 143 | ||||
| -rw-r--r-- | research/tech-stack.org | 487 |
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=? |
