diff --git a/.agents/skills/README.md b/.agents/skills/README.md new file mode 100644 index 00000000000..891b465f1f7 --- /dev/null +++ b/.agents/skills/README.md @@ -0,0 +1,82 @@ +# FSO Project Skills + +Vendor-agnostic Agent Skills for the FreeSpace 2 Open engine. Each skill is +a directory containing a `SKILL.md` that encodes an FSO-specific workflow +(conventions, the exact files/functions to touch, and how to verify). Skills +auto-trigger from natural requests; you can also invoke one explicitly by name. + +This directory (`.agents/skills/`) is the single source of truth. Vendor skill +directories contain thin pointer stubs back to it (see "Cross-agent compatibility" +below). + +## Available skills + +| Skill | Use it when you want to… | Key code | Module guide | +| --- | --- | --- | --- | +| `fso-add-table-field` | Add/modify a parsed option in a `.tbl`/`.tbm` (ships, weapons, ai_profiles, game_settings, hud_gauges) and wire it into an `*_info` struct | `code/parse/parselo.*` + the owning module's parser | `documentation/modules/parse.md` | +| `fso-add-sexp` | Add a new SEXP operator (mission-scripting function): `OP_*`, `Operators` table, arg typing, `eval_sexp`, help text | `code/parse/sexp.{h,cpp}` | `documentation/modules/parse.md` | +| `fso-add-lua-api` | Expose engine data/functions to Lua via ADE bindings, or add a `scripting::Hook` mods can subscribe to | `code/scripting/` (`api/`, `global_hooks.*`) | `documentation/modules/scripting.md` | +| `fso-add-object-type` | Introduce a new in-world `OBJ_*` entity with create/move/delete + collision + render | `code/object/object.{h,cpp}` | `documentation/modules/object.md` | +| `fso-add-hud-gauge` | Add a new built-in HUD gauge (`HudGauge` subclass + `HUD_OBJECT_*` + `hud_gauges.tbl` parsing) | `code/hud/hud.h`, `code/hud/hudparse.*` | `documentation/modules/hud.md` | +| `fso-build-and-test` | Configure/build with CMake+Ninja, run `unittests`, and run clang-format/clang-tidy, mirroring CI | `CMakeLists.txt`, `ci/linux/*` | root `AGENTS.md` | + +## Review skills + +| Skill | Use it when you want to… | Invocation | +| --- | --- | --- | +| `thermo-nuclear-code-quality-review` | Run an extremely strict maintainability/abstraction audit of the current branch's changes (file-size growth, spaghetti conditionals, missed "code-judo" simplifications) | Explicit only (`disable-model-invocation`); ask for a "thermo-nuclear review" | + +## Conventions shared by all skills + +- Builds use `FSO_FATAL_WARNINGS=ON` (CI default) — keep changes warning-clean. +- Code must compile on GCC, Clang, and MSVC; isolate platform code under + `code/osapi/`, `code/windows_stub/`, or platform guards. +- New parsed options use `optional_string` (never `required_string`) to preserve + backward compatibility with existing tables. +- Author-facing ship/weapon/SEXP changes may also need FRED updates (`fred2/`, `qtfred/`). +- After any change, validate with the `fso-build-and-test` skill. + +## How skills load + +- **Automatic:** the agent matches a request to a skill's `description` and reads + its `SKILL.md` before acting. +- **Explicit:** reference the skill by name (e.g. "use `fso-add-sexp`"). + +## Cross-agent compatibility + +The skills here live in a vendor-agnostic folder. Each vendor reads its own skills +directory, so the per-vendor directories carry thin pointer stubs (plain files, not +symlinks — symlinks are avoided for Windows checkout compatibility). + +| Path | Agent | Kind | +| --- | --- | --- | +| `.agents/skills/` | opencode (native), shared canonical | full playbooks | +| `.cursor/skills/` | Cursor | pointer stubs → `.agents/skills` | +| `.claude/skills/` | Claude Code, opencode | pointer stubs → `.agents/skills` | + +Project guidance (build/style/test conventions) is shared via the root `AGENTS.md`, +read natively by Cursor and opencode; Claude Code reads it through the root +`CLAUDE.md`, which imports `AGENTS.md`. + +Each stub at `.cursor/skills//SKILL.md` and `.claude/skills//SKILL.md` +carries the discovery frontmatter (`name` + `description`) and a body that points +to the canonical playbook here. Edit the playbook here in `.agents/skills/`; if you +change a skill's `name`/`description`, mirror that line into the matching stubs so +triggering stays in sync. The Cursor stub for `thermo-nuclear-code-quality-review` +also keeps `disable-model-invocation: true` (a Cursor-only field) to preserve its +explicit-only behavior. + +## Authoring more skills + +Use the built-in `create-skill` workflow. Place new project skills here +(`.agents/skills//SKILL.md`); keep descriptions third-person with clear +WHAT + WHEN trigger terms, and link one level deep to the relevant +`documentation/modules/*.md`. Then add a matching pointer stub under +`.cursor/skills//SKILL.md` and `.claude/skills//SKILL.md`. + +## Related documentation + +- Engine architecture overview: `documentation/ARCHITECTURE.md` +- Per-module entry-point guides: `documentation/modules/` +- Build/style/test conventions: root `AGENTS.md` +- Table-option reference (wiki): https://wiki.hard-light.net/index.php/Tables diff --git a/.agents/skills/fso-add-hud-gauge/SKILL.md b/.agents/skills/fso-add-hud-gauge/SKILL.md new file mode 100644 index 00000000000..ff6b63a28df --- /dev/null +++ b/.agents/skills/fso-add-hud-gauge/SKILL.md @@ -0,0 +1,61 @@ +--- +name: fso-add-hud-gauge +description: >- + Add a new HUD gauge to FSO. Use when creating a HUD element/gauge, subclassing + HudGauge, adding a HUD_OBJECT_ type, or wiring a gauge into hud_gauges.tbl + parsing and the per-frame HUD render path. +--- + +# FSO: Add a HUD Gauge + +HUD gauges live in `code/hud/`. Each gauge is a subclass of `HudGauge` +(`code/hud/hud.h`, ~line 214) with a `render()` method; gauges are instantiated +from `hud_gauges.tbl` via `code/hud/hudparse.cpp`. Decide first which path you need. + +## Choose an approach + +- **Mod-authored custom gauge** → usually no engine change: mission/mod authors use + the existing `HUD_OBJECT_CUSTOM` gauge (and Lua HUD-draw hooks). Point the user to + the `fso-add-lua-api` skill + `hud_gauges.tbl` if that suffices. +- **New built-in gauge type** → follow the steps below. + +## Steps (new built-in gauge) + +1. **Subclass `HudGauge`.** Create `hudmygauge.h`/`.cpp` (or add to a related + existing gauge file). Implement the constructor (gauge type, default position, + colors) and override `render(float frametime)` using `gr_*` / `renderString` / + `renderBitmap` helpers from the base class. + +2. **Add a gauge type id.** Add `#define HUD_OBJECT_MY_GAUGE NN` in + `code/hud/hudparse.h` (the `HUD_OBJECT_*` list) and keep the count in sync. + +3. **Parse + load.** In `code/hud/hudparse.cpp`: + - Add a `load_gauge_my_gauge(...)` that reads gauge settings (coords, font, + color, frames) and constructs your gauge, registering it on the ship/HUD. + - Add a `case HUD_OBJECT_MY_GAUGE:` to the load dispatch (the switch around + line 1184) calling `load_gauge_my_gauge(settings)`. + - Map the gauge's table name string to the new id where gauge names are parsed. + +4. **Render path.** Gauges added to the HUD list are rendered automatically each + frame; confirm your gauge is added to the per-ship gauge list so it draws. + +5. **Defaults.** Provide retail-style default coordinates for 640 and 1024 layouts + like the existing gauges, so it works without an explicit `hud_gauges.tbl` entry. + +## Conventions + +- Reuse base-class draw helpers; don't call backend `gr_*` for text/bitmaps if a + `HudGauge::render*` helper exists. +- Respect HUD config (gauge can be toggled); check the gauge's active/visible state. +- Keep warning-clean (`FSO_FATAL_WARNINGS=ON`). + +## Verify + +- Build (see `fso-build-and-test`), start a mission, confirm the gauge renders at + the right place, scales across resolutions, and obeys HUD config show/hide. +- Test a `hud_gauges.tbl` entry that positions/recolors it. + +## Reference + +- `code/hud/hud.h` (`HudGauge`), `code/hud/hudparse.{h,cpp}`, `documentation/modules/hud.md`. +- hud_gauges.tbl docs: https://wiki.hard-light.net/index.php/Hud_gauges.tbl diff --git a/.agents/skills/fso-add-lua-api/SKILL.md b/.agents/skills/fso-add-lua-api/SKILL.md new file mode 100644 index 00000000000..9d7f58f75b8 --- /dev/null +++ b/.agents/skills/fso-add-lua-api/SKILL.md @@ -0,0 +1,74 @@ +--- +name: fso-add-lua-api +description: >- + Expose engine functionality to Lua scripting in FSO via ADE bindings or add a + new scripting hook. Use when adding a Lua API object/library/function/property, + writing ADE_OBJ/ADE_FUNC/ADE_VIRTVAR/ADE_LIB bindings, or creating a + scripting::Hook/OverridableHook that mods can subscribe to. +--- + +# FSO: Add a Lua/ADE Binding or Hook + +FSO's Lua layer (ADE) lives in `code/scripting/`. There are two common tasks: +**A)** expose data/functions to Lua, and **B)** fire an engine event mods can hook. +(For mission-designer scripting use the `fso-add-sexp` skill instead.) + +## A) Add a Lua binding (ADE) + +Bound **objects** live in `code/scripting/api/objs/`, **libraries** in +`code/scripting/api/libs/`. Copy an existing file (e.g. `objs/wing.cpp`) as a model. + +- **Define an object type:** +```cpp +ADE_OBJ(l_MyThing, my_thing_h, "mything", "My thing handle"); +``` +- **Add a method:** +```cpp +ADE_FUNC(doStuff, l_MyThing, "number amount", + "Does stuff to the thing.", "boolean", "true on success") +{ /* parse args with ade_get_args, act, return with ade_set_args */ } +``` +- **Add a gettable/settable property:** +```cpp +ADE_VIRTVAR(Name, l_MyThing, "string", "The name.", "string", "name or empty") +{ /* ... */ } +``` +- **Add to a library:** use `ADE_LIB` / `ADE_LIB_DERIV` and attach functions. + +Argument marshalling uses `ade_get_args(L, "...", ...)` and +`ade_set_args(L, "...", ...)`; format strings are documented in +`code/scripting/ade_args.h`. Match the documented type string in the macro to the +actual returned type, since the Lua API docs are generated from these. + +## B) Add a scripting hook + +1. Declare the hook in `code/scripting/global_hooks.h` and define it in + `global_hooks.cpp`, choosing `scripting::Hook<...>` (non-overridable) or + `scripting::OverridableHook<...>` (script can replace default behaviour). +2. Define any condition/parameter types via the hook's template args (see + `hook_conditions.h` / `hook_api.h`). +3. **Fire it** from the relevant subsystem at the right point: +```cpp +if (scripting::hooks::OnMyEvent->isActive()) { + scripting::hooks::OnMyEvent->run(scripting::hook_param_list(/* params */)); +} +``` + For overridable hooks, check `isOverride(...)` to let scripts suppress default logic. + +## Conventions + +- Keep binding docs accurate — they are the generated Lua API reference (`doc_*`). +- Register script files/tables via `scripting.tbl` (`script_parse_table`). +- Keep warning-clean (`FSO_FATAL_WARNINGS=ON`). + +## Verify + +- Build (see `fso-build-and-test`). +- Test from a Lua script (a `scripting.tbl` `$On Game Init:` block or a hook) that + calls the new API / responds to the new hook. + +## Reference + +- `code/scripting/ade_api.h`, `code/scripting/ade_args.h`, `code/scripting/hook_api.h`. +- `documentation/modules/scripting.md`. +- Lua API wiki: https://wiki.hard-light.net/index.php/Scripting diff --git a/.agents/skills/fso-add-object-type/SKILL.md b/.agents/skills/fso-add-object-type/SKILL.md new file mode 100644 index 00000000000..d28d0c7ab7e --- /dev/null +++ b/.agents/skills/fso-add-object-type/SKILL.md @@ -0,0 +1,72 @@ +--- +name: fso-add-object-type +description: >- + Add a new in-world object type to the FSO object system (alongside ships, + weapons, debris, asteroids, etc.). Use when introducing a new OBJ_ type, a new + kind of simulated entity, or wiring a new entity into obj_create/obj_move_all + and the collision system. +--- + +# FSO: Add a New Object Type + +Every world entity is an `object` (`code/object/object.h`) whose `type` is an +`OBJ_*` constant and whose `instance` indexes a type-specific array. Adding a new +type means defining that array + the create/move/delete trio and registering it +with the object system. + +## Checklist + +``` +- [ ] 1. Define OBJ_MY_TYPE + bump MAX_OBJECT_TYPES (object.h) +- [ ] 2. Add the name to Object_type_names[] (object.cpp) +- [ ] 3. Create the instance storage + *_info if data-driven (info-vs-instance) +- [ ] 4. Implement my_create() (wraps obj_create), my_move(), my_delete() +- [ ] 5. Hook my_move into obj_move_all_pre/post dispatch +- [ ] 6. Hook deletion into obj_delete_all_that_should_be_dead path +- [ ] 7. Add collision handling if it collides (object/objcollide + collide*.cpp) +- [ ] 8. Add rendering in the object render dispatch +``` + +## Steps + +1. **Type constant** — add `#define OBJ_MY_TYPE NN` in `object.h` and increase + `MAX_OBJECT_TYPES`. Add the matching string to `Object_type_names[]` in + `object.cpp` (order must match). + +2. **Storage** — follow the **info-vs-instance** pattern used by ships/weapons: + a `my_type_info` class table (parsed from a `.tbl`, see `fso-add-table-field`) + if the type is data-driven, plus a `my_type` instance array or `SCP_vector`. + +3. **Create** — write `int my_create(...)` that calls + `obj_create(OBJ_MY_TYPE, parent, instance, &orient, &pos, radius, flags)` and + initializes the instance. Set `Collides` flag only if it participates in collisions. + +4. **Move** — write `my_move(object *objp, float frametime)` and call it from the + per-type dispatch inside `obj_move_all_pre()` / `obj_move_all_post()` + (`object.cpp`). Physics runs via `obj_move_call_physics()` if `phys_info` is used. + +5. **Delete** — to destroy, set the `Should_be_dead` flag. Add a `case OBJ_MY_TYPE:` + in `obj_delete_all_that_should_be_dead()` (or the delete dispatch) to free + instance data, then `obj_delete()`. + +6. **Collisions** (optional) — register pairs in `code/object/objcollide.cpp` and + add a `collide_my_type_*` handler (model the existing `collide*.cpp` files). + +7. **Rendering** — add a `case OBJ_MY_TYPE:` to the object render dispatch + (`obj_render` / `obj_queue_render` in `object.cpp`) that queues your draw call. + +## Conventions + +- Use `object_h`/signatures for cross-frame references, not raw objnums. +- Change flags via `obj_set_flags()` so collision-pair state stays consistent. +- Respect `MAX_OBJECTS` (must stay < 2^16-1 for collision-pair caching). +- Keep warning-clean (`FSO_FATAL_WARNINGS=ON`). + +## Verify + +- Build (see `fso-build-and-test`), spawn the entity (via code, SEXP, or Lab), + confirm it moves, renders, collides, and cleans up without asserts. + +## Reference + +- `code/object/object.h` / `object.cpp`, `documentation/modules/object.md`. diff --git a/.agents/skills/fso-add-sexp/SKILL.md b/.agents/skills/fso-add-sexp/SKILL.md new file mode 100644 index 00000000000..de611335f65 --- /dev/null +++ b/.agents/skills/fso-add-sexp/SKILL.md @@ -0,0 +1,78 @@ +--- +name: fso-add-sexp +description: >- + Add a new SEXP operator (mission-scripting function) to FSO. Use when creating + a new SEXP, adding a mission event/goal operator, registering an OP_ identifier, + or wiring a sexp into eval_sexp. Covers sexp.h enum, the Operators table, + argument typing, evaluation, and help text in code/parse/sexp.cpp. +--- + +# FSO: Add a SEXP Operator + +SEXPs are the mission designer's scripting language. Adding one touches a fixed +set of locations in `code/parse/sexp.h` and `code/parse/sexp.cpp`. Search an +existing similar operator name across both files to find every spot to edit. + +## Checklist + +``` +- [ ] 1. Declare OP_* identifier (sexp.h) +- [ ] 2. Register in the Operators table (sexp.cpp ~line 146) +- [ ] 3. Add argument type-checking (sexp.cpp, get/check argument-type switch) +- [ ] 4. Implement the handler + dispatch in eval_sexp() (sexp.cpp ~line 28166) +- [ ] 5. Add help text to Sexp_help (sexp.cpp ~line 37998) +- [ ] 6. (If multiplayer-relevant) handle packing in network/multi_sexp.cpp +``` + +## Steps + +1. **OP_ identifier** — add to the operator enum in `code/parse/sexp.h` + (the block starting `OP_PLUS = FIRST_OP, ...`). Do not reuse a value. + +2. **Register the operator** in the `Operators` vector (`sexp.cpp`, ~line 146). + Entry format is `{ text, OP_id, min_args, max_args, category }`: + +```cpp +{ "my-operator", OP_MY_OPERATOR, 1, 2, SEXP_ACTION_OPERATOR, }, +``` + Pick the right category (`OP_CATEGORY_*` / the `SEXP_*_OPERATOR` kind) so it + shows in the correct FRED submenu. + +3. **Argument typing** — in the argument-type switch (search `case OP_` near the + `get_argument_type`/`check_sexp_syntax` logic, ~line 4869), declare what each + argument slot expects using the `OPF_*` enums (`OPF_NUMBER`, `OPF_SHIP`, …). + +4. **Implement + dispatch** — write a handler function, then add a `case OP_MY_OPERATOR:` + in `eval_sexp()` (`sexp.cpp`, ~line 28166) that calls it and returns a SEXP + result (`SEXP_TRUE`/`SEXP_FALSE`/`SEXP_KNOWN_*`, or a number for arithmetic ops). + Read arguments via the `CDR`/`CADR` node walk like neighbouring cases. + +5. **Help text** — add an entry to the `Sexp_help` vector (`sexp.cpp`, ~line 37998): + +```cpp +{ OP_MY_OPERATOR, "my-operator\r\n" + "\tWhat it does.\r\n\r\n" + "Takes 1 or 2 arguments...\r\n" + "\t1:\tFirst argument.\r\n" + "\t2:\t(optional) Second argument." }, +``` + +6. **Multiplayer** — if the operator changes game state on the server, ensure it + packs/sends correctly via `code/network/multi_sexp.*`. + +## Conventions + +- Operator names are lowercase-hyphenated and must be ≤ `OPERATOR_LENGTH` (30). +- Prefer adding self-contained logic; large new systems may live under `code/parse/sexp/`. +- Keep warning-clean (`FSO_FATAL_WARNINGS=ON`). + +## Verify + +- Build (see `fso-build-and-test`), open FRED/qtFRED, confirm the operator appears + in the right category with correct arg constraints, and test it in a mission. + +## Reference + +- `code/parse/sexp.h` — `OP_*` enum, `OPF_*` arg types, `OP_CATEGORY_*`, return codes. +- `documentation/modules/parse.md`. +- SEXP list: https://wiki.hard-light.net/index.php/SEXP diff --git a/.agents/skills/fso-add-table-field/SKILL.md b/.agents/skills/fso-add-table-field/SKILL.md new file mode 100644 index 00000000000..1e8b82ec651 --- /dev/null +++ b/.agents/skills/fso-add-table-field/SKILL.md @@ -0,0 +1,68 @@ +--- +name: fso-add-table-field +description: >- + Add or modify a parsed option in an FSO table file (.tbl/.tbm) such as + ships.tbl, weapons.tbl, ai_profiles.tbl, game_settings.tbl, hud_gauges.tbl. + Use when adding a new ships.tbl/weapons.tbl entry field, a new table option, + a new $Property to a table, or wiring a parsed value into an *_info struct. +--- + +# FSO: Add a Table (.tbl) Field + +FSO parses `.tbl`/`.tbm` text into global `*_info` structs using the hand-rolled +parser in `code/parse/parselo.*`. Adding an option means: extend the struct, parse +it, give it a default, and (if applicable) expose it to FRED and reset it on remix. + +## Locate the owning module and parser + +Tables map to modules (see `documentation/modules/.md`). Common ones: + +| Table | Parser function | File | +| --- | --- | --- | +| `ships.tbl` | `parse_ship_values` / `parse_shiptbl` | `code/ship/ship.cpp` | +| `weapons.tbl` | `parse_weapon` / `parse_weaponstbl` | `code/weapon/weapons.cpp` | +| `ai_profiles.tbl` | `parse_ai_profiles_tbl` | `code/ai/ai_profiles.cpp` | +| `game_settings.tbl` | `parse_mod_table` | `code/mod_table/mod_table.cpp` | +| `hud_gauges.tbl` | `parse_hud_gauges_tbl` | `code/hud/hudparse.cpp` | + +Find the exact spot by searching the parse function for a nearby `optional_string`. + +## Steps + +1. **Add the field to the `*_info` struct** (e.g. `ship_info` in `code/ship/ship.h`). + Give it a sensible default in the struct or its reset/`clear()` path. +2. **Parse it.** In the module's parse function, copy an adjacent block: + +```cpp +if (optional_string("$My New Option:")) { + stuff_float(&sip->my_new_option); // or stuff_int / stuff_string / stuff_boolean / stuff_vec3d +} +``` + - Use `optional_string` for new fields (never `required_string` — it breaks old tables). + - For names that reference other tables, use the existing lookup helpers + (e.g. `ship_info_lookup`, `weapon_info_lookup`). +3. **Respect `.tbm` modularity.** Most parsers run once per table file and merge + `*-shp.tbm`/`*-wep.tbm` modular tables; ensure your `optional_string` sits inside + the same per-entry loop so modular edits apply. +4. **Use the parsed value** where the behaviour lives. +5. **FRED (if author-facing):** mirror new ship/weapon fields in `fred2/` and + `qtfred/` if they should be editable in the mission editor. +6. **Defaults & tables:** if you ship a new stock option, update the in-tree + default tables under `def_files/` only when appropriate. + +## Conventions + +- Parsing advances the global pointer `Mp`; don't read raw text yourself. +- Keep the option name style consistent: `$Title Case:` for fields, `+Sub Option:` + for sub-fields, `#Section` for sections. +- Build with `FSO_FATAL_WARNINGS=ON` (CI default) — keep it warning-clean. + +## Verify + +- Build (see the `fso-build-and-test` skill) and load a mission/table that uses it. +- Add a unit test under `test/src` if the parsed value drives testable logic. + +## Reference + +- Parser API: `code/parse/parselo.h` (`optional_string`, `stuff_*`, field-type `F_*`). +- Table option docs: https://wiki.hard-light.net/index.php/Tables diff --git a/.agents/skills/fso-build-and-test/SKILL.md b/.agents/skills/fso-build-and-test/SKILL.md new file mode 100644 index 00000000000..1047520be9a --- /dev/null +++ b/.agents/skills/fso-build-and-test/SKILL.md @@ -0,0 +1,78 @@ +--- +name: fso-build-and-test +description: >- + Configure, build, and test the FSO engine with CMake/Ninja and run the + GoogleTest unit tests, mirroring CI so changes stay warning-clean across + GCC/Clang/MSVC. Use when building FSO, running unittests, reproducing a CI + failure, or checking clang-format/clang-tidy compliance before a PR. +--- + +# FSO: Build and Test + +FSO uses CMake (out-of-source builds required). CI builds with +`FSO_FATAL_WARNINGS=ON` across GCC, Clang, and MSVC, so a local clean build is the +minimum bar. Prefer Ninja for speed. + +## Prerequisites + +Submodules must be present: + +```bash +git submodule update --init --recursive +``` + +## Configure + build (Linux/macOS, Ninja) + +```bash +mkdir -p build && cd build +cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug \ + -DFSO_BUILD_TESTS=ON -DFSO_FATAL_WARNINGS=ON \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. +ninja -k 20 all +``` + +- Build types: `Debug`, `Release`, `FastDebug`. +- Existing build dirs in-tree: `cmake-build-debug/`, `cmake-build-release/`, + `cmake-build-relwithdebinfo/` — reuse one instead of reconfiguring if present. +- The CI configure script is `ci/linux/configure_cmake.sh` (adds ccache, SIMD, + AppImage flags); match its options when reproducing a CI build. + +## Run unit tests + +Tests build only with `FSO_BUILD_TESTS=ON`; the binary is `unittests` in `build/bin/`. + +```bash +./bin/unittests --gtest_shuffle +# subset: +./bin/unittests --gtest_filter='SomeSuite.*' +``` + +CI test runner: `ci/linux/run_tests.sh` (Debug non-macOS runs under valgrind). + +## Formatting & static analysis (pre-PR) + +```bash +# Format only files you changed: +clang-format -i path/to/changed_file.cpp +``` + +- `.clang-format` (LLVM-based) and `.clang-tidy` are enforced; CI runs clang-tidy + on changed code for clang builds (`ci/linux/clang_tidy.sh`). +- Indentation is tabs (width 4); column limit 120; left pointer alignment. + +## Workflow + +``` +- [ ] Submodules updated +- [ ] Configure (Ninja, FSO_BUILD_TESTS=ON, FSO_FATAL_WARNINGS=ON) +- [ ] ninja all builds with zero warnings +- [ ] unittests pass (--gtest_shuffle) +- [ ] clang-format applied to changed files +``` + +## Notes + +- Keep builds out-of-source (top-level CMake refuses in-source builds). +- Code must compile on GCC, Clang, and MSVC; avoid platform APIs outside + `code/osapi/`, `code/windows_stub/`, and platform guards. +- For build options reference see the top-level `CMakeLists.txt` and root `AGENTS.md`. diff --git a/.agents/skills/thermo-nuclear-code-quality-review/SKILL.md b/.agents/skills/thermo-nuclear-code-quality-review/SKILL.md new file mode 100644 index 00000000000..ac76a2bc88b --- /dev/null +++ b/.agents/skills/thermo-nuclear-code-quality-review/SKILL.md @@ -0,0 +1,192 @@ +--- +name: thermo-nuclear-code-quality-review +description: Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. Use for a thermo-nuclear code quality review, thermonuclear review, deep code quality audit, or especially harsh maintainability review. +disable-model-invocation: true +--- + +# Thermo-Nuclear Code Quality Review + +Use this skill for an unusually strict review focused on implementation quality, maintainability, abstraction quality, and codebase health. + +Above all, this skill should push the reviewer to be **ambitious** about code structure. Do not merely identify local cleanup opportunities. Actively search for "code judo" moves: restructurings that preserve behavior while making the implementation dramatically simpler, smaller, more direct, and more elegant. + +## Core Prompt + +Start from this baseline: + +> Perform a deep code quality audit of the current branch's changes. +> Rethink how to structure / implement the changes to meaningfully improve code quality without impacting behavior. +> Work to improve abstractions, modularity, reduce Spaghetti code, improve succinctness and legibility. +> Be ambitious, if there is a clear path to improving the implementation that involves restructuring some of the codebase, go for it. +> Be extremely thorough and rigorous. Measure twice, cut once. + +## Non-Negotiable Additional Standards + +Apply the baseline prompt above, plus these explicit review rules: + +0. **Be ambitious about structural simplification.** + - Do not stop at "this could be a bit cleaner." + - Look for opportunities to reframe the change so that whole branches, helpers, modes, conditionals, or layers disappear entirely. + - Prefer the solution that makes the code feel inevitable in hindsight. + - Assume there is often a "code judo" move available: a re-organization that uses the existing architecture more effectively and makes the change dramatically simpler and more elegant. + - If you see a path to delete complexity rather than rearrange it, push hard for that path. + +1. **Do not let a PR push a file from under 1k lines to over 1k lines without a very strong reason.** + - Treat this as a strong code-quality smell by default. + - Prefer extracting helpers, subcomponents, modules, or local abstractions instead of letting a file sprawl past 1000 lines. + - If the diff crosses that threshold, explicitly ask whether the code should be decomposed first. + - Only waive this if there is a compelling structural reason and the resulting file is still clearly organized. + +2. **Do not allow random spaghetti growth in existing code.** + - Be highly suspicious of new ad-hoc conditionals, scattered special cases, or one-off branches inserted into unrelated flows. + - If a change adds "weird if statements in random places", treat that as a design problem, not a stylistic nit. + - Prefer pushing the logic into a dedicated abstraction, helper, state machine, policy object, or separate module instead of tangling an existing path. + - Call out changes that make the surrounding code harder to reason about, even if they technically work. + +3. **Bias toward cleaning the design, not just accepting working code.** + - If behavior can stay the same while the structure becomes meaningfully cleaner, push for the cleaner version. + - Do not rubber-stamp "it works" implementations that leave the codebase messier. + - Strongly prefer simplifications that remove moving pieces altogether over refactors that merely spread the same complexity around. + +4. **Prefer direct, boring, maintainable code over hacky or magical code.** + - Treat brittle, ad-hoc, or "magic" behavior as a code-quality problem. + - Be skeptical of generic mechanisms that hide simple data-shape assumptions. + - Flag thin abstractions, identity wrappers, or pass-through helpers that add indirection without buying clarity. + +5. **Push hard on type and boundary cleanliness when they affect maintainability.** + - Question unnecessary optionality, `unknown`, `any`, or cast-heavy code when a clearer type boundary could exist. + - Prefer explicit typed models or shared contracts over loosely-shaped ad-hoc objects. + - If a branch relies on silent fallback to paper over an unclear invariant, ask whether the boundary should be made explicit instead. + +6. **Keep logic in the canonical layer and reuse existing helpers.** + - Call out feature logic leaking into shared paths or implementation details leaking through APIs. + - Prefer existing canonical utilities/helpers over bespoke one-offs. + - Push code toward the right package, service, or module instead of normalizing architectural drift. + +7. **Treat unnecessary sequential orchestration and non-atomic updates as design smells when the cleaner structure is obvious.** + - If independent work is serialized for no good reason, ask whether the flow should run in parallel instead. + - If related updates can leave state half-applied, push for a more atomic structure. + - Do not over-index on micro-optimizations, but do flag avoidable orchestration complexity that makes the implementation more brittle. + +## Primary Review Questions + +For every meaningful change, ask: + +- Is there a "code judo" move that would make this dramatically simpler? +- Can this change be reframed so fewer concepts, branches, or helper layers are needed? +- Does this improve or worsen the local architecture? +- Did the diff add branching complexity where a better abstraction should exist? +- Did a previously cohesive module become more coupled, more stateful, or harder to scan? +- Is this logic living in the right file and layer? +- Did this change enlarge a file or component past a healthy size boundary? +- Are there repeated conditionals that signal a missing model or missing helper? +- Is the implementation direct and legible, or does it rely on special cases and incidental control flow? +- Is this abstraction actually earning its keep, or is it just a wrapper? +- Did the diff introduce casts, optionality, or ad-hoc object shapes that obscure the real invariant? +- Is this logic living in the canonical layer, or did the diff leak details across a boundary? +- Is this orchestration more sequential or less atomic than it needs to be? + +## What to Flag Aggressively + +Escalate findings when you see: + +- A complicated implementation where a cleaner reframing could delete whole categories of complexity. +- Refactors that move code around but fail to reduce the number of concepts a reader must hold in their head. +- A file crossing 1000 lines due to the PR, especially if the new code could be split out. +- New conditionals bolted onto unrelated code paths. +- One-off booleans, nullable modes, or flags that complicate existing control flow. +- Feature-specific logic leaking into general-purpose modules. +- Generic "magic" handling that hides simple structure and makes the code harder to reason about. +- Thin wrappers or identity abstractions that add indirection without simplifying anything. +- Unnecessary casts, `any`, `unknown`, or optional params that muddy the real contract. +- Copy-pasted logic instead of extracted helpers. +- Narrow edge-case handling implemented in the middle of an already busy function. +- Refactors that technically pass tests but make the code less modular or less readable. +- "Temporary" branching that is likely to become permanent debt. +- Bespoke helpers where the codebase already has a canonical utility for the job. +- Logic added in the wrong layer/package when it should live somewhere more central. +- Sequential async flow where obviously independent work could stay simpler and clearer with parallel execution. +- Partial-update logic that leaves state less atomic than necessary. + +## Preferred Remedies + +When you identify a code-quality problem, prefer suggestions like: + +- Delete a whole layer of indirection rather than polishing it. +- Reframe the state model so conditionals disappear instead of getting centralized. +- Change the ownership boundary so the feature becomes a natural extension of an existing abstraction. +- Turn special-case logic into a simpler default flow with fewer exceptions. +- Extract a helper or pure function. +- Split a large file into smaller focused modules. +- Move feature-specific logic behind a dedicated abstraction. +- Replace condition chains with a typed model or explicit dispatcher. +- Separate orchestration from business logic. +- Collapse duplicate branches into a single clearer flow. +- Delete wrappers that do not meaningfully clarify the API. +- Reuse the existing canonical helper instead of introducing a near-duplicate. +- Make type boundaries more explicit so the control flow gets simpler. +- Move the logic to the package/module/layer that already owns the concept. +- Parallelize independent work when that also simplifies the orchestration. +- Restructure related updates into a more atomic flow when partial state would be harder to reason about. + +Do not be satisfied with "maybe rename this" feedback when the real issue is structural. +Do not be satisfied with a merely cleaner version of the same messy idea if there is a plausible path to a much simpler idea. + +## Review Tone + +Be direct, serious, and demanding about quality. +Do not be rude, but do not soften major maintainability issues into mild suggestions. +If the code is making the codebase messier, say so clearly. +If the implementation missed an opportunity for a dramatic simplification, say that clearly too. + +Good phrases: + +- `this pushes the file past 1k lines. can we decompose this first?` +- `this adds another special-case branch into an already busy flow. can we move this behind its own abstraction?` +- `this works, but it makes the surrounding code more spaghetti. let's keep the behavior and restructure the implementation.` +- `this feels like feature logic leaking into a shared path. can we isolate it?` +- `this abstraction seems unnecessary. can we just keep the direct flow?` +- `why does this need a cast / optional here? can we make the boundary more explicit instead?` +- `this looks like a bespoke helper for something we already have elsewhere. can we reuse the canonical one?` +- `i think there's a code-judo move here that makes this much simpler. can we reframe this so these branches disappear?` +- `this refactor moves complexity around, but doesn't really delete it. is there a way to make the model itself simpler?` + +## Output Expectations + +Prioritize findings in this order: + +1. Structural code-quality regressions +2. Missed opportunities for dramatic simplification / code-judo restructuring +3. Spaghetti / branching complexity increases +4. Boundary / abstraction / type-contract problems that make the code harder to reason about +5. File-size and decomposition concerns +6. Modularity and abstraction issues +7. Legibility and maintainability concerns + +Do not flood the review with low-value nits if there are larger structural issues. +Prefer a smaller number of high-conviction comments over a long list of cosmetic notes. + +## Approval Bar + +Do not approve merely because behavior seems correct. +The bar for approval is: + +- no clear structural regression +- no obvious missed opportunity to make the implementation dramatically simpler when such a path is visible +- no unjustified file-size explosion +- no obvious spaghetti-growth from special-case branching +- no obviously hacky or magical abstraction that makes the code harder to reason about +- no unnecessary wrapper/cast/optionality churn obscuring the real design +- no clear architecture-boundary leak or avoidable canonical-helper duplication +- no missed opportunity for an obvious decomposition that would materially improve maintainability + +Treat these as presumptive blockers unless the author can justify them clearly: + +- the PR preserves a lot of incidental complexity when there is a plausible code-judo move that would delete it +- the PR pushes a file from below 1000 lines to above 1000 lines +- the PR adds ad-hoc branching that makes an existing flow more tangled +- the PR solves a local problem by scattering feature checks across shared code +- the PR adds an unnecessary abstraction, wrapper, or cast-heavy contract that makes the design more indirect +- the PR duplicates an existing helper or puts logic in the wrong layer when there is a clear canonical home + +If those conditions are not met, leave explicit, actionable feedback and push for a cleaner decomposition. diff --git a/.claude/skills/fso-add-hud-gauge/SKILL.md b/.claude/skills/fso-add-hud-gauge/SKILL.md new file mode 100644 index 00000000000..6c0f85e0172 --- /dev/null +++ b/.claude/skills/fso-add-hud-gauge/SKILL.md @@ -0,0 +1,13 @@ +--- +name: fso-add-hud-gauge +description: >- + Add a new HUD gauge to FSO. Use when creating a HUD element/gauge, subclassing + HudGauge, adding a HUD_OBJECT_ type, or wiring a gauge into hud_gauges.tbl + parsing and the per-frame HUD render path. +--- + +# FSO: Add a HUD Gauge + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-hud-gauge/SKILL.md` — read that file in full and follow +it before acting. diff --git a/.claude/skills/fso-add-lua-api/SKILL.md b/.claude/skills/fso-add-lua-api/SKILL.md new file mode 100644 index 00000000000..21b3a825e07 --- /dev/null +++ b/.claude/skills/fso-add-lua-api/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-add-lua-api +description: >- + Expose engine functionality to Lua scripting in FSO via ADE bindings or add a + new scripting hook. Use when adding a Lua API object/library/function/property, + writing ADE_OBJ/ADE_FUNC/ADE_VIRTVAR/ADE_LIB bindings, or creating a + scripting::Hook/OverridableHook that mods can subscribe to. +--- + +# FSO: Add a Lua/ADE Binding or Hook + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-lua-api/SKILL.md` — read that file in full and follow it +before acting. diff --git a/.claude/skills/fso-add-object-type/SKILL.md b/.claude/skills/fso-add-object-type/SKILL.md new file mode 100644 index 00000000000..d1be460a3bc --- /dev/null +++ b/.claude/skills/fso-add-object-type/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-add-object-type +description: >- + Add a new in-world object type to the FSO object system (alongside ships, + weapons, debris, asteroids, etc.). Use when introducing a new OBJ_ type, a new + kind of simulated entity, or wiring a new entity into obj_create/obj_move_all + and the collision system. +--- + +# FSO: Add a New Object Type + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-object-type/SKILL.md` — read that file in full and follow +it before acting. diff --git a/.claude/skills/fso-add-sexp/SKILL.md b/.claude/skills/fso-add-sexp/SKILL.md new file mode 100644 index 00000000000..09814caed69 --- /dev/null +++ b/.claude/skills/fso-add-sexp/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-add-sexp +description: >- + Add a new SEXP operator (mission-scripting function) to FSO. Use when creating + a new SEXP, adding a mission event/goal operator, registering an OP_ identifier, + or wiring a sexp into eval_sexp. Covers sexp.h enum, the Operators table, + argument typing, evaluation, and help text in code/parse/sexp.cpp. +--- + +# FSO: Add a SEXP Operator + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-sexp/SKILL.md` — read that file in full and follow it +before acting. diff --git a/.claude/skills/fso-add-table-field/SKILL.md b/.claude/skills/fso-add-table-field/SKILL.md new file mode 100644 index 00000000000..212c44af3ac --- /dev/null +++ b/.claude/skills/fso-add-table-field/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-add-table-field +description: >- + Add or modify a parsed option in an FSO table file (.tbl/.tbm) such as + ships.tbl, weapons.tbl, ai_profiles.tbl, game_settings.tbl, hud_gauges.tbl. + Use when adding a new ships.tbl/weapons.tbl entry field, a new table option, + a new $Property to a table, or wiring a parsed value into an *_info struct. +--- + +# FSO: Add a Table (.tbl) Field + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-table-field/SKILL.md` — read that file in full and follow +it before acting. diff --git a/.claude/skills/fso-build-and-test/SKILL.md b/.claude/skills/fso-build-and-test/SKILL.md new file mode 100644 index 00000000000..2bc96fe1c84 --- /dev/null +++ b/.claude/skills/fso-build-and-test/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-build-and-test +description: >- + Configure, build, and test the FSO engine with CMake/Ninja and run the + GoogleTest unit tests, mirroring CI so changes stay warning-clean across + GCC/Clang/MSVC. Use when building FSO, running unittests, reproducing a CI + failure, or checking clang-format/clang-tidy compliance before a PR. +--- + +# FSO: Build and Test + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-build-and-test/SKILL.md` — read that file in full and follow +it before acting. diff --git a/.claude/skills/thermo-nuclear-code-quality-review/SKILL.md b/.claude/skills/thermo-nuclear-code-quality-review/SKILL.md new file mode 100644 index 00000000000..f3682c8689e --- /dev/null +++ b/.claude/skills/thermo-nuclear-code-quality-review/SKILL.md @@ -0,0 +1,15 @@ +--- +name: thermo-nuclear-code-quality-review +description: Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. Use for a thermo-nuclear code quality review, thermonuclear review, deep code quality audit, or especially harsh maintainability review. +--- + +# Thermo-Nuclear Code Quality Review + +Explicit invocation only — do not auto-trigger this skill; run it only when the +user explicitly asks for a "thermo-nuclear" / deep code-quality review. (In Cursor +this is enforced via `disable-model-invocation`; other agents should honor it by +convention.) + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/thermo-nuclear-code-quality-review/SKILL.md` — read that file in +full and follow it before acting. diff --git a/.cursor/skills/fso-add-hud-gauge/SKILL.md b/.cursor/skills/fso-add-hud-gauge/SKILL.md new file mode 100644 index 00000000000..6c0f85e0172 --- /dev/null +++ b/.cursor/skills/fso-add-hud-gauge/SKILL.md @@ -0,0 +1,13 @@ +--- +name: fso-add-hud-gauge +description: >- + Add a new HUD gauge to FSO. Use when creating a HUD element/gauge, subclassing + HudGauge, adding a HUD_OBJECT_ type, or wiring a gauge into hud_gauges.tbl + parsing and the per-frame HUD render path. +--- + +# FSO: Add a HUD Gauge + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-hud-gauge/SKILL.md` — read that file in full and follow +it before acting. diff --git a/.cursor/skills/fso-add-lua-api/SKILL.md b/.cursor/skills/fso-add-lua-api/SKILL.md new file mode 100644 index 00000000000..21b3a825e07 --- /dev/null +++ b/.cursor/skills/fso-add-lua-api/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-add-lua-api +description: >- + Expose engine functionality to Lua scripting in FSO via ADE bindings or add a + new scripting hook. Use when adding a Lua API object/library/function/property, + writing ADE_OBJ/ADE_FUNC/ADE_VIRTVAR/ADE_LIB bindings, or creating a + scripting::Hook/OverridableHook that mods can subscribe to. +--- + +# FSO: Add a Lua/ADE Binding or Hook + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-lua-api/SKILL.md` — read that file in full and follow it +before acting. diff --git a/.cursor/skills/fso-add-object-type/SKILL.md b/.cursor/skills/fso-add-object-type/SKILL.md new file mode 100644 index 00000000000..d1be460a3bc --- /dev/null +++ b/.cursor/skills/fso-add-object-type/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-add-object-type +description: >- + Add a new in-world object type to the FSO object system (alongside ships, + weapons, debris, asteroids, etc.). Use when introducing a new OBJ_ type, a new + kind of simulated entity, or wiring a new entity into obj_create/obj_move_all + and the collision system. +--- + +# FSO: Add a New Object Type + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-object-type/SKILL.md` — read that file in full and follow +it before acting. diff --git a/.cursor/skills/fso-add-sexp/SKILL.md b/.cursor/skills/fso-add-sexp/SKILL.md new file mode 100644 index 00000000000..09814caed69 --- /dev/null +++ b/.cursor/skills/fso-add-sexp/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-add-sexp +description: >- + Add a new SEXP operator (mission-scripting function) to FSO. Use when creating + a new SEXP, adding a mission event/goal operator, registering an OP_ identifier, + or wiring a sexp into eval_sexp. Covers sexp.h enum, the Operators table, + argument typing, evaluation, and help text in code/parse/sexp.cpp. +--- + +# FSO: Add a SEXP Operator + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-sexp/SKILL.md` — read that file in full and follow it +before acting. diff --git a/.cursor/skills/fso-add-table-field/SKILL.md b/.cursor/skills/fso-add-table-field/SKILL.md new file mode 100644 index 00000000000..212c44af3ac --- /dev/null +++ b/.cursor/skills/fso-add-table-field/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-add-table-field +description: >- + Add or modify a parsed option in an FSO table file (.tbl/.tbm) such as + ships.tbl, weapons.tbl, ai_profiles.tbl, game_settings.tbl, hud_gauges.tbl. + Use when adding a new ships.tbl/weapons.tbl entry field, a new table option, + a new $Property to a table, or wiring a parsed value into an *_info struct. +--- + +# FSO: Add a Table (.tbl) Field + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-add-table-field/SKILL.md` — read that file in full and follow +it before acting. diff --git a/.cursor/skills/fso-build-and-test/SKILL.md b/.cursor/skills/fso-build-and-test/SKILL.md new file mode 100644 index 00000000000..2bc96fe1c84 --- /dev/null +++ b/.cursor/skills/fso-build-and-test/SKILL.md @@ -0,0 +1,14 @@ +--- +name: fso-build-and-test +description: >- + Configure, build, and test the FSO engine with CMake/Ninja and run the + GoogleTest unit tests, mirroring CI so changes stay warning-clean across + GCC/Clang/MSVC. Use when building FSO, running unittests, reproducing a CI + failure, or checking clang-format/clang-tidy compliance before a PR. +--- + +# FSO: Build and Test + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/fso-build-and-test/SKILL.md` — read that file in full and follow +it before acting. diff --git a/.cursor/skills/thermo-nuclear-code-quality-review/SKILL.md b/.cursor/skills/thermo-nuclear-code-quality-review/SKILL.md new file mode 100644 index 00000000000..2a332f94b16 --- /dev/null +++ b/.cursor/skills/thermo-nuclear-code-quality-review/SKILL.md @@ -0,0 +1,11 @@ +--- +name: thermo-nuclear-code-quality-review +description: Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. Use for a thermo-nuclear code quality review, thermonuclear review, deep code quality audit, or especially harsh maintainability review. +disable-model-invocation: true +--- + +# Thermo-Nuclear Code Quality Review + +This is a vendor-compat pointer. The canonical, maintained playbook lives at +`.agents/skills/thermo-nuclear-code-quality-review/SKILL.md` — read that file in +full and follow it before acting. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..8b5f2df4fbc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,117 @@ +# AGENTS.md + +Guidance for AI coding agents working in the FreeSpace 2 Open (FSO) source tree. + +## Project overview + +FSO is the open-source continuation of the FreeSpace 2 game engine (C++, ~C++17), +maintained by the Source Code Project (SCP). It builds the game engine, the +FreeSpace 2 executable, and the FRED mission editors. The codebase is large, +legacy-heavy, and cross-platform (Windows, Linux, macOS). + +For a map of the engine's subsystems and where concepts live (so you can navigate +without blind grepping), read **`documentation/ARCHITECTURE.md`** first. Per-module +entry-point guides live under **`documentation/modules/`**. + +## Repository layout + +- `code/` — Core engine source. Organized by subsystem (e.g. `ship/`, `weapon/`, + `ai/`, `graphics/`, `model/`, `mission/`, `network/`, `scripting/`, `parse/`, + `math/`, `physics/`, `sound/`, `hud/`, `ui/`). Built as a static library. +- `code/globalincs/` — Globally-included headers (`pstypes.h`, etc.). Included + first everywhere; treat as the foundation. +- `freespace2/` — The game executable (entry point `freespace.cpp`). +- `fred2/` — Windows (MFC) mission editor. +- `qtfred/` — Cross-platform Qt mission editor (opt-in build). +- `wxfred2/` — Legacy wxWidgets editor. +- `lib/` — Bundled third-party libraries (vendored; avoid editing). +- `parsers/` — Generated/parser code. +- `test/` — GoogleTest unit tests (`test/src`, `test/test_data`). +- `cmake/` — CMake modules, toolchain, finders, external submodules. +- `ci/`, `.github/workflows/` — CI scripts and workflow definitions. + +## Setup + +Submodules are required. After cloning: + +```bash +git submodule update --init --recursive +``` + +## Build + +The project uses CMake. Build types: `Debug`, `Release`, `FastDebug`. +Always build out-of-source (the top-level `CMakeLists.txt` refuses in-source builds). +There are already configured build dirs: `cmake-build-debug/`, +`cmake-build-release/`, `cmake-build-relwithdebinfo/`. + +Typical Linux configure + build (mirrors CI in `ci/linux/configure_cmake.sh`): + +```bash +mkdir -p build && cd build +cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DFSO_BUILD_TESTS=ON \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. +ninja -k 20 all +``` + +Useful CMake options (see top-level `CMakeLists.txt` for the full list): + +- `FSO_BUILD_TESTS` — build unit tests (default OFF; turn ON to run tests). +- `FSO_BUILD_FRED2` (Win) / `FSO_BUILD_QTFRED` — build the editors. +- `FSO_BUILD_WITH_OPENGL` / `FSO_BUILD_WITH_VULKAN` — renderer backends. +- `FSO_FATAL_WARNINGS` — warnings become errors (used in CI; keep code clean). +- `FSO_BUILD_INCLUDED_LIBS` — build bundled libs instead of system libs + (default ON for Win/macOS, OFF for Linux). + +## Tests + +Unit tests use GoogleTest and are only built when `FSO_BUILD_TESTS=ON`. +The produced binary is `unittests` (under `build/bin/`). + +```bash +./bin/unittests --gtest_shuffle +# Run a subset: +./bin/unittests --gtest_filter='SomeSuite.*' +``` + +CI reference: `ci/linux/run_tests.sh` (Debug builds on non-macOS run under valgrind). + +## Code style + +Formatting is enforced via `.clang-format` (LLVM-based, customized) and +`.editorconfig`. Key conventions: + +- **Indentation:** tabs (width 4) for `.h`/`.cpp`/`.sdr`; spaces (2) for YAML. +- **Column limit:** 120. +- **Pointer alignment:** left (`int* p`). +- **Braces:** opening brace on its own line for functions; same line for + control statements/namespaces/classes. +- **Includes:** sorted and regrouped; `globalincs/*.h` always come first. + Let clang-format handle include ordering. + +Run formatting on changed files before submitting: + +```bash +clang-format -i path/to/changed_file.cpp +``` + +`.clang-tidy` is also configured and run in CI on clang builds for changed code. + +## Conventions for agents + +- This is legacy game-engine code: prefer **minimal, surgical changes** that match + surrounding style over broad refactors. +- Do **not** edit vendored code under `lib/` or generated files unless explicitly + required. +- Keep changes warning-clean — CI builds with `FSO_FATAL_WARNINGS=ON`. +- When adding source files, register them with the appropriate `CMakeLists.txt` + / `source_groups.cmake` in the relevant subdirectory. +- Cross-platform matters: code must compile on GCC, Clang, and MSVC. Avoid + platform-specific APIs outside `osapi/`, `windows_stub/`, and platform guards. +- Add or update unit tests under `test/src` when changing testable logic. + +## Useful references + +- Build wiki: https://github.com/scp-fs2open/fs2open.github.com/wiki/Building +- Community/forums: https://www.hard-light.net/forums/ +- `Changelog.md` for recent notable changes. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..74d30e7a703 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,28 @@ +# CLAUDE.md + +This file is the Claude Code entry point for the FreeSpace 2 Open (FSO) repository. + +The project guidance is maintained once, tool-agnostically, in `AGENTS.md` +(also read natively by Cursor, opencode, Codex, and other agents). Claude Code +does not read `AGENTS.md` on its own, so it is imported here: + +@AGENTS.md + +## Skills + +Project skills are available under `.claude/skills/` and auto-trigger from natural +requests (or can be invoked explicitly by name). Each one is a thin pointer stub to +the canonical playbook in `.agents/skills//SKILL.md`, which is the +vendor-agnostic single source of truth shared across agents. + +| Skill | Use it when you want to… | +| --- | --- | +| `fso-add-table-field` | Add/modify a parsed option in a `.tbl`/`.tbm` and wire it into an `*_info` struct | +| `fso-add-sexp` | Add a new SEXP operator (mission-scripting function) | +| `fso-add-lua-api` | Expose engine data/functions to Lua (ADE bindings) or add a `scripting::Hook` | +| `fso-add-object-type` | Introduce a new in-world `OBJ_*` entity (create/move/delete + collision + render) | +| `fso-add-hud-gauge` | Add a new built-in HUD gauge (`HudGauge` subclass + `hud_gauges.tbl` parsing) | +| `fso-build-and-test` | Configure/build with CMake+Ninja, run `unittests`, clang-format/clang-tidy | +| `thermo-nuclear-code-quality-review` | Run a strict maintainability/abstraction audit (explicit invocation only) | + +See `.agents/skills/README.md` for the full skill index and shared conventions. diff --git a/code/ai/AGENTS.md b/code/ai/AGENTS.md new file mode 100644 index 00000000000..a88e3490682 --- /dev/null +++ b/code/ai/AGENTS.md @@ -0,0 +1,10 @@ +# Module: ai + +Entry-point guide for this module lives at: +**`documentation/modules/ai.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/cfile/AGENTS.md b/code/cfile/AGENTS.md new file mode 100644 index 00000000000..85502520fc7 --- /dev/null +++ b/code/cfile/AGENTS.md @@ -0,0 +1,10 @@ +# Module: cfile + +Entry-point guide for this module lives at: +**`documentation/modules/cfile.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/cmdline/AGENTS.md b/code/cmdline/AGENTS.md new file mode 100644 index 00000000000..064e5e6ce41 --- /dev/null +++ b/code/cmdline/AGENTS.md @@ -0,0 +1,10 @@ +# Module: cmdline + +Entry-point guide for this module lives at: +**`documentation/modules/cmdline.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/globalincs/AGENTS.md b/code/globalincs/AGENTS.md new file mode 100644 index 00000000000..9939d47dd6d --- /dev/null +++ b/code/globalincs/AGENTS.md @@ -0,0 +1,10 @@ +# Module: globalincs + +Entry-point guide for this module lives at: +**`documentation/modules/globalincs.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/graphics/AGENTS.md b/code/graphics/AGENTS.md new file mode 100644 index 00000000000..8cf16baa305 --- /dev/null +++ b/code/graphics/AGENTS.md @@ -0,0 +1,10 @@ +# Module: graphics + +Entry-point guide for this module lives at: +**`documentation/modules/graphics.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/hud/AGENTS.md b/code/hud/AGENTS.md new file mode 100644 index 00000000000..ae12e436d26 --- /dev/null +++ b/code/hud/AGENTS.md @@ -0,0 +1,10 @@ +# Module: hud + +Entry-point guide for this module lives at: +**`documentation/modules/hud.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/io/AGENTS.md b/code/io/AGENTS.md new file mode 100644 index 00000000000..91d173b3ffa --- /dev/null +++ b/code/io/AGENTS.md @@ -0,0 +1,10 @@ +# Module: io + +Entry-point guide for this module lives at: +**`documentation/modules/io.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/lab/AGENTS.md b/code/lab/AGENTS.md new file mode 100644 index 00000000000..0a4f99ede5e --- /dev/null +++ b/code/lab/AGENTS.md @@ -0,0 +1,10 @@ +# Module: lab + +Entry-point guide for this module lives at: +**`documentation/modules/lab.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/localization/AGENTS.md b/code/localization/AGENTS.md new file mode 100644 index 00000000000..4e2ba1d1de8 --- /dev/null +++ b/code/localization/AGENTS.md @@ -0,0 +1,10 @@ +# Module: localization + +Entry-point guide for this module lives at: +**`documentation/modules/localization.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/menuui/AGENTS.md b/code/menuui/AGENTS.md new file mode 100644 index 00000000000..c34c63d805f --- /dev/null +++ b/code/menuui/AGENTS.md @@ -0,0 +1,11 @@ +# Module: menuui + +Entry-point guide for this module lives at: +**`documentation/modules/menuui.md`** (from repo root). + +It covers the non-mission front-end screens (main hall, pilot select, barracks, +tech room, ready room, options, credits): purpose, the file→screen→game-state +mapping, key concepts, and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/mission/AGENTS.md b/code/mission/AGENTS.md new file mode 100644 index 00000000000..bb7fca371ab --- /dev/null +++ b/code/mission/AGENTS.md @@ -0,0 +1,10 @@ +# Module: mission + +Entry-point guide for this module lives at: +**`documentation/modules/mission.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/mod_table/AGENTS.md b/code/mod_table/AGENTS.md new file mode 100644 index 00000000000..708eb4a0ee8 --- /dev/null +++ b/code/mod_table/AGENTS.md @@ -0,0 +1,10 @@ +# Module: mod_table + +Entry-point guide for this module lives at: +**`documentation/modules/mod_table.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/model/AGENTS.md b/code/model/AGENTS.md new file mode 100644 index 00000000000..ba98fe3e154 --- /dev/null +++ b/code/model/AGENTS.md @@ -0,0 +1,10 @@ +# Module: model + +Entry-point guide for this module lives at: +**`documentation/modules/model.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/network/AGENTS.md b/code/network/AGENTS.md new file mode 100644 index 00000000000..0a9a47e67ae --- /dev/null +++ b/code/network/AGENTS.md @@ -0,0 +1,10 @@ +# Module: network + +Entry-point guide for this module lives at: +**`documentation/modules/network.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/object/AGENTS.md b/code/object/AGENTS.md new file mode 100644 index 00000000000..ccde44c04d0 --- /dev/null +++ b/code/object/AGENTS.md @@ -0,0 +1,10 @@ +# Module: object + +Entry-point guide for this module lives at: +**`documentation/modules/object.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/options/AGENTS.md b/code/options/AGENTS.md new file mode 100644 index 00000000000..9790e4f172c --- /dev/null +++ b/code/options/AGENTS.md @@ -0,0 +1,12 @@ +# Module: options + +Entry-point guide for this module lives at: +**`documentation/modules/options.md`** (from repo root). + +It covers the SCP options framework and the in-game Options menu: purpose, key +files, core data structures (`OptionBuilder`/`Option`/`OptionsManager`), major +enums/flags, how to declare an option, persistence/defaults, and the +`default_settings.tbl` it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/parse/AGENTS.md b/code/parse/AGENTS.md new file mode 100644 index 00000000000..81a8c8f0e63 --- /dev/null +++ b/code/parse/AGENTS.md @@ -0,0 +1,10 @@ +# Module: parse + +Entry-point guide for this module lives at: +**`documentation/modules/parse.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/physics/AGENTS.md b/code/physics/AGENTS.md new file mode 100644 index 00000000000..053667492bc --- /dev/null +++ b/code/physics/AGENTS.md @@ -0,0 +1,10 @@ +# Module: physics + +Entry-point guide for this module lives at: +**`documentation/modules/physics.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/scripting/AGENTS.md b/code/scripting/AGENTS.md new file mode 100644 index 00000000000..0be74cf7ffa --- /dev/null +++ b/code/scripting/AGENTS.md @@ -0,0 +1,10 @@ +# Module: scripting + +Entry-point guide for this module lives at: +**`documentation/modules/scripting.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/ship/AGENTS.md b/code/ship/AGENTS.md new file mode 100644 index 00000000000..4d183a9af6e --- /dev/null +++ b/code/ship/AGENTS.md @@ -0,0 +1,10 @@ +# Module: ship + +Entry-point guide for this module lives at: +**`documentation/modules/ship.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/sound/AGENTS.md b/code/sound/AGENTS.md new file mode 100644 index 00000000000..c60fd9ee72b --- /dev/null +++ b/code/sound/AGENTS.md @@ -0,0 +1,10 @@ +# Module: sound + +Entry-point guide for this module lives at: +**`documentation/modules/sound.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/ui/AGENTS.md b/code/ui/AGENTS.md new file mode 100644 index 00000000000..930855febad --- /dev/null +++ b/code/ui/AGENTS.md @@ -0,0 +1,10 @@ +# Module: ui + +Entry-point guide for this module lives at: +**`documentation/modules/ui.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/code/weapon/AGENTS.md b/code/weapon/AGENTS.md new file mode 100644 index 00000000000..da78908ccdd --- /dev/null +++ b/code/weapon/AGENTS.md @@ -0,0 +1,10 @@ +# Module: weapon + +Entry-point guide for this module lives at: +**`documentation/modules/weapon.md`** (from repo root). + +It covers the module's purpose, key files, core data structures, major constants, +and the configuration tables it parses. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/documentation/ARCHITECTURE.md b/documentation/ARCHITECTURE.md new file mode 100644 index 00000000000..9599ddc445c --- /dev/null +++ b/documentation/ARCHITECTURE.md @@ -0,0 +1,371 @@ +# FSO Engine Architecture — Discovery Guide + +This document is a **map of the FreeSpace 2 Open (FSO) engine for AI agents and +new contributors**. Its purpose is to let you locate the code that owns a concept +*without* blindly grepping the ~3000-file tree. When you need to touch a feature, +start here: find the concept, jump to the listed file/dir, then read or grep +*locally* within that scope. + +Conventions used below: +- Paths are relative to the repo root. The engine library lives under `code/`. +- "Owner" means the file/dir where the concept's data model and core logic live. +- All header/source pairs are `.h`/`.cpp` unless noted. + +--- + +## 1. The 10-second mental model + +FSO is a single-threaded, frame-driven game engine. Almost everything in the +world is an **`object`** (ship, weapon, debris, etc.). Each frame the engine: + +1. reads input → 2. runs the **game state machine** → 3. simulates (move objects, +physics, AI, collisions, mission logic/SEXPs) → 4. renders → 5. flips the buffer. + +Static game content (ships, weapons, etc.) is defined in **table files** (`*.tbl`/`*.tbm`) +that are parsed at load time into global arrays (`Ship_info`, `Weapon_info`, …). +Per-mission content comes from **`.fs2` mission files**. Behaviour is extended at +runtime via **SEXPs** (mission scripting) and **Lua** (engine scripting). + +--- + +## 2. Concept index (where do I look for…?) + +| I'm looking for… | Go to | +| --- | --- | +| Program entry / `main()` / top-level loop | `freespace2/freespace.cpp` | +| Game states & transitions (menu, gameplay, briefing…) | `code/gamesequence/` | +| The base entity (everything in the world) | `code/object/object.h` | +| Global primitive types, containers, macros | `code/globalincs/` | +| Ships (instances + class definitions) | `code/ship/ship.h` | +| Weapons / beams / projectiles | `code/weapon/` | +| AI behaviour & goals | `code/ai/` | +| Movement / flight model | `code/physics/` | +| 3D models (POF), submodels, collision geometry | `code/model/` | +| Renderer (low-level draw API, backends) | `code/graphics/` | +| Mission file parsing & spawn logic | `code/mission/missionparse.*` | +| Mission events / triggers (SEXP) | `code/parse/sexp.*`, `code/parse/sexp/` | +| Table (`.tbl`) / generic text parsing | `code/parse/parselo.*` | +| File access & VP archives | `code/cfile/` | +| Lua / engine scripting API | `code/scripting/` | +| Multiplayer / networking | `code/network/` | +| Sound, music, streaming audio | `code/sound/` | +| Input (keyboard, mouse, joystick) | `code/io/`, `code/controlconfig/` | +| OS abstraction, registry, logging window | `code/osapi/` | +| HUD gauges & combat UI | `code/hud/` | +| In-game/menu UI widgets | `code/ui/`, `code/scpui/`, `code/menuui/`, `code/missionui/` | +| Front-end menu screens (main hall, barracks, tech room, ...) | `code/menuui/` | +| Math (vectors, matrices, fixed-point) | `code/math/` | +| The player (pilot, stats, controls) | `code/playerman/`, `code/pilotfile/` | +| Camera | `code/camera/` | +| Particles / fireballs / explosions | `code/particle/`, `code/fireball/` | +| Nebula / starfield / background | `code/nebula/`, `code/starfield/` | +| Asteroids / debris fields | `code/asteroid/`, `code/debris/` | +| Global engine tunables ("game settings" table) | `code/mod_table/` | +| Command-line options | `code/cmdline/` | +| Player options / in-game Options menu | `code/options/` | +| Localized text / translations (`XSTR`) | `code/localization/` | +| Asset viewer / test bench (the "Lab") | `code/lab/` | +| FRED mission editor (separate executables) | `fred2/` (MFC), `qtfred/` (Qt) | + +--- + +## 2b. Per-module guides + +Each major module has its own entry-point guide under `documentation/modules/` +(purpose, key files, core data structures, major constants, and the config tables +it parses). Several also include a **subsystem architecture diagram** (object, +parse/SEXP, ai, physics, model, graphics + OpenGL backend, scripting, network). +When you start working inside a module, open its guide first: + +| Module | Code path | Guide | +| --- | --- | --- | +| Base entity / object system | `code/object/` | `modules/object.md` | +| Ships | `code/ship/` | `modules/ship.md` | +| Weapons / beams | `code/weapon/` | `modules/weapon.md` | +| AI | `code/ai/` | `modules/ai.md` | +| Physics / flight model | `code/physics/` | `modules/physics.md` | +| 3D models (POF) | `code/model/` | `modules/model.md` | +| Renderer | `code/graphics/` | `modules/graphics.md` | +| Virtual file system / VP | `code/cfile/` | `modules/cfile.md` | +| Table parsing + SEXPs | `code/parse/` | `modules/parse.md` | +| Missions / campaigns | `code/mission/` | `modules/mission.md` | +| Lua scripting | `code/scripting/` | `modules/scripting.md` | +| Multiplayer | `code/network/` | `modules/network.md` | +| Audio | `code/sound/` | `modules/sound.md` | +| Input / timing | `code/io/` | `modules/io.md` | +| HUD | `code/hud/` | `modules/hud.md` | +| UI widget toolkit | `code/ui/` | `modules/ui.md` | +| Front-end menu screens (main hall, barracks, ...) | `code/menuui/` | `modules/menuui.md` | +| Asset viewer / test bench (Lab) | `code/lab/` | `modules/lab.md` | +| Command-line options | `code/cmdline/` | `modules/cmdline.md` | +| Player options / in-game Options menu | `code/options/` | `modules/options.md` | +| Localization / translations | `code/localization/` | `modules/localization.md` | +| Engine-wide settings table | `code/mod_table/` | `modules/mod_table.md` | +| Foundation types & limits | `code/globalincs/` | `modules/globalincs.md` | +| FRED2 mission editor (MFC, Windows) | `fred2/` | `modules/fred2.md` | +| qtFRED mission editor (Qt, cross-platform) | `qtfred/` | `modules/qtfred.md` | + +Full table-option documentation is on the wiki: https://wiki.hard-light.net/index.php/Tables + +--- + +## 3. Game lifecycle & the frame loop + +**`freespace2/freespace.cpp`** is the heart of the program. Key functions: + +- `main()` → `game_main()` — startup, init all subsystems, then run the loop. +- `game_do_state(state)` — dispatches per-frame work based on the current game state. +- `game_frame()` → `game_simulation_frame()` — the per-frame **simulation** step. +- `game_render_frame_setup()` / `game_render_frame()` — the per-frame **render** step. +- `game_do_full_frame()` — ties simulation + render + flip together. + +**Game state machine** — `code/gamesequence/gamesequence.h`: +- `enum GS_STATE` — every screen/mode (e.g. `GS_STATE_GAME_PLAY`, `GS_STATE_BRIEFING`). +- `enum GS_EVENT` — events that drive transitions (e.g. `GS_EVENT_START_GAME`). +- Use `gameseq_post_event()`, `gameseq_set_state()`, `gameseq_get_state()`. +- States can be stacked: `gameseq_push_state()` / `gameseq_pop_state()`. +- `game_enter_state()` / `game_leave_state()` / `game_do_state()` are the hooks + each state implements; their bodies live in `freespace.cpp`. + +`game_simulation_frame()` is the best single function to read to understand +ordering: camera → AWACS → autopilot → `obj_move_all()` (physics/AI/collisions) → +`mission_eval_goals()` → training checks → multiplayer interpolation. + +```mermaid +flowchart TD + main["main() / game_main()
(freespace.cpp)"] --> init["init subsystems
(cfile, graphics, sound, parse tables...)"] + init --> loop{{"main loop:
gameseq_process_events()"}} + + loop -->|current GS_STATE| dispatch["game_do_state(state)
(gamesequence)"] + dispatch -->|GS_STATE_GAME_PLAY| frame["game_frame()"] + dispatch -->|menu / briefing / debrief / ...| ui["state-specific do_frame
(menuui, missionui, scpui)"] + + frame --> input["read_player_controls()
game_process_keys() (io/)"] + input --> sim["game_simulation_frame()"] + + subgraph SIM["Simulation step"] + direction TB + cam["camera + cutscenes (camera/)"] --> mission_pre["mission_parse_eval_stuff()
arrivals / departures"] + mission_pre --> move["obj_move_all(frametime) (object/)"] + move --> physics["physics integrate (physics/)"] + move --> ai["AI think (ai/)"] + move --> collide["object pair collisions (object/objcollide)"] + collide --> goals["mission_eval_goals()
SEXP eval (parse/sexp)"] + goals --> training["training_check_objectives()"] + training --> multi["multiplayer interpolation (network/)"] + end + + sim --> SIM + SIM --> render["game_render_frame_setup()
game_render_frame()"] + + subgraph REN["Render step"] + direction TB + scene["build model_draw_list (model/, graphics/)"] --> world["draw world: ships, weapons,
nebula, starfield, particles"] + world --> hud["draw HUD gauges (hud/)"] + hud --> uidraw["draw UI overlays (ui/, scpui/)"] + end + + render --> REN + REN --> flip["gr_flip() — present frame (graphics/)"] + flip --> loop + + hooks["Lua hooks fire throughout
(scripting/: on-frame, on-death, HUD draw...)"] -.-> sim + hooks -.-> render +``` + + +--- + +## 4. The object system (the core abstraction) + +**Owner: `code/object/object.h` + `object.cpp`.** + +Everything dynamic in the world is an `object`. The `object` struct holds the +*common* state: `type`, `pos`, `orient`, `radius`, `phys_info` (physics), +`hull_strength`, `shield_quadrant`, flags, docking, and an `instance` field. + +- `type` is one of the `OBJ_*` defines (`OBJ_SHIP`, `OBJ_WEAPON`, `OBJ_DEBRIS`, + `OBJ_FIREBALL`, `OBJ_ASTEROID`, `OBJ_BEAM`, …). +- `instance` indexes into the **type-specific array**: for `OBJ_SHIP` it indexes + `Ships[]`, for `OBJ_WEAPON` it indexes `Weapons[]`, etc. This indirection is the + central pattern — `object` is the generic node, the subsystem array holds the + specialized data. +- Global storage: `object Objects[]`, iterated via the intrusive linked lists + `obj_used_list` / `obj_free_list` / `obj_create_list` (see `globalincs/linklist.h` + and the `list_range()` helper). + +Lifecycle: each type provides `*_create` (calls `obj_create()`), `*_move` +(per-frame, called from `obj_move_all()`), `*_delete`. **To kill an object you set +the `Should_be_dead` flag**; actual deletion happens later in +`obj_delete_all_that_should_be_dead()`. + +**Handles & signatures:** every object has a unique `signature`. Prefer +`object_h` (objnum + sig) and `obj_get_by_signature()` over raw pointers/indices +when storing references across frames, because slots get reused. + +--- + +## 5. Gameplay entities + +### Ships — `code/ship/ship.h` +Two-level model used throughout the engine: +- **`ship_info`** — the *class* definition (one per ship type, from `ships.tbl`). + Stored in `SCP_vector Ship_info`. +- **`ship`** — a *live instance* in the mission. Stored in `ship Ships[MAX_SHIPS]`; + `ship.ship_info_index` points back into `Ship_info`. +- **`wing`** — groups of ships; `wing Wings[MAX_WINGS]`. +- Subsystems (turrets, engines, sensors) are `ship_subsys`; weapons banks live in + `ship_weapon`. `ship.cpp` is huge — grep within it for `ship_` functions. + +This *info-vs-instance* split (`Xxx_info` class table + `Xxx[]` instance array) is +the dominant data pattern; weapons, fireballs, asteroids all follow it. + +### Weapons — `code/weapon/weapon.h` +- `weapon_info` (class, `SCP_vector Weapon_info`) vs `weapon` + (instance, `weapon Weapons[MAX_WEAPONS]`). Beams, swarm/corkscrew, flak, EMP, + trails are sub-areas in the same dir (`beam.*`, `swarm.*`, `flak.*`, etc.). + +### AI — `code/ai/` +- `ai_info Ai_info[]` — per-ship AI state; `ai.cpp`/`aicode.cpp` hold behaviours, + `aigoals.*` the goal/order system, `ai_profiles.*` tunable difficulty profiles. + +### Physics — `code/physics/` +- `physics_info` (embedded in every `object`) + `physics.cpp` integrator. + `physics_state.*` handles snapshots used for multiplayer interpolation/rollback. + +### Other object types +- Explosions: `code/fireball/`. Debris: `code/debris/`. Asteroids: `code/asteroid/`. +- Collision detection between object pairs: `code/object/objcollide.*` and the + per-type `collide*.cpp` files in `code/object/`. + +--- + +## 6. Models & rendering + +### Models (POF format) — `code/model/` +- `model.h` defines `polymodel` (geometry/submodels/subsystems) and + `polymodel_instance` (per-object animation/runtime state). +- `modelread.cpp` loads POFs; `modelinterp.cpp`/`modelrender.cpp` draw them; + `modelcollide.cpp` does ray/sphere-vs-model collision; `animation/` holds + subobject + procedural animation. +- Lookups: `object_get_model()` / `object_get_model_instance()` (in `object.h`). + +### Renderer — `code/graphics/` +- `2d.h`/`2d.cpp` define the **abstract rendering interface** (`gr_*` function + pointers) that the rest of the engine calls. The active backend fills these in. +- Backends: `opengl/` (primary), `vulkan/` (experimental), `stub` (`grstub.cpp`). + Backend selection is toggled by the `FSO_BUILD_WITH_OPENGL` / `_VULKAN` CMake options. +- Supporting: `light.*` (dynamic lights), `shadows.*`, `post_processing.*`, + `material.*`, `shaders/`, `paths/` (NanoVG 2D), `font.h`. +- High-level scene assembly: `model_draw_list` / `modelrender.*` and `render.*`. + +--- + +## 7. Data pipeline (how content gets in) + +### File access — `code/cfile/` +- `cfile.h` is the **VFS**: `cfopen`, `cfread`, etc. operate transparently on + loose files *or* files inside **VP archives**. Content is grouped by + `CF_TYPE_*` (maps, models, tables, sounds, missions, …) which map to directories. +- Mods are layered: see the `CFileLocationFlags` / root-and-mod search logic. + +### Table & text parsing — `code/parse/parselo.*` +- The hand-rolled parser used for all `.tbl`/`.tbm`/`.fs2` text. Core idiom: + `read_file_text()`, then `required_string()`, `optional_string()`, + `stuff_int()`, `stuff_string()`, `stuff_float()`, etc., advancing the global + pointer `Mp`. When adding a table field, follow the existing `optional_string` + pattern in that subsystem's `*_parse` function. + +### Missions — `code/mission/` +- `missionparse.*` parses `.fs2` files into `Parse_objects` (`p_object`) and + spawns them; arrivals/departures are evaluated each frame via + `mission_parse_eval_stuff()`. +- `missiongoals.*` (objectives), `missionmessage.*` (in-mission messages), + `missiontraining.*`, `missionbriefcommon.*` (briefings), + `missioncampaign.*` (campaign progression), `missionlog.*`. + +### SEXPs (mission scripting) — `code/parse/sexp.*` and `code/parse/sexp/` +- S-expression trees are the mission designer's logic language (events, goals, + triggers). `sexp.cpp` is the giant operator dispatch (`OP_*`); newer operators + may live under `code/parse/sexp/`. `sexp_container.*` adds list/map containers. + +--- + +## 8. Scripting (Lua) — `code/scripting/` + +The runtime extension API exposed to mod authors. +- `ade*` files implement **ADE**, the C++↔Lua binding layer (`ade_api.h` macros + define Lua classes/libraries). The bound API objects live in + `code/scripting/api/objs/` and libraries in `code/scripting/api/libs/`. +- **Hooks** are the event system: `hook_api.h` (`scripting::Hook` / + `OverridableHook`), `global_hooks.*`, `hook_conditions.*`. The engine fires + hooks (e.g. on-frame, on-death) that Lua scripts subscribe to. +- `scripting.h` ties it together (`script_state`, `Script_system`). + +When you need to expose a new engine feature to Lua, you add an ADE binding here +and usually a hook firing site in the relevant subsystem. + +--- + +## 9. Other major subsystems + +- **Networking — `code/network/`**: `multi.h` is the hub. Files are grouped by + concern: `multimsgs.*` (packets), `multi_obj.*` (object updates), + `multi_interpolate.*`, `multi_respawn.*`, `multi_team.*`, `psnet2.*` (sockets), + `multi_pxo.*` (online lobby). Standalone server UI: `stand_gui*`. +- **Sound — `code/sound/`**: `sound.*` high-level API, `ds.*`/`ds3d.*` low-level + (OpenAL via `openal.*`), `audiostr.*` streaming, `ffmpeg/` decoding, + `fsspeech.*`/`speech_*` TTS, `voicerec.*` voice recognition (Windows). +- **Input — `code/io/`**: `key.*`, `mouse.*`, `joy*.cpp` (SDL-based). + `code/controlconfig/` maps physical inputs to game actions (control presets). +- **OS / platform — `code/osapi/`**: window/event loop (`osapi.*`), settings + (`osregistry.*`), debug log window (`outwnd.*`). Platform stubs in + `code/windows_stub/`. +- **HUD — `code/hud/`**: per-gauge files (`hudtarget.*`, `hudreticle.*`, + `hudshield.*`, `hudescort.*`, …). Gauges are configurable via tables. +- **UI — `code/ui/`** (low-level widgets), **`code/scpui/`** (RocketUI/libRocket + based modern UI), **`code/menuui/`** / **`code/missionui/`** (specific screens + like main hall, briefing, ship/weapon select). +- **Player — `code/playerman/`** (player struct, controls) and + **`code/pilotfile/`** (pilot save/load). + +--- + +## 10. Foundational / cross-cutting code — `code/globalincs/` + +Read these to understand idioms you'll see everywhere: +- `pstypes.h` — base types, `vec3d`/`matrix` forward decls, `MAX_*` limits, + `Int3()`/`Assert()` debug macros, fixed-point `fix`. +- `vmallocator.h` — the `SCP_vector`, `SCP_string`, `SCP_map`, etc. aliases + (thin wrappers over `std::`). Use these, not raw `std::` types, for consistency. +- `flagset.h` — type-safe bitflag sets (`flagset`). Flags are defined + per subsystem in `*_flags.h` files (e.g. `object_flags.h`, `ship_flags.h`). +- `linklist.h` — intrusive doubly-linked lists + `list_range()` iteration used by + the object lists and many others. +- `globals.h` — shared `MAX_*` sizes. `systemvars.*` — global game state vars + (`Game_mode`, `Missiontime`, `flFrametime`, viewer/player globals). +- `alphacolors.*` — standard UI colors. `version.*` — build/version info. + +--- + +## 11. Practical discovery tips + +- **Info vs instance:** to change a *property of a ship class*, edit `ship_info` + + its parser in `ship.cpp`; to change *runtime behaviour of a live ship*, edit + `ship` handling. Same split for weapons/fireballs/asteroids. +- **Following a frame:** read `game_simulation_frame()` then `obj_move_all()` then + the relevant `*_move()` (e.g. `ship_process_post`, `weapon_process_post`). +- **Adding a table field:** grep the subsystem's `*_parse`/`parse_*` function and + copy an adjacent `optional_string(...)` block. +- **Adding a SEXP:** add an `OP_*` enum + entry in the `Operators[]` table + a + handler in `sexp.cpp` (search an existing operator name to find all the spots). +- **Exposing to Lua:** add an ADE binding under `code/scripting/api/` and, if it's + event-driven, fire a hook from the subsystem. +- **Big files are normal:** `ship.cpp`, `sexp.cpp`, `freespace.cpp`, + `hudtarget.cpp` are thousands of lines. Prefer a scoped `Grep`/`SemanticSearch` + *within the file* over reading it top-to-bottom. +- **Editors:** `fred2/` and `qtfred/` reuse `code/` but have their own entry + points and UI; engine changes that touch parsing or data often need matching + FRED updates. + +For build/test/style conventions see `AGENTS.md` in the repository root. diff --git a/documentation/modules/ai.md b/documentation/modules/ai.md new file mode 100644 index 00000000000..c98f2daeaa5 --- /dev/null +++ b/documentation/modules/ai.md @@ -0,0 +1,60 @@ +# Module: ai — `code/ai/` + +## Purpose +Owns **AI behaviour** for ships: combat maneuvering, target selection, goals +(orders), formation flying, docking, waypoint following, and per-difficulty +tunables ("AI profiles"). Per-ship runtime AI state lives in `ai_info`. + +## Key files +- `ai.h` — `ai_info`, behaviour modes (`AIM_*`), goal types, `Ai_info[]`. +- `aicode.cpp` — the bulk of AI behaviour and per-frame thinking. +- `aigoals.cpp` / `aigoals.h` — the goal/order system (what the player/SEXPs command). +- `ai_profiles.cpp` / `ai_profiles.h` — difficulty-scaling profile table. +- `ai_flags.h` — AI flagsets. `aibig.cpp` — big-ship/turret-specific AI. +- `aiturret.cpp` — turret target selection and firing. + +## Core data structures / globals +- `ai_info Ai_info[MAX_AI_INFO]` — per-ship AI state (`MAX_AI_INFO == MAX_SHIPS`). +- `ai_profile_t` / `Ai_profiles` — tunable behaviour profiles. + +## Major constants +- `MAX_AI_BEHAVIORS` (23, number of `AIM_*` modes). +- `MAX_AI_GOALS` (5), `MAX_WAYPOINTS_PER_LIST` (20), `MAX_PATH_POINTS` (1000). +- `MAX_ENEMY_DISTANCE` (2500.0f), `MAX_IGNORE_NEW_OBJECTS` (7). +- `MAX_AI_INFO` (= `MAX_SHIPS`), `MAX_BURST_DAMAGE` (20.0f). + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `ai.tbl` | `parse_aitbl()` (`aicode.cpp`) | Base AI class behaviour | +| `ai_profiles.tbl` | `ai_profiles.cpp` | Difficulty-scaling profiles (default `Default`) | +| `mission_profiles.tbl` | `ai_profiles.cpp` | Per-mission profile overrides | + +Table option reference: https://wiki.hard-light.net/index.php/Tables (see *AI.tbl*, *AI_profiles.tbl*). + +## Architecture diagram (per-ship AI tick) + +```mermaid +flowchart TD + move["obj_move_all() (code/object)"] --> frame["ai_frame(objnum)
(aicode.cpp ~15324)"] + frame --> goals["process AI goals/orders
(aigoals.cpp) → sets aip->mode"] + goals --> exec["ai_execute_behavior(aip)
(aicode.cpp ~14262)"] + exec --> mode{"switch on aip->mode
(AIM_*)"} + mode -->|AIM_CHASE| chase["pursue & attack target"] + mode -->|AIM_EVADE| evade["evasive maneuvers"] + mode -->|AIM_WAYPOINTS| wp["follow waypoint path"] + mode -->|AIM_GUARD / DOCK / ...| other["guard, dock, strafe, etc."] + mode -->|AIM_NONE| idle["do nothing"] + chase --> out + evade --> out + wp --> out + other --> out + profile["ai_profiles.tbl tunables
(skill scaling)"] -.-> exec + exec -.-> evade2["maybe_evade_dumbfire_weapon()"] + out["set desired heading / throttle / fire
→ control_info + physics desired_vel/rotvel"] --> turret["turret AI fires
(aiturret.cpp)"] + out --> phys["physics integrates next
(code/physics)"] +``` + +## See also +- `code/ship/` (the entity AI controls), `code/physics/` (movement output), + `code/autopilot/` (player autopilot), `code/parse/sexp.*` (AI goals issued by missions). diff --git a/documentation/modules/cfile.md b/documentation/modules/cfile.md new file mode 100644 index 00000000000..7e671320ac5 --- /dev/null +++ b/documentation/modules/cfile.md @@ -0,0 +1,33 @@ +# Module: cfile — `code/cfile/` + +## Purpose +The engine's **virtual file system**. Provides `cfopen`/`cfread`/`cfwrite`/… that +transparently read from loose files *or* from inside **VP archives**. Files are +categorized by `CF_TYPE_*` (which map to directories like `maps`, `models`, +`tables`, `sounds`, `missions`). Implements the **mod layering** search order +(primary mod → secondary mods → game root → user dir). + +## Key files +- `cfile.h` / `cfile.cpp` — public API, `CFILE` handle, path-type defines. +- `cfilesystem.cpp` / `cfilesystem.h` — directory/VP indexing and search order. +- `cfilearchive.cpp` / `cfilearchive.h` — VP archive reading. +- (Tools: `code/cfilearchiver/`, `code/cfileextractor/` build VP pack/unpack utilities.) + +## Core data structures +- `struct CFILE` — opaque file handle (loose file or in-archive offset). +- `file_list_info`, `CFileLocation*` — listing and location results. + +## Major constants +- Path types `CF_TYPE_*`: `ROOT`, `DATA`, `MAPS`, `TEXT`, `MODELS`, `TABLES`, + `SOUNDS`, `VOICE*`, `MUSIC`, `MOVIES`, `INTERFACE`, `FONT`, `EFFECTS`, `HUD`, + `PLAYERS`, `MISSIONS`, `CACHE`, `SCRIPTS`, `FICTION`, … (`CF_MAX_PATH_TYPES` = 37). +- `CF_TYPE_ANY` (-1), `CF_TYPE_INVALID` (0). +- Seek: `CF_SEEK_SET/CUR/END`. Sort: `CF_SORT_NAME/TIME/REVERSE`. +- Location flags: `CF_LOCATION_ROOT_GAME`, `CF_LOCATION_ROOT_USER`, mod-scope flags. + +## Configuration tables +None — this *is* the layer every other module uses to find its tables and assets. + +## See also +- `code/parse/parselo.*` (reads table text via this), every asset-loading module. +- Table option reference: https://wiki.hard-light.net/index.php/Tables diff --git a/documentation/modules/cmdline.md b/documentation/modules/cmdline.md new file mode 100644 index 00000000000..005a2b4d7f1 --- /dev/null +++ b/documentation/modules/cmdline.md @@ -0,0 +1,34 @@ +# Module: cmdline — `code/cmdline/` + +## Purpose +Parses **command-line arguments** and exposes them as global `Cmdline_*` variables +that the rest of the engine reads during init and at runtime. This is the central +place for launch-time feature toggles (graphics, networking, debug, mod selection, +resolution, etc.). Many flags are also surfaced through the Knossos launcher. + +## Key files +- `cmdline.cpp` / `cmdline.h` — `parse_cmdline()` and every `Cmdline_*` global. + +## Core data structures / globals +- `int parse_cmdline(int argc, char *argv[])` — called early from `game_main()`. +- A large set of `extern` globals declared in `cmdline.h` and defined in + `cmdline.cpp`, grouped as **RETAIL OPTIONS** and **FSO OPTIONS** (graphics, + network, audio, debug). Examples: `Cmdline_window`, `Cmdline_res`, + `Cmdline_freespace_no_sound`, `Cmdline_network_port`, `Cmdline_spew_pof_info`. + +## Adding a new command-line flag +1. Declare `extern Cmdline_my_flag;` in `cmdline.h` (in the right group). +2. Define it and register its parsing in `cmdline.cpp` (follow an adjacent flag: + add to the options/parameter table and assign the parsed value). +3. Read `Cmdline_my_flag` where the behaviour lives. +4. Ensure it doesn't break FRED or TestCode (the header warns about this). + +## Configuration tables +None. (Command-line flags are launch arguments, not table data. Persistent +*player* options live in `code/options/` / `default_settings.tbl`; engine-wide mod +behaviour lives in `code/mod_table/` / `game_settings.tbl`.) + +## See also +- `code/mod_table/` (per-mod engine settings), `code/options/` (player option values). +- `freespace2/freespace.cpp` (`game_main` calls `parse_cmdline`). +- Command-line reference: https://wiki.hard-light.net/index.php/Command-Line_Reference diff --git a/documentation/modules/fred2.md b/documentation/modules/fred2.md new file mode 100644 index 00000000000..2f85028c78f --- /dev/null +++ b/documentation/modules/fred2.md @@ -0,0 +1,42 @@ +# Module: fred2 — `fred2/` (FReespace EDitor) + +## Purpose +**FRED2** is the original Windows-only mission editor. It is a separate executable +(not part of the game binary) that links against the engine code in `code/` and +adds an **MFC**-based GUI for building `.fs2` missions and campaigns: placing +ships/wings, editing arrivals/departures, briefings/debriefings, events and goals +(SEXP trees), backgrounds, and reinforcements. + +It is the legacy editor; the cross-platform replacement is **qtFRED** +(`documentation/modules/qtfred.md`). Both reuse the same engine parsing/data. + +## Build +- Built only on Windows when `FSO_BUILD_FRED2=ON` (see top-level `CMakeLists.txt`). +- Target: `Fred2`. Uses MFC; not available on Linux/macOS. + +## Key files (representative) +- `fred.cpp`, `freddoc.*`, `fredview.*` — MFC app/document/view skeleton. +- `Management.*` — central editor state and engine glue. +- Editor dialogs (`*dlg` / `*EditorDlg`), e.g. `shipeditordlg.*`, + `wing_editor.*`, `eventeditor.*`, `briefingeditordlg.*`, + `campaigneditordlg.*`, `sexp_tree.*` (the SEXP tree control), `bgbitmapdlg.*`. +- `CMakeLists.txt` — target definition and MFC setup. + +## Relationship to the engine +- FRED edits the same structures the game parses: ships → `ship_info`/`Ships`, + parse objects → `p_object`/`Parse_objects` (`code/mission/missionparse.*`), + events/goals → SEXPs (`code/parse/sexp.*`). +- When you add an author-facing field to a table or a new SEXP, FRED usually needs + a matching UI/editor change here (and in qtFRED). +- FRED-only stubs replace some game-only systems; guard editor-specific behaviour + rather than changing shared engine code. + +## Configuration tables +None of its own; it reads the same content tables as the game (`ships.tbl`, +`weapons.tbl`, etc.) to populate editor lists. + +## See also +- `documentation/modules/qtfred.md` (modern Qt editor), `code/mission/`, + `code/parse/sexp.*`, `documentation/ARCHITECTURE.md`. +- Wiki: https://wiki.hard-light.net/index.php/FRED2 · + Table reference: https://wiki.hard-light.net/index.php/Tables diff --git a/documentation/modules/globalincs.md b/documentation/modules/globalincs.md new file mode 100644 index 00000000000..e19926e7e09 --- /dev/null +++ b/documentation/modules/globalincs.md @@ -0,0 +1,43 @@ +# Module: globalincs — `code/globalincs/` + +## Purpose +**Foundation headers** included almost everywhere. Defines the base types, the +project-wide container aliases, the type-safe flagset, intrusive linked lists, +global size limits, debug/assert macros, and shared global game-state variables. +By the include rules (`.clang-format`), `globalincs/*.h` always sort first. + +## Key files +- `pstypes.h` — base types, `MAX_PLAYERS`, fixed-point `fix`, `Int3()`/`Assert()`, + `UNINITIALIZED`, `FSO_DEBUG`, dir separators. +- `globals.h` — central `MAX_*` size limits shared across modules (see below). +- `vmallocator.h` — `SCP_vector`, `SCP_string`, `SCP_map`, `SCP_unordered_map`, + etc. (the aliases you should use instead of raw `std::`). +- `flagset.h` — `flagset` type-safe bitflag container (paired with the + per-module `*_flags.h` files). +- `linklist.h` — intrusive doubly-linked list macros + `list_range()` iteration. +- `systemvars.h` / `systemvars.cpp` — global runtime state: `Game_mode`, + `Missiontime`, `flFrametime`, `flRealframetime`, detail/skill levels. +- `alphacolors.*` — standard named UI colors (from `colors.tbl`). +- `version.*`, `safe_strings.*`, `scp_defines.h`, `utility.h`, `type_traits.h`. + +## Major constants (the canonical limits) +- `MAX_OBJECTS` (5000), `MAX_SHIPS` (500), `MAX_SHIP_CLASSES` (500), + `MAX_WEAPONS` (3000), `MAX_WEAPON_TYPES` (500). +- `MAX_WINGS` (75), `MAX_SHIPS_PER_WING` (6). +- `MAX_SHIP_PRIMARY_BANKS` (3), `MAX_SHIP_SECONDARY_BANKS` (4), `MAX_SHIP_WEAPONS` (7). +- `MAX_POLYGON_MODELS` (300), `MAX_MODEL_TEXTURES` (64). +- String lengths: `NAME_LENGTH` (32), `TOKEN_LENGTH` (32), `PATHNAME_LENGTH` (192), + `MESSAGE_LENGTH` (512), `MULTITEXT_LENGTH` (4096). +- `MAX_PLAYERS` (12, in `pstypes.h`). + +## Conventions +- Prefer the `SCP_*` aliases over raw `std::` types. +- Change a flag with the owning module's setter (e.g. `obj_set_flags`), not by + poking the `flagset` directly, when side effects are involved. + +## Configuration tables +`colors.tbl` (named UI colors) via `alphacolors.cpp`. + +## See also +- Engine-wide tunables table lives in `code/mod_table/` (`game_settings.tbl`). +- Table option reference: https://wiki.hard-light.net/index.php/Tables diff --git a/documentation/modules/graphics.md b/documentation/modules/graphics.md new file mode 100644 index 00000000000..e09616d94fa --- /dev/null +++ b/documentation/modules/graphics.md @@ -0,0 +1,66 @@ +# Module: graphics — `code/graphics/` + +## Purpose +The **rendering layer**. `2d.h`/`2d.cpp` define an abstract draw API (a set of +`gr_*` function pointers) that the rest of the engine calls without knowing the +backend. Concrete backends (OpenGL, Vulkan, stub) fill those pointers in. Also +owns dynamic lighting, shadows, post-processing, materials, shaders, fonts, and +2D vector paths. + +## Key files +- `2d.h` / `2d.cpp` — abstract interface, `screen` struct, `gr_*` dispatch. +- `opengl/` — primary backend. `vulkan/` — experimental backend. `grstub.cpp` — headless. +- `light.cpp` / `light.h` — dynamic lights (`light_reset()`, add light). +- `shadows.*`, `post_processing.*`, `material.*`, `uniforms.*`. +- `shaders/` (GLSL sources), `paths/` (NanoVG 2D vector), `font.h`, `software/` (fonts). +- `render.cpp` / `render.h`, `matrix.*`, `color.*`. + +## Core data structures / globals +- `gr_screen` (the `screen` struct) — current resolution, the bound `gr_*` calls. +- Backend chosen at init based on `FSO_BUILD_WITH_OPENGL` / `_VULKAN` build options. + +## Major constants +- Backend ids and bit-depth/mode defines in `2d.h`; resize/alignment modes. +- Color/alpha standards live in `code/globalincs/alphacolors.*`. + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `fonts.tbl` | `parse_font_tbl()` (`software/font.cpp`) | Font definitions | +| `post_processing.tbl` | `post_processing.cpp` | Post-processing effect setup | +| `colors.tbl` | `code/globalincs/alphacolors.cpp` | Named UI colors | +| `lighting_profiles.tbl` | `code/lighting/lighting_profiles.cpp` | Lighting presets | + +Table option reference: https://wiki.hard-light.net/index.php/Tables + +## Architecture diagram (abstraction + OpenGL backend) + +```mermaid +flowchart TD + subgraph ABS["Abstraction (graphics/2d.*)"] + engine["engine code calls gr_*
(gr_screen.gf_* function pointers)"] + end + subgraph INIT["Backend selection at init"] + sel{"FSO_BUILD_WITH_OPENGL / _VULKAN"} + sel -->|OpenGL| oglinit["gr_opengl_init_function_pointers()
(gropengl.cpp ~1004): gf_* = gr_opengl_*"] + sel -->|Vulkan| vkinit["vulkan backend (graphics/vulkan)"] + sel -->|headless| stub["grstub.cpp"] + end + engine --> sel + + subgraph OGL["OpenGL render path"] + draw["model_draw_list / render.* build draw queue"] --> rm["gr_opengl_render_model
(gropengldraw)"] + rm --> tnl["transform & lighting
(gropengltnl)"] + tnl --> sh["shader programs
(gropenglshader, ShaderProgram)"] + sh --> st["GL state mgmt
(gropenglstate)"] + st --> def["deferred lighting
(gropengldeferred)"] + def --> post["post-processing
(gropenglpostprocessing)"] + post --> flip["gr_opengl_flip() present
(gropengl.cpp ~116)"] + end + oglinit -.binds.-> OGL + engine --> draw +``` + +## See also +- `code/model/` (model rendering builds on this), `code/nebula/`, `code/starfield/`, + `code/particle/`, `code/lighting/`. diff --git a/documentation/modules/hud.md b/documentation/modules/hud.md new file mode 100644 index 00000000000..5686c3c735d --- /dev/null +++ b/documentation/modules/hud.md @@ -0,0 +1,35 @@ +# Module: hud — `code/hud/` + +## Purpose +The in-flight **Heads-Up Display**: all combat gauges (targeting, reticle, +shields, radar/escort lists, messages, directives, lock indicators, wingman +status, artillery/SSM) and the HUD configuration system. Gauges are data-driven +and configurable through `hud_gauges.tbl`. + +## Key files +- `hud.cpp` / `hud.h` — HUD framework, gauge base, render dispatch. +- `hudparse.cpp` / `hudparse.h` — parses `hud_gauges.tbl` into gauge layout. +- `hudconfig.*` — player-facing HUD configuration screen. +- Per-gauge files: `hudtarget.*`, `hudreticle.*`, `hudshield.*`, `hudescort.*`, + `hudmessage.*`, `hudtargetbox.*`, `hudlock.*`, `hudwingmanstatus.*`, + `hudbrackets.*`, `hudsquadmsg.*`, `hudets.*`, `hudartillery.*`, `hudnavigation.*`. + +## Core data structures / globals +- `HudGauge` (base class) and derived gauge classes. +- Per-ship/player HUD config in `hud_config` (`hudconfig.h`). + +## Major constants +- `MAX_COMPLETE_ESCORT_LIST` (20, in `code/globalincs/globals.h`). +- Gauge id/coordinate defines and color tags within the per-gauge headers. + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `hud_gauges.tbl` | `parse_hud_gauges_tbl()` (`hudparse.cpp`) | Gauge layout/appearance | +| `ssm.tbl` | `parse_ssm()` (`hudartillery.cpp`) | Subspace missile strikes | + +Table option reference: https://wiki.hard-light.net/index.php/Tables (see *hud_gauges.tbl*). + +## See also +- `code/ship/` (target/subsystem data shown), `code/radar/`, `code/graphics/` (drawing), + `code/scripting/` (custom Lua HUD gauges via the HUD-draw hook). diff --git a/documentation/modules/io.md b/documentation/modules/io.md new file mode 100644 index 00000000000..a116d5297ef --- /dev/null +++ b/documentation/modules/io.md @@ -0,0 +1,32 @@ +# Module: io — `code/io/` + +## Purpose +**Low-level input and timing**: keyboard, mouse, joystick/gamepad (SDL-based), +force feedback, the cursor, and the millisecond/mission timers. Raw input is +translated into game *actions* by the separate `code/controlconfig/` module. + +## Key files +- `key.cpp` / `key.h` — keyboard state, scancodes, key queue. +- `mouse.cpp` / `mouse.h` — mouse position/buttons. +- `joy-sdl.cpp` / `joy.h`, `joy_ff-sdl.cpp` / `joy_ff.h` — joystick + force feedback. +- `timer.cpp` / `timer.h` — `timer_get_milliseconds()`, timestamps. +- `keycontrol.cpp` — in-mission key handling (`game_process_keys`). +- `cursor.*`, `spacemouse.*`. + +## Core data structures / globals +- Key/mouse global state queried via `key_*` / `mouse_*` functions. +- `timestamp` and `Missiontime`/`flFrametime` timing (timing helpers here; + game-time globals in `code/globalincs/systemvars.*`). + +## Major constants +- Key scancodes `KEY_*` and modifier masks (`KEY_SHIFTED`, etc.) in `key.h`. +- Mouse button bits `MOUSE_LEFT_BUTTON`, etc. (`mouse.h`). + +## Configuration tables +Input *bindings* are owned by `code/controlconfig/` → +`controlconfigdefaults.tbl` (`controlsconfigcommon.cpp`). + +## See also +- `code/controlconfig/` (maps inputs → game actions / presets), + `code/osapi/` (window + OS event pump that feeds this). +- Table option reference: https://wiki.hard-light.net/index.php/Tables diff --git a/documentation/modules/lab.md b/documentation/modules/lab.md new file mode 100644 index 00000000000..5f5965e9bff --- /dev/null +++ b/documentation/modules/lab.md @@ -0,0 +1,85 @@ +# Module: lab — `code/lab/` + +## Purpose +The **Lab** (a.k.a. LabV2) is an in-engine viewer/test bench for inspecting game +assets without building a mission. It can display and manipulate a single ship, +weapon, asteroid, or prop using the real engine renderer, with an ImGui-based +control panel for render flags, texture/lighting options, animations, weapon +firing, docking/bay tests, and backgrounds. It is a game state +(`GS_STATE_LAB`), entered from the main hall, not a separate executable. + +## Key files +- `labv2.h` / `labv2.cpp` — public entry points (`lab_init`, `lab_do_frame`, + `lab_close`) and `enum class LabMode { Asteroid, Ship, Weapon, Prop, None }`. +- `labv2_internal.h` — `getLabManager()` accessor + ImGui includes. +- `manager/lab_manager.{h,cpp}` — `LabManager`: owns state, spawns/changes the + displayed object, runs per-frame logic, docking/bay tests, cleanup. +- `renderer/lab_renderer.{h,cpp}` — `LabRenderer`: draws the model + HUD, owns + render flags, lighting/tonemapper/AA/bloom and background settings. +- `renderer/lab_cameras.{h,cpp}` — `LabCamera` / `OrbitCamera` view control. +- `dialogs/lab_ui.{h,cpp}` — `LabUi`: the ImGui control panel (object lists, + options menus, toolbars, subsystem/weapon/dock test UI). +- `dialogs/lab_ui_helpers.{h,cpp}` — table-text lookups and UI helpers. + +## Core data structures / globals +- `LabManager` (single instance via `getLabManager()`) — `CurrentMode`, + `CurrentObject`/`CurrentClass`/`CurrentSubtype`, docker/bay objects, fire-state + arrays (`FirePrimaries`/`FireSecondaries`/`FireTurrets`), `flagset`. +- `LabRenderer` (owned by the manager as `Renderer`) — `flagset`, + `gfx_options`, current camera, team color, background. +- `LabUi` (owned by the manager) — builds the ImGui panels each frame. +- Displayed assets are real engine `object`s (`OBJ_SHIP`/`OBJ_WEAPON`/`OBJ_BEAM`/ + `OBJ_ASTEROID`/`OBJ_PROP`); the `isSafeFor*()` guards validate type before use. + +## Major constants / enums +- `LabMode` (Asteroid, Ship, Weapon, Prop, None). +- `LabRotationMode` (Both, Yaw, Pitch, Roll); `ManagerFlags` (`ModelRotationEnabled`). +- `LabRenderFlag` — large render-toggle set (wireframe, full detail, thrusters, + insignia, per-map disables: diffuse/glow/spec/normal/height/AO, lighting, + particles, post-processing, orthographic projection, time stopped, …). +- `TextureQuality`, `TextureChannel`, `TextureOverride`, `LabTurretAimType`, + `BayMode` (Arrival/Departure). +- `LAB_MISSION_NONE_STRING` ("None"), `LAB_TEAM_COLOR_NONE` (""). + +## Game-state integration (`freespace.cpp`) +- `GS_EVENT_LAB` → `gameseq_push_state(GS_STATE_LAB)`. +- Enter `GS_STATE_LAB` → `lab_init()` (sets `GM_LAB`, pauses AI, etc.). +- Per frame → `lab_do_frame(flFrametime)` → `LabManager::onFrame()`. +- Leave → `lab_close()` → `LabManager::close()` (restores cmdline collision flag, + frees models/asteroids/props, clears `GM_LAB`, posts `GS_EVENT_PREVIOUS_STATE`). + +## Configuration tables +None of its own. The Lab *reads* existing content tables to populate its lists: +`ships.tbl`, `weapons.tbl`, `asteroid.tbl`, `props.tbl` (via +`dialogs/lab_ui_helpers.cpp`), plus lighting/graphics from `lighting_profiles.tbl`. + +Table option reference: https://wiki.hard-light.net/index.php/Tables + +## Architecture diagram (Lab state + frame) + +```mermaid +flowchart TD + mh["Main hall → GS_EVENT_LAB"] --> push["gameseq_push_state(GS_STATE_LAB)"] + push --> init["lab_init() (sets GM_LAB, pauses AI)"] + init --> mgr["LabManager (getLabManager())"] + + loop["lab_do_frame(frametime)"] --> onframe["LabManager::onFrame()"] + onframe --> ui["LabUi::create_ui()
ImGui panels: object lists, options, tests"] + ui -->|user picks asset| change["changeDisplayedObject(LabMode, info_index)
spawns real object (OBJ_SHIP/WEAPON/...)"] + onframe --> input["handle keyboard/mouse → camera + rotation"] + onframe --> render["LabRenderer::onFrame()"] + render --> rmodel["renderModel() → engine model/graphics path"] + render --> rhud["renderHud()"] + change -.-> render + + tests["docking / undocking / bay tests
(spawnDockerObject, beginBayTest, ...)"] -.-> onframe + + close["leave state → lab_close()"] --> cleanup["LabManager::close(): cleanup objects,
free models/asteroids/props, clear GM_LAB"] + cleanup --> prev["gameseq_post_event(GS_EVENT_PREVIOUS_STATE)"] +``` + +## See also +- `code/ship/`, `code/weapon/`, `code/asteroid/`, `code/prop/` (assets displayed). +- `code/model/` & `code/graphics/` (rendering), `code/camera/` (view), + `code/lighting/lighting_profiles.*` (lab lighting), `code/libs/` (ImGui via `lib/imgui`). +- `documentation/ARCHITECTURE.md` (game-state machine, frame loop). diff --git a/documentation/modules/localization.md b/documentation/modules/localization.md new file mode 100644 index 00000000000..0f285022140 --- /dev/null +++ b/documentation/modules/localization.md @@ -0,0 +1,40 @@ +# Module: localization — `code/localization/` + +## Purpose +**Translation and localized text.** Manages the active language, loads translated +string tables (`strings.tbl`/`tstrings.tbl`), and resolves `XSTR("...", id)` +lookups used throughout the UI and game text. Also handles language +auto-detection and the special-character font offsets per language. + +## Key files +- `localize.cpp` / `localize.h` — language state, table parsing, `XSTR` resolution. +- `fhash.cpp` / `fhash.h` — string hashing used for fast lookup. + +## Core data structures / globals +- `lang_info` + `Lcl_languages` (`SCP_vector`) and `Lcl_builtin_languages[]` — + the supported-language table (name, on-disk extension, special chars, checksum). +- `int Lcl_current_lang` — index of the active language. +- `Lcl_special_chars`, `Lcl_en/fr/gr/pl` — font offsets / language flags. + +## Major constants +- Language ids: `LCL_ENGLISH` (0), `LCL_GERMAN` (1), `LCL_FRENCH` (2), + `LCL_POLISH` (3); `NUM_BUILTIN_LANGUAGES` (4). +- `LCL_DEFAULT` (0), `LCL_UNTRANSLATED` (100), `LCL_RETAIL_HYBRID` (101). +- `LCL_LANG_NAME_LEN` (32), `LCL_MIN_FONTS` (3). + +## Translation lookup pattern +- Engine code wraps translatable literals in `XSTR("English text", id)`; at runtime + the id is looked up in the active language's string table, falling back to the + literal when untranslated. + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `strings.tbl` | `parse_stringstbl()` | Built-in (engine) `XSTR` translations | +| `tstrings.tbl` | `parse_tstringstbl()` | Mod/mission (`+Tstrings`) translations | + +Table option reference: https://wiki.hard-light.net/index.php/Tables + +## See also +- `code/graphics/software/font.cpp` (`fonts.tbl`, special-character fonts), + `code/parse/parselo.*` (table parsing), most UI modules (consumers of `XSTR`). diff --git a/documentation/modules/menuui.md b/documentation/modules/menuui.md new file mode 100644 index 00000000000..5aa8e118b93 --- /dev/null +++ b/documentation/modules/menuui.md @@ -0,0 +1,72 @@ +# Module: menuui — `code/menuui/` + +## Purpose +The **non-mission menu screens** — the front-end interface the player navigates +outside of a mission. Each screen is its own game state and is built on the +low-level widget toolkit in `code/ui/`. Covers the main hall, pilot select, +barracks, tech room, ready room/campaign select, training menu, credits, and the +retail Options screen. + +These are the classic/retail-style screens (bitmap layouts + `code/ui/` widgets); +the newer libRocket-based front end lives in `code/scpui/`, and the modern options +framework in `code/options/`. + +## Key files → screen (game state) +| File | Screen | Game state | +| --- | --- | --- | +| `mainhallmenu.{cpp,h}` | Main hall (hub) | `GS_STATE_MAIN_MENU` | +| `playermenu.{cpp,h}` | Pilot create/select | `GS_STATE_INITIAL_PLAYER_SELECT` | +| `barracks.{cpp,h}` | Barracks (pilot mgmt, medals, stats) | `GS_STATE_BARRACKS_MENU` | +| `techmenu.{cpp,h}` | Tech room (ship/weapon/intel database) | `GS_STATE_TECH_MENU` | +| `readyroom.{cpp,h}` | Mission/campaign sim room | `GS_STATE_SIMULATOR_ROOM` | +| `trainingmenu.{cpp,h}` | Training menu | `GS_STATE_TRAINING_MENU` | +| `credits.{cpp,h}` | Credits | `GS_STATE_CREDITS` | +| `optionsmenu.{cpp,h}` | Retail Options screen | `GS_STATE_OPTIONS_MENU` | +| `optionsmenumulti.{cpp,h}` | Multiplayer options sub-screen | (within options) | + +Helpers: +- `snazzyui.{cpp,h}` — "snazzy menu" region/hotspot interaction system used by + bitmap menus (reads `menu.tbl`). +- `fishtank.{cpp,h}` — the animated fish in the main hall aquarium. + +## Core concepts +- Each screen follows the engine state pattern: an `*_init()` (enter), a + per-frame `*_do_frame()` / `*_do()` (called from `game_do_state`), and a + `*_close()` (leave). See `freespace.cpp` state dispatch and + `documentation/ARCHITECTURE.md`. +- Screens instantiate `code/ui/` gadgets (`UI_WINDOW`, `UI_BUTTON`, …) over a + background bitmap, with mask-based hotspots (snazzy menu). +- The main hall is data-driven by `mainhall.tbl` (regions, animations, sounds). + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `mainhall.tbl` | `parse_main_hall_table()` (`mainhallmenu.cpp`) | Main hall layout/regions/animations | +| `credits.tbl` | `credits_parse_table()` (`credits.cpp`) | Credits text | +| `tips.tbl` | `parse_tips_table()` (`playermenu.cpp`) | Startup tips | +| `intel.tbl` / `species.tbl` | `techmenu.cpp` | Tech-room intel entries | +| `menu.tbl` | `snazzyui.cpp` | Snazzy-menu hotspot definitions | + +Table option reference: https://wiki.hard-light.net/index.php/Tables + +## Architecture diagram (front-end navigation) + +```mermaid +flowchart TD + pilot["Pilot select
(playermenu, GS_STATE_INITIAL_PLAYER_SELECT)"] --> hall["Main hall
(mainhallmenu, GS_STATE_MAIN_MENU)"] + hall -->|region click via snazzyui| barracks["Barracks
(barracks)"] + hall --> tech["Tech room
(techmenu)"] + hall --> ready["Ready/Sim room
(readyroom)"] + hall --> train["Training menu
(trainingmenu)"] + hall --> opt["Options (retail)
(optionsmenu, GS_STATE_OPTIONS_MENU)"] + hall --> credits["Credits
(credits)"] + ready -->|start mission| brief["Briefing → gameplay
(code/missionui, code/mission)"] + opt --> multiopt["Multi options
(optionsmenumulti)"] + tbls["mainhall.tbl · menu.tbl · credits.tbl ·
tips.tbl · intel.tbl"] -.data.-> hall +``` + +## See also +- `code/ui/` (widget toolkit these screens use), `code/scpui/` (modern front end), + `code/options/` (modern Options framework), `code/missionui/` (briefing/loadout), + `code/playerman/` & `code/pilotfile/` (pilot data shown in barracks). +- `documentation/ARCHITECTURE.md` (game-state machine). diff --git a/documentation/modules/mission.md b/documentation/modules/mission.md new file mode 100644 index 00000000000..519a1eb0efb --- /dev/null +++ b/documentation/modules/mission.md @@ -0,0 +1,41 @@ +# Module: mission — `code/mission/` + +## Purpose +Loads and runs **missions and campaigns**. Parses `.fs2` mission files into spawn +data, evaluates arrivals/departures and goals each frame, and owns mission-time +features: objectives, in-mission messages/personas, training directives, +briefings/debriefings, the mission log, and campaign progression. + +## Key files +- `missionparse.cpp` / `missionparse.h` — `.fs2` parsing, `p_object`, + `Parse_objects`, arrival/departure evaluation, ship/wing spawning. +- `missiongoals.*` — objectives/goals (`mission_eval_goals()`). +- `missionmessage.*` — in-mission messages, personas, voice. +- `missiontraining.*` — training directives/objectives. +- `missionbriefcommon.*` — briefing/debriefing data and icons. +- `missioncampaign.*` — campaign state and progression. +- `missionload.*`, `missionlog.*`, `missionhotkey.*`, `missiongrid.*`, + `mission_flags.h`. + +## Core data structures / globals +- `SCP_vector Parse_objects` — parsed-but-not-yet-spawned ship entries. +- Mission globals such as `The_mission`, plus time globals `Missiontime` + (in `code/globalincs/systemvars.*`). + +## Major constants +- `MISSION_DESC_LENGTH` (512), message/training lengths in `code/globalincs/globals.h`. +- Mission/parse flags in `mission_flags.h`. + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `messages.tbl` | `parse_msgtbl()` / `parse_custom_message_table()` | Built-in & custom messages | +| `icons.tbl` | `brief_parse_icon_tbl()` | Briefing icons | + +Mission content itself comes from `.fs2` files (not `.tbl`). + +Table option reference: https://wiki.hard-light.net/index.php/Tables + +## See also +- `code/parse/` (parsing + SEXPs that drive mission logic), `code/ship/` (spawned entities), + `code/missionui/` & `code/menuui/` (briefing/debrief screens). diff --git a/documentation/modules/mod_table.md b/documentation/modules/mod_table.md new file mode 100644 index 00000000000..f68194f51c8 --- /dev/null +++ b/documentation/modules/mod_table.md @@ -0,0 +1,30 @@ +# Module: mod_table — `code/mod_table/` + +## Purpose +Owns **`game_settings.tbl`** — the engine-wide, per-mod tunables table. This is +where global feature toggles and default behaviour switches live (collision +behaviour, default detail, scoring, AI/physics defaults exposed at the mod level, +fiction/cutscene options, and many compatibility flags). When a mod needs to +change engine-global behaviour rather than a specific ship/weapon, it goes here. + +## Key files +- `mod_table.cpp` / `mod_table.h` — `parse_mod_table()` and the global flag set. + +## Core data structures / globals +- Many free-standing `extern` globals (e.g. `Weapons_inherit_parent_collision_group`) + set from the table and read across the engine. + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `game_settings.tbl` | `parse_mod_table()` | Engine-wide / per-mod behaviour flags | + +Table option reference: https://wiki.hard-light.net/index.php/Game_settings.tbl +and the general index: https://wiki.hard-light.net/index.php/Tables + +## Related global-settings modules +- `code/options/` — `default_settings.tbl` (default player option values). +- `code/globalincs/` — compile-time `MAX_*` limits (not table-driven). + +## See also +- Almost every gameplay module reads one or more flags defined here. diff --git a/documentation/modules/model.md b/documentation/modules/model.md new file mode 100644 index 00000000000..3bc05cb5451 --- /dev/null +++ b/documentation/modules/model.md @@ -0,0 +1,57 @@ +# Module: model — `code/model/` + +## Purpose +Loads and renders **POF 3D models** and manages their runtime state. Handles +submodels/subobjects, detail levels (LODs), turret/gun points, docking bays, +thrusters, eye points, insignias, glowpoints, and model-vs-ray/sphere collision. +Splits static geometry (`polymodel`) from per-object runtime state +(`polymodel_instance`). + +## Key files +- `model.h` — `polymodel`, `polymodel_instance`, `bsp_info` (submodels), many limits. +- `modelread.cpp` — POF loader. +- `modelinterp.cpp` — legacy/interp rendering. `modelrender.cpp` / `.h` — draw lists. +- `modelcollide.cpp` — ray/sphere-vs-model collision. +- `modelreplace.cpp` — virtual POF assembly (`virtual_pofs.tbl`). +- `model_flags.h`, `animation/` (subobject + procedural animation). + +## Core data structures / globals +- `polymodel` — geometry, submodels, subsystems, paths, detail levels. +- `polymodel_instance` — per-object animation/submodel orientation state. +- Lookups from an object: `object_get_model()` / `object_get_model_instance()` + (declared in `code/object/object.h`). + +## Major constants +- `MAX_MODEL_DETAIL_LEVELS` (8), `MAX_MODEL_TEXTURES` (64), + `MAX_POLYGON_MODELS` (300), `MAX_MODEL_SUBSYSTEMS` (200). +- `MAX_DEBRIS_OBJECTS` (32), `MAX_LIVE_DEBRIS` (7), `MAX_TFP` (10 turret fire points). +- `MAX_DOCK_SLOTS` (2), `MAX_SHIP_BAY_PATHS` (31), `MAX_EYES` (10), + `MAX_SPLIT_PLANE` (5), `MAX_ARC_EFFECTS` (8), `MAX_MODEL_INSIGNIAS` (6). + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `glowpoints.tbl` | `parse_glowpoint_table()` | Reusable glowpoint presets | +| `virtual_pofs.tbl` | `parse_virtual_pof_table()` | Assemble models from POF parts | + +Table option reference: https://wiki.hard-light.net/index.php/Tables + +## Architecture diagram (load → render → collide) + +```mermaid +flowchart TD + pof[".pof file"] -->|modelread.cpp| pm["polymodel
(geometry, submodels/bsp, subsystems, LODs)"] + vpof["virtual_pofs.tbl"] -->|modelreplace.cpp| pm + pm --> inst["polymodel_instance
(per-object submodel orientation/animation)"] + anim["animation/ (subobject + procedural)"] -.-> inst + + inst --> rq["obj_queue_render() (code/object)"] + rq --> mdl["model_render_queue() (modelrender.cpp)
builds model_draw_list"] + mdl --> gr["gr_render_model (code/graphics)"] + + pm --> col["modelcollide.cpp
ray/sphere vs bsp tree"] + col --> hit["collision hit point + submodel"] +``` + +## See also +- `code/graphics/` (rendering backend), `code/ship/` (subsystems map to submodels). diff --git a/documentation/modules/network.md b/documentation/modules/network.md new file mode 100644 index 00000000000..450296b3bb2 --- /dev/null +++ b/documentation/modules/network.md @@ -0,0 +1,57 @@ +# Module: network — `code/network/` + +## Purpose +**Multiplayer networking**: client/server session management, packet (message) +exchange, object/ship state synchronization and interpolation, respawning, teams, +chat, voice, the standalone server, and the PXO online lobby/tracker integration. +Files are prefixed `multi_*`; `multi.h` is the central include. + +## Key files +- `multi.h` / `multi.cpp` — core multiplayer state, `Net_player`, `Netgame`. +- `multimsgs.*` — packet definitions and send/receive. +- `multi_obj.*` — object update packets. `multi_interpolate.*` — client smoothing. +- `multi_respawn.*`, `multi_team.*`, `multi_endgame.*`, `multi_pause.*`. +- `psnet2.*` — low-level sockets/UDP. `multi_pxo.*` — online lobby. +- `multi_sexp.*` — networking SEXP packets. `multi_lua.*` — scripting sync. +- `stand_gui*.cpp` — standalone dedicated-server UI. + +## Core data structures / globals +- `net_player Net_player` / `Net_players[]` — connected players. +- `netgame_info Netgame` — current session state (`NETGAME_STATE_*`). +- Object net identity: `object::net_signature`. + +## Major constants +- `MAX_PLAYERS` (12, in `code/globalincs/pstypes.h`), `MAX_OBSERVERS` (4). +- `MAX_GAMENAME_LEN` (32), `MAX_PASSWD_LEN` (16), `MAX_RESPAWN_POINTS` (25), + `MAX_PINGS` (10), `MAX_TYPE_ID` (0xFF), `MAX_IP_ADDRS` (100). +- Modes: `MULTIPLAYER_MASTER`, `MULTIPLAYER_CLIENT` macros; `GM_MULTIPLAYER`, + `GM_STANDALONE_SERVER` (game-mode bits in `code/globalincs/systemvars.*`). + +## Configuration tables +None directly (networked content derives from ships/weapons/mission tables). + +## Architecture diagram (client/server object sync) + +```mermaid +flowchart LR + subgraph SERVER["Server (MULTIPLAYER_MASTER)"] + sim["authoritative simulation
(obj_move_all, AI, collisions)"] --> pack["multi_obj.cpp
pack object updates"] + pack --> msg["multimsgs.cpp
build packets"] + msg --> sock_s["psnet2.cpp (UDP)"] + end + + subgraph CLIENT["Client (MULTIPLAYER_CLIENT)"] + sock_c["psnet2.cpp (UDP)"] --> unpack["multi_obj unpack"] + unpack --> interp["multi_interpolate.cpp
smooth via physics_snapshot"] + interp --> local["local object state for render"] + end + sock_s -->|state packets| sock_c + sock_c -->|input / fire packets| sock_s + + extra["multi_sexp (event sync) · multi_respawn · multi_team
chat/voice · Netgame state (NETGAME_STATE_*)"] -.-> msg +``` + +## See also +- `code/physics/physics_state.*` (interpolation snapshots), `code/object/multi_obj` flow, + `code/parse/sexp.*` (`multi_sexp`). +- Table option reference: https://wiki.hard-light.net/index.php/Tables diff --git a/documentation/modules/object.md b/documentation/modules/object.md new file mode 100644 index 00000000000..6842eb24592 --- /dev/null +++ b/documentation/modules/object.md @@ -0,0 +1,65 @@ +# Module: object — `code/object/` + +## Purpose +Defines the **base entity** of the engine. Every dynamic thing in the world +(ship, weapon, debris, fireball, asteroid, beam, …) is an `object`. This module +owns object storage, lifecycle (create/move/delete), the per-frame update driver +(`obj_move_all`), and collision-pair management. The `object` struct holds the +*common* state (position, orientation, physics, hull/shields, flags); the +`type` + `instance` fields point to the type-specific array (e.g. `Ships[]`). + +## Key files +- `object.h` / `object.cpp` — the `object` struct, global `Objects[]`, lifecycle. +- `object_flags.h` — `Object::Object_Flags` flagset values. +- `objcollide.cpp` / `objcollide.h` — broad-phase collision pair generation. +- `collide*.cpp` (`collideshipship`, `collideshipweapon`, …) — per-type collisions. +- `objectsnd.cpp` — persistent per-object sounds. +- `objectdock.cpp`, `objectshield.cpp`, `parseobjectdock.cpp`, `waypoint.cpp`. +- `deadobjectdock.cpp`, `object_quadtree.cpp` (spatial partitioning). + +## Core data structures / globals +- `class object` — common entity state (see `object.h`). +- `object Objects[]` — global storage (capacity `MAX_OBJECTS`). +- `obj_used_list` / `obj_free_list` / `obj_create_list` — intrusive linked lists. +- `object *Player_obj`, `object *Viewer_obj` — key global pointers. +- `struct object_h` — safe handle (objnum + signature) for cross-frame refs. + +## Major constants +- Object types: `OBJ_NONE`, `OBJ_SHIP`, `OBJ_WEAPON`, `OBJ_FIREBALL`, `OBJ_START`, + `OBJ_WAYPOINT`, `OBJ_DEBRIS`, `OBJ_GHOST`, `OBJ_POINT`, `OBJ_SHOCKWAVE`, + `OBJ_WING`, `OBJ_OBSERVER`, `OBJ_ASTEROID`, `OBJ_JUMP_NODE`, `OBJ_BEAM`, + `OBJ_RAW_POF`, `OBJ_PROP` (`MAX_OBJECT_TYPES`). +- `MAX_OBJECTS` (5000, in `globals.h`) — must stay < 2^16-1 for collision caching. +- `DEFAULT_SHIELD_SECTIONS` (4), `UNUSED_OBJNUM`. + +## Lifecycle notes +- Create via `obj_create()`; each type wraps it in `*_create()`. +- Per-frame movement runs through `obj_move_all()` → `obj_move_all_pre/post()`. +- **To destroy: set the `Should_be_dead` flag.** Cleanup happens later in + `obj_delete_all_that_should_be_dead()`. +- Use `obj_set_flags()` (not direct flag writes) so collision state stays correct. + +## Configuration tables +None directly. Object *types* (categories) are defined in `objecttypes.tbl` +(parsed by the ship module). + +## Architecture diagram (object update + lifecycle) + +```mermaid +flowchart TD + create["*_create() → obj_create()
adds to obj_used_list"] --> loop + loop["obj_move_all(frametime)
iterate obj_used_list"] --> pre["pre_move event → obj_move_all_pre()"] + pre --> phys["obj_move_call_physics()
(code/physics)"] + phys --> typemove["type-specific move
(ship_process_post, weapon move, ...)"] + typemove --> post["obj_move_all_post() → post_move event"] + post --> collide["collision pairs
(objcollide → collide*_check)"] + collide --> killcheck{"Should_be_dead
flag set?"} + killcheck -->|yes| reap["obj_delete_all_that_should_be_dead()
→ *_delete() → obj_delete()"] + killcheck -->|no| render + render["obj_render_all() → obj_queue_render()
→ model_draw_list (code/model)"] + note["type + instance → type array
(Ships[], Weapons[], ...)"] -.-> typemove +``` + +## See also +- `code/ship/`, `code/weapon/`, `code/physics/`, `code/model/`. +- Table option reference: https://wiki.hard-light.net/index.php/Tables diff --git a/documentation/modules/options.md b/documentation/modules/options.md new file mode 100644 index 00000000000..f30499b0ae9 --- /dev/null +++ b/documentation/modules/options.md @@ -0,0 +1,109 @@ +# Module: options — `code/options/` (Ingame Options) + +## Purpose +The **SCP options system**: a declarative, type-safe framework for defining +player-facing settings and the **in-game Options menu** that displays them. Any +module can register an option with a fluent builder; the framework handles UI +generation, value display/serialization, persistence to the player config, preset +("Basic/Low/Medium/High/Ultra") values, and per-mod default overrides. + +This is distinct from: +- **`code/cmdline/`** — launch-time flags (not player-editable in-game). +- **`code/mod_table/`** (`game_settings.tbl`) — engine/mod behaviour, not player options. +- The legacy **retail** options screen in `code/menuui/optionsmenu*` (`GS_STATE_OPTIONS_MENU`). + +## Key files +- `Option.h` — the core: `OptionBase`, `Option`, and the fluent `OptionBuilder`. +- `Option.cpp` — type default (de)serializers (`internal::set_defaults`). +- `manager/OptionsManager.{h,cpp}` — `OptionsManager` singleton: registry, + config overrides, change tracking, persistence, enforced options. +- `Ingame_Options.{h,cpp}` + `Ingame_Options_internal.h` — the in-game Options + state logic (`ingame_options_init/close/do_frame`, `Option_categories`). +- `dialogs/ingame_options_ui.{h,cpp}` — the ImGui Options UI. +- `manager/ingame_options_manager.{h,cpp}` — in-game options UI manager. +- `default_settings_table.{h,cpp}` — `default_settings.tbl` parsing + (`default_settings_init`), per-mod default option values. + +## Core data structures +- `OptionBuilder` — fluent builder; `.finish()` constructs the option and + registers it with `OptionsManager::instance()`. +- `Option` / `OptionBase` — a single option (key, title, description, category, + expert level, range/selection type, serializer/deserializer, change listener). +- `OptionsManager` (singleton) — holds all `_options`, `_config_overrides`, + `_changed_values`, `_enforcedOptions`; does `persistChanges()` / `discardChanges()` + / `loadInitialValues()`. +- `ValueDescription` — a `{display, serialized}` pair (display string + JSON). + +## Major enums / flags (`Option.h`) +- `OptionType` — `Range` (slider) or `Selection` (discrete values). +- `ExpertLevel` — `Beginner`, `Advanced`, `Expert` (UI filtering). +- `PresetKind` — `Basic`, `Low`, `Medium`, `High`, `Ultra` (graphics presets). +- `OptionFlags` — `ForceMultiValueSelection`, `RetailBuiltinOption`, `RangeTypeInteger`. + +## Defining an option (registered at static init) +Options are declared next to the code they affect (search `OptionBuilder<` — they +live in `io/`, `graphics/`, `sound/`, `network/`, etc.). Example from `io/mouse.cpp`: + +```cpp +static auto MouseSensitivityOption __UNUSED = + options::OptionBuilder("Input.MouseSensitivity", + std::pair{"Sensitivity", 1374}, // title (XSTR) + std::pair{"The sensitivity of the mouse input", 1747}) + .category(std::make_pair("Input", 1827)) + .range(0, 9) + .level(options::ExpertLevel::Beginner) + .default_val(4) + .bind_to(&Mouse_sensitivity) // live-applied + .importance(0) + .flags({options::OptionFlags::RetailBuiltinOption}) + .finish(); +``` + +Useful builder methods: `.values({...})` (discrete), `.range(min,max)` (slider), +`.display(fn)`, `.change_listener(fn)`, `.bind_to(ptr)` (apply immediately), +`.bind_to_once(ptr)` (needs restart to persist), `.parser(fn)` + `.default_func(fn)` +(parse default from `default_settings.tbl`), `.preset(kind, value)`, `.flags(...)`. + +## Persistence & defaults +- Changed values are tracked in `OptionsManager` and written by `persistChanges()` + (JSON in the player config). Options that can't change live return from + `persistChanges()` to signal a **restart required**. +- **Defaults:** `.default_val` / `.default_func`; mods can override first-launch + defaults via `default_settings.tbl` (`.parser(...)` reads `$Option Key:` blocks). +- **Enforced options:** `enforceOption()` hides an option and ignores the user's + saved value (e.g. forced by a mod). + +## Game-state integration (`freespace.cpp`) +- `GS_STATE_INGAME_OPTIONS` → `ingame_options_init()` / `ingame_options_do_frame()` + — the SCP options UI (reachable in-mission and from menus). +- `GS_STATE_OPTIONS_MENU` → legacy retail options screen (`code/menuui/optionsmenu*`). + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `default_settings.tbl` | `default_settings_init()` (`default_settings_table.cpp`) | Per-mod default/first-launch option values | + +Table option reference: https://wiki.hard-light.net/index.php/Tables + +## Architecture diagram (option lifecycle) + +```mermaid +flowchart TD + decl["modules declare options at static init
OptionBuilder(...).finish()"] --> reg["OptionsManager::instance()->addOption()"] + defs["default_settings.tbl
(default_settings_init)"] -.default overrides.-> reg + reg --> load["loadInitialValues()
apply saved config or defaults"] + load --> bind["change_listener / bind_to → live engine vars"] + + ui["GS_STATE_INGAME_OPTIONS
ingame_options_do_frame() (ImGui)"] --> list["read OptionsManager::getOptions()
grouped by category / expert level"] + list --> edit["player edits value"] + edit --> setv["setConfigValue() → _changed_values"] + setv --> apply{"persist or discard?"} + apply -->|persistChanges| save["write JSON config; apply live
(restart-required list returned)"] + apply -->|discardChanges| revert["drop changes"] + save --> bind +``` + +## See also +- `code/cmdline/` (launch flags), `code/mod_table/` (engine/mod settings), + `code/menuui/optionsmenu*` (retail options screen), `code/localization/` (`XSTR` titles). +- `code/libs/jansson.*` (JSON), `code/osapi/osregistry.*` (config storage). diff --git a/documentation/modules/parse.md b/documentation/modules/parse.md new file mode 100644 index 00000000000..003a207eb9c --- /dev/null +++ b/documentation/modules/parse.md @@ -0,0 +1,59 @@ +# Module: parse — `code/parse/` + +## Purpose +Two related responsibilities: +1. **Text/table parsing** (`parselo.*`) — the hand-rolled tokenizer used to read + every `.tbl`/`.tbm` table and `.fs2` mission file. +2. **SEXPs** (`sexp.*`, `sexp/`) — the S-expression language mission designers use + for events, goals, triggers and conditionals (the engine's "mission scripting"). + +## Key files +- `parselo.h` / `parselo.cpp` — `read_file_text()`, `required_string()`, + `optional_string()`, `stuff_int/float/string()`, global parse pointer `Mp`. +- `parsehi.cpp` — higher-level parse helpers. +- `sexp.h` / `sexp.cpp` — the `OP_*` operator set and evaluator (very large file). +- `sexp/` — newer/modularized SEXP operator implementations. +- `sexp_container.cpp` / `.h` — list/map containers for SEXPs. +- `encrypt.*`, `md5_hash.*`, `generic_log.*`. + +## Core concepts +- **Parsing idiom:** load text, then advance `Mp` with `required_string`/ + `optional_string` + `stuff_*`. To add a table option, copy an adjacent + `optional_string(...)` block in the owning module's parse function. +- **SEXP node:** parsed into a tree of nodes (`Sexp_nodes`); operators identified + by `OP_*` constants and dispatched in `eval_sexp()`. + +## Major constants +- Field-type ids for `stuff`-style parsing: `F_NAME`, `F_DATE`, `F_SEXP`, + `F_MULTITEXT`, `F_PATHNAME`, `F_RAW`, … (`parselo.h`). +- SEXP: `OPERATOR_LENGTH` (30), `MAX_SEXP_VARIABLES` (250), `FIRST_OP` (0x0400), + `OP_INSERT_FLAG`/`OP_REPLACE_FLAG`, atom types `SEXP_ATOM_*`, return values + `SEXP_TRUE/FALSE/KNOWN_TRUE/KNOWN_FALSE/UNKNOWN`, variable flags `SEXP_VARIABLE_*`. + +## Configuration tables +`parselo` reads *all* tables; it doesn't own one itself. `strings.tbl` / +`tstrings.tbl` are read by `code/localization/localize.cpp`. + +## Architecture diagram (SEXP evaluation) + +```mermaid +flowchart TD + fs2[".fs2 mission file
(events, goals, triggers)"] -->|parselo parse| tree["Sexp_nodes tree
(operators + arguments)"] + eval_src["mission_eval_goals() / event triggers
(code/mission)"] --> eval["eval_sexp(node)
(sexp.cpp ~28166)"] + tree --> eval + eval --> ident["identify OP_* via Operators table
(sexp.cpp ~146)"] + ident --> sw["switch(op) dispatch"] + sw --> handler["operator handler
(reads args via CAR/CDR walk)"] + handler --> ret{"result type"} + ret -->|"SEXP_TRUE / SEXP_FALSE"| state["apply effect / report goal status"] + ret -->|"SEXP_KNOWN_TRUE/FALSE"| cache["cache & short-circuit
(never re-evaluated)"] + ret -->|number / string| state + handler -.->|server-authoritative effects| multi["multi_sexp pack/send
(code/network)"] + state --> help["FRED shows operator from
Sexp_help (sexp.cpp ~37998)"] +``` + +## See also +- `code/mission/` (consumes both parsing and SEXPs), `code/cfile/` (file access), + `code/scripting/` (Lua, the *other* scripting system). +- Adding a SEXP: add an `OP_*` enum, an `Operators[]` entry, and a handler in `sexp.cpp`. +- Table option reference: https://wiki.hard-light.net/index.php/Tables diff --git a/documentation/modules/physics.md b/documentation/modules/physics.md new file mode 100644 index 00000000000..09fee7da197 --- /dev/null +++ b/documentation/modules/physics.md @@ -0,0 +1,45 @@ +# Module: physics — `code/physics/` + +## Purpose +The **flight/movement model**. Integrates velocity, acceleration, rotation, +damping, afterburner, gliding and Newtonian behaviour for every object. A +`physics_info` struct is embedded in every `object`; this module advances it each +frame. Also provides physics *snapshots* used by multiplayer interpolation/rollback. + +## Key files +- `physics.h` / `physics.cpp` — `physics_info`, `physics_sim()`, viewer handling. +- `physics_state.h` / `physics_state.cpp` — `physics_snapshot` capture/restore. + +## Core data structures / globals +- `struct physics_info` — velocity, rotvel, mass, forces, damping, flags. +- `physics_snapshot` — serializable state for networking/interpolation. +- Object integration entry: `obj_move_call_physics()` (in `code/object/object.cpp`). + +## Major constants +- Viewer directions: `PHYSICS_VIEWER_FRONT`, `PHYSICS_VIEWER_REAR`, + `PHYSICS_VIEWER_UP`, `PHYSICS_VIEWER_LEFT`, `PHYSICS_VIEWER_RIGHT`. +- Physics behaviour flags `PF_*` (accelerating, gliding, afterburner, etc.). + +## Configuration tables +None. Per-ship physics values (mass, thrust, max velocities, rotation time, +glide settings) are defined in `ships.tbl` and applied by the ship module. + +## Architecture diagram (per-object integration) + +```mermaid +flowchart TD + inputs["inputs: pi->desired_vel, desired_rotvel
(from AI control_info / player), gravity, damping"] --> call + call["obj_move_call_physics(objp, frametime)
(code/object)"] --> sim["physics_sim(pos, orient, pi, frametime)"] + sim --> vel["physics_sim_vel()
translational (physics.cpp ~274)"] + sim --> rot["physics_sim_rot()
rotational (physics.cpp ~151)"] + vel --> apply["apply_physics()
damped, frame-rate-independent (physics.cpp ~108)"] + rot --> apply + apply --> outp["new object pos / orient / velocities"] + outp -.->|capture for networking| snap["physics_populate_snapshot()"] + snap -.-> restore["physics_apply_pstate_to_object()
(multiplayer interpolation/rollback)"] +``` + +## See also +- `code/object/object.h` (`physics_populate_snapshot`, `physics_apply_pstate_to_object`). +- `code/math/` (vectors/matrices), `code/network/multi_interpolate.*`. +- Table option reference: https://wiki.hard-light.net/index.php/Tables diff --git a/documentation/modules/qtfred.md b/documentation/modules/qtfred.md new file mode 100644 index 00000000000..c2f0b61889a --- /dev/null +++ b/documentation/modules/qtfred.md @@ -0,0 +1,35 @@ +# Module: qtfred — `qtfred/` + +## Purpose +**qtFRED** is the cross-platform (Qt-based) reimplementation of the FRED mission +editor. Like FRED2 it is a separate executable that links the engine in `code/`, +but its GUI is built with **Qt** so it runs on Windows, Linux, and macOS. It edits +the same `.fs2` missions/campaigns (ships, wings, events/goals, briefings, +backgrounds, etc.). + +## Build +- Built when `FSO_BUILD_QTFRED=ON` (off by default; see top-level `CMakeLists.txt`). +- Requires Qt5 (`Qt5_DIR`). Target: `qtfred`. + +## Layout +- `src/main.cpp`, `qmain.cpp`, `FredApplication.*` — application entry/bootstrap. +- `src/mission/` — editor-side mission model and engine glue. +- `src/ui/` — Qt dialogs/widgets; `ui/` and `resources/` — `.ui` forms and assets. +- `src/fredstubs.cpp` — editor stubs for game-only engine hooks. +- `CMakeLists.txt`, `cmake/`, `source_groups.cmake` — build config. `README.md`, + `CHANGELOG.md`, `help-src/` — docs. + +## Relationship to the engine +- Shares the engine's data model: `ship_info`/`Ships`, `p_object`/`Parse_objects` + (`code/mission/missionparse.*`), SEXPs (`code/parse/sexp.*`). +- Editor logic in `src/mission/` separates engine state from the Qt UI layer. +- Author-facing engine additions (new table fields, new SEXPs) generally need + matching qtFRED UI changes (and FRED2). + +## Configuration tables +None of its own; reads the same content tables as the game to populate lists. + +## See also +- `documentation/modules/fred2.md` (legacy MFC editor), `code/mission/`, + `code/parse/sexp.*`, `documentation/ARCHITECTURE.md`. +- Wiki: https://wiki.hard-light.net/index.php/Qtate_of_the_Art_(qtFRED) diff --git a/documentation/modules/scripting.md b/documentation/modules/scripting.md new file mode 100644 index 00000000000..861c228676e --- /dev/null +++ b/documentation/modules/scripting.md @@ -0,0 +1,67 @@ +# Module: scripting — `code/scripting/` + +## Purpose +The **Lua scripting system** exposed to mods (distinct from SEXPs in `code/parse/`). +Provides ADE — the C++↔Lua binding layer — plus the **hook** system that lets Lua +scripts react to engine events (on-frame, on-death, HUD draw, key press, …). + +## Key files +- `ade.cpp` / `ade.h`, `ade_api.h`, `ade_args.*` — the binding framework and the + macros used to register Lua classes/libraries/functions. +- `api/objs/` — bound Lua *objects* (ship, weapon, vector, …). +- `api/libs/` — bound Lua *libraries* (mn, gr, ba, hv, …). +- `hook_api.h` / `hook_api.cpp` — `scripting::Hook` / `OverridableHook`. +- `global_hooks.*` — central registry of built-in hooks. +- `hook_conditions.*` — conditional gating of hooks. +- `scripting.cpp` / `scripting.h` — `script_state`, `Script_system`, table parsing. +- `lua/` — Lua interpreter integration; `doc_*` — documentation generators. + +## Core data structures / globals +- `script_state Script_system` — the global Lua state/manager. +- `script_hook` — an override + hook function pair. +- ADE registers types via `ade_obj<>` / `ade_lib` (`ade_api.h`). + +## Major constants +- Language flag `SC_LUA` (1<<0). +- Conditional types `CHC_*`, legacy global actions `CHA_*` (`scripting.h`). +- `MAX_HOOK_CONDITIONS` (8). + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `scripting.tbl` (+ `*-sct.tbm`) | `script_parse_table()` | Script files & hook bindings | + +Table option reference: https://wiki.hard-light.net/index.php/Tables (see *Scripting.tbl*). +Lua API reference is generated from the ADE bindings (`doc_*`). + +## Extending +- Expose engine data to Lua → add an ADE object/library under `api/`. +- React to a new event → declare a `scripting::Hook` (or `OverridableHook`) in + `global_hooks.*` and fire it from the relevant subsystem. + +## Architecture diagram (ADE bindings + hooks) + +```mermaid +flowchart TD + subgraph REG["Registration (at startup)"] + ade["ADE_OBJ / ADE_LIB / ADE_FUNC / ADE_VIRTVAR
(api/objs, api/libs)"] --> reg["registered Lua types & libraries"] + tbl["scripting.tbl"] -->|script_parse_table()| scripts["loaded script functions / hook bindings"] + end + + subgraph CALL["Lua → engine call"] + lua["Lua script calls API"] --> marshal["ade_get_args() unmarshal
→ C++ logic → ade_set_args() return"] + end + + subgraph EVT["engine → Lua event"] + site["engine event site fires
Hook->run() / isOverride()"] --> active{"hook active?"} + active -->|yes| run["run subscribed Lua functions"] + run --> ovr{"OverridableHook
& isOverride()?"} + ovr -->|yes| suppress["skip default engine behaviour"] + ovr -->|no| default["run default behaviour too"] + end + reg -.exposes.-> lua + scripts -.subscribe.-> site +``` + +## See also +- `code/parse/sexp.*` (the other, mission-designer scripting system). diff --git a/documentation/modules/ship.md b/documentation/modules/ship.md new file mode 100644 index 00000000000..ba1ef65c83e --- /dev/null +++ b/documentation/modules/ship.md @@ -0,0 +1,42 @@ +# Module: ship — `code/ship/` + +## Purpose +Owns **ships**: both the class definitions (`ship_info`, loaded from `ships.tbl`) +and the live mission instances (`ship`). Handles subsystems (turrets, engines, +sensors), weapon banks, damage/death, warp in/out, afterburners, contrails, and +ship-type categories. This is one of the largest and most central modules. + +## Key files +- `ship.h` / `ship.cpp` — `ship`, `ship_info`, `wing`, subsystems, most logic. +- `ship_flags.h` — ship and ship_info flagsets. +- `shipfx.cpp` / `shipfx.h` — visual effects (warp, death roll, sparks, breakup). +- `shiphit.cpp` / `shiphit.h` — damage application, subsystem/hull/shield hits. +- `shipcontrails.cpp`, `afterburner.cpp`, `awacs.cpp`. + +## Core data structures / globals +- `ship Ships[MAX_SHIPS]` — live instances; `ship.ship_info_index` → `Ship_info`. +- `SCP_vector Ship_info` — class definitions from `ships.tbl`. +- `wing Wings[MAX_WINGS]` — wing groupings. +- `SCP_vector Ships_exited` — record of ships that left/died. +- `ship_subsys`, `ship_weapon` — per-instance subsystem and weapon-bank state. + +## Major constants (mostly in `globals.h`) +- `MAX_SHIPS` (500), `SHIPS_LIMIT`, `MAX_SHIP_CLASSES` (500). +- `MAX_WINGS` (75), `MAX_SHIPS_PER_WING` (6), `MAX_STARTING_WINGS`, + `MAX_SQUADRON_WINGS`, `MAX_TVT_TEAMS`, `MAX_TVT_WINGS`. +- `MAX_SHIP_PRIMARY_BANKS` (3), `MAX_SHIP_SECONDARY_BANKS` (4), + `MAX_SHIP_WEAPONS` (7). +- `MAX_MODEL_SUBSYSTEMS` (200), `MAX_SHIP_BAY_PATHS` (in `model.h`). + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `ships.tbl` (+ `*-shp.tbm`) | `parse_shiptbl()` | Ship class definitions | +| `objecttypes.tbl` | `parse_shiptype_tbl()` | Ship-type categories/behaviour | +| `armor.tbl` | `armor_parse_table()` | Armor/damage-type definitions | + +Table option reference: https://wiki.hard-light.net/index.php/Tables (see *Ships.tbl*). + +## See also +- `code/weapon/` (weapon banks), `code/ai/` (per-ship AI), `code/model/` (POF + subsystems). +- Mission-side ship spawning: `code/mission/missionparse.*` (`p_object`, `Parse_objects`). diff --git a/documentation/modules/sound.md b/documentation/modules/sound.md new file mode 100644 index 00000000000..f1bd1e2567d --- /dev/null +++ b/documentation/modules/sound.md @@ -0,0 +1,36 @@ +# Module: sound — `code/sound/` + +## Purpose +**Audio**: 2D/3D sound effects, streaming audio (music, voice, movies), the sound +abstraction over OpenAL, FFmpeg-based decoding, text-to-speech, and (Windows) +voice recognition. Note: event music is owned by `code/gamesnd/`, which defines the +sound *entries* this module plays. + +## Key files +- `sound.cpp` / `sound.h` — high-level API: load, play, 3D positioning, priorities. +- `ds.cpp` / `ds.h`, `ds3d.*` — low-level device/buffer layer (the "DirectSound"-era API). +- `openal.cpp` / `openal.h` — OpenAL backend. +- `audiostr.*` — streaming audio (music/voice). `ffmpeg/` — decoding. +- `fsspeech.*`, `speech_*` (win/linux/mac) — text-to-speech. +- `voicerec.*`, `rtvoice.*` — voice recognition / real-time voice (multiplayer). + +## Core data structures / globals +- `game_snd` (defined in `code/gamesnd/gamesnd.h`) — a named, table-driven sound entry. +- Sound handles returned by `snd_play*` calls. + +## Major constants +- Game-sound flags: `GAME_SND_USE_DS3D`, `GAME_SND_VOICE`, `GAME_SND_PRELOAD`, + `GAME_SND_RETAIL_STYLE`, `GAME_SND_EXPLICITLY_EMPTY`. +- Priorities: `SND_PRIORITY_MUST_PLAY`, `..._SINGLE/DOUBLE/TRIPLE_INSTANCE`. +- Volume buses: `AAV_MUSIC`, `AAV_VOICE`, `AAV_EFFECTS`. + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `sounds.tbl` | `parse_sound_table()` (`code/gamesnd/gamesnd.cpp`) | Named sound effect entries | +| `music.tbl` | `event_music_parse_musictbl()` (`code/gamesnd/eventmusic.cpp`) | Event/menu music | + +Table option reference: https://wiki.hard-light.net/index.php/Tables (see *Sounds.tbl*, *Music.tbl*). + +## See also +- `code/gamesnd/` (sound/music table entries), `code/sound/ffmpeg/`, `code/cutscene/`. diff --git a/documentation/modules/ui.md b/documentation/modules/ui.md new file mode 100644 index 00000000000..41d2a039655 --- /dev/null +++ b/documentation/modules/ui.md @@ -0,0 +1,34 @@ +# Module: ui — `code/ui/` + +## Purpose +The **low-level UI widget toolkit** used by the classic (retail-style) interface +screens: buttons, checkboxes, sliders, input boxes, list boxes, windows, and +gadget focus/keyboard handling. Higher-level screens are built on top of this in +`code/menuui/`, `code/missionui/`, and the newer `code/scpui/`. + +## Key files +- `ui.h` — the widget classes (`UI_WINDOW`, `UI_BUTTON`, `UI_GADGET`, …). +- `window.cpp`, `button.cpp`, `gadget.cpp`, `checkbox.cpp`, `slider.cpp`, + `inputbox.cpp`, `listbox.cpp`, `scroll.cpp`, `icon.cpp`, `radio.cpp`. + +## Core data structures +- `UI_GADGET` (base) and the concrete widgets; `UI_WINDOW` owns a gadget list. + +## Major constants +- Gadget state/style flags and key/mouse handling defines in `ui.h`. + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `tooltips.tbl` | `window.cpp` | UI tooltip strings | + +## Related UI modules +- `code/scpui/` — modern libRocket-based UI (newer screens; data in retail UI VPs). +- `code/menuui/` — main hall, barracks, tech room, credits, player select + (`mainhall.tbl`, `tips.tbl`, `menu.tbl`, `intel.tbl`). +- `code/missionui/` — briefing, ship/weapon select, debrief screens. + +Table option reference: https://wiki.hard-light.net/index.php/Tables + +## See also +- `code/graphics/` (rendering), `code/io/` (input), `code/localization/` (string tables). diff --git a/documentation/modules/weapon.md b/documentation/modules/weapon.md new file mode 100644 index 00000000000..e4d5573c64c --- /dev/null +++ b/documentation/modules/weapon.md @@ -0,0 +1,37 @@ +# Module: weapon — `code/weapon/` + +## Purpose +Owns **weapons and projectiles**: lasers/ballistics, missiles, beams, plus their +special behaviours (swarm, corkscrew, flak, EMP, electronics, trails, spawning +child weapons, muzzle flashes). Like ships, it uses the info-vs-instance split: +`weapon_info` (class, from `weapons.tbl`) and `weapon` (live projectile instance). + +## Key files +- `weapon.h` / `weapons.cpp` — `weapon`, `weapon_info`, core firing/move logic. +- `weapon_flags.h` — weapon and weapon_info flagsets. +- `beam.cpp` / `beam.h` — beam weapons (also object type `OBJ_BEAM`). +- `swarm.cpp`, `corkscrew.cpp`, `flak.cpp`, `emp.cpp`, `trails.cpp`, + `muzzleflash.cpp`, `shockwave/` (see `code/shockwave` if present). + +## Core data structures / globals +- `weapon Weapons[MAX_WEAPONS]` — live projectile instances. +- `SCP_vector Weapon_info` — class definitions from `weapons.tbl`. + +## Major constants +- Subtypes: `WP_UNUSED` (-1), `WP_LASER` (0, incl. ballistic primaries), + `WP_MISSILE` (1), `WP_BEAM` (2). +- `MAX_WEAPONS` (3000), `MAX_WEAPON_TYPES` (500) — both in `globals.h`. +- `MAX_SPAWN_TYPES_PER_WEAPON` (5), `MAX_SUBSTITUTION_PATTERNS` (10), + `MAX_BEAM_SECTIONS` (5). + +## Configuration tables +| File | Parsed in | Purpose | +| --- | --- | --- | +| `weapons.tbl` (+ `*-wep.tbm`) | `parse_weaponstbl()` | Weapon class definitions | +| `mflash.tbl` | `parse_mflash_tbl()` | Muzzle-flash definitions | + +Table option reference: https://wiki.hard-light.net/index.php/Tables (see *Weapons.tbl*). + +## See also +- `code/ship/` (weapon banks `ship_weapon`), `code/object/` (collision handling), + `code/fireball/` (impact explosions). diff --git a/fred2/AGENTS.md b/fred2/AGENTS.md new file mode 100644 index 00000000000..d7349c1b89a --- /dev/null +++ b/fred2/AGENTS.md @@ -0,0 +1,10 @@ +# Module: fred2 + +Entry-point guide for this module lives at: +**`documentation/modules/fred2.md`** (from repo root). + +It covers the editor's purpose, build requirements, key files, and how it relates +to the shared engine code in `code/`. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`. diff --git a/qtfred/AGENTS.md b/qtfred/AGENTS.md new file mode 100644 index 00000000000..c54ef11c87a --- /dev/null +++ b/qtfred/AGENTS.md @@ -0,0 +1,10 @@ +# Module: qtfred + +Entry-point guide for this module lives at: +**`documentation/modules/qtfred.md`** (from repo root). + +It covers the editor's purpose, build requirements, layout, and how it relates to +the shared engine code in `code/`. + +For the engine-wide architecture overview see `documentation/ARCHITECTURE.md`. +For build/test/style conventions see the root `AGENTS.md`.