-
Notifications
You must be signed in to change notification settings - Fork 182
Add AI agent guidance, architecture docs, and project skills #7550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
The-E
wants to merge
14
commits into
scp-fs2open:master
Choose a base branch
from
The-E:feature/agent-guidance
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
f89837d
adds guidance documents for AI agents
The-E f514f8f
adds fso-specific skills
The-E 570c86a
add more docs
The-E 5c9b0e6
add review skill
The-E ed5d43c
make guidance vendor agnostic
The-E 8a8d2d0
add section about core philosophies of the scp
The-E faf328f
expand guidance on logging practices and hardware compatibility requi…
The-E bc3a6ca
fix typo in ci script and update clang-tidy documentation in AGENTS.md
The-E 54197c5
Suppress SDL_Init one-time leak reported via os_init in valgrind
The-E 3fbca41
ci: force DWARF4 debug info for Linux clang builds
The-E fb69e51
ci: add clang-tidy checks to Linux clang builds
The-E 73e9c05
address review feedback
The-E 9df5b63
ci: fix macOS clang-tidy pip install failing under PEP 668
The-E 092dfc8
ci: install pyyaml for macOS clang-tidy step
The-E File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # 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` | **Last resort** — introduce a new in-world `OBJ_*` entity with create/move/delete + collision + render; only when no existing object type (ship/weapon/debris/asteroid/etc.) can represent it | `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 check clang-format/clang-tidy compliance, 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 — unless nested inside a block | ||
| that's itself only entered after a new `optional_string` was matched (e.g. | ||
| sub-fields of a new optional block), where `required_string` is fine since | ||
| the whole block is already optional. | ||
| - 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/<name>/SKILL.md` and `.claude/skills/<name>/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/<name>/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/<name>/SKILL.md` and `.claude/skills/<name>/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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| --- | ||
| 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. | ||
|
|
||
| This is a last resort: adding a new `OBJ_*` type touches `MAX_OBJECT_TYPES`, | ||
| every switch over object type, and the collision matrix. Prefer extending an | ||
| existing type (ship subsystem, weapon subtype, etc.) if it can represent the | ||
| new entity at all. | ||
|
|
||
| ## 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`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"unless gated as suboptions only passed if a new optional_string is hit"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.