Plugin development and test guide¶
This guide covers the full lifecycle of a CTLD plugin: creation, unit testing, DCS visual testing, and CI.
1. Creating a plugin¶
Directory layout¶
plugins/
└── my-plugin/
├── README.md ← source of truth for docs (FR prose + YAML front-matter)
└── src/
└── CTLD_myPlugin.lua
Step 1 — Copy the template¶
plugins/_template/
Rename the folder and update every template / Template occurrence in the
.lua file and the README.md.
Step 2 — Fill in the README front-matter¶
---
modUrls:
- mod: MyModType # DCS typename declared in modTypes
url: https://... # download page for the mod
---
Omit the modUrls block entirely if the plugin requires no DCS mod.
Step 3 — Write the Lua scene¶
Key rules:
- Register every DCS type you spawn via
CTLDObjectRegistry.registerIfAbsent. - List every non-stock (mod) type in
metalFarpScene.modTypes. - Stock DCS types do not go in
modTypes. - Set
probeSkip = trueon mod types —getDesc().lifeis always 0 for mods at runtime; skipping avoids a false "NOT FOUND" alarm. - Objects destroyed in a later step must have their reference saved into
scene._paramsduring their spawn stepfunccallback.
Step 4 — Generate the doc pages¶
Run the generate-plugin-doc skill to produce docs/plugins/my-plugin.md
(EN) and docs/plugins/my-plugin.fr.md (FR) from the README.md.
Never edit those generated files by hand.
2. Unit tests (Busted)¶
Location¶
plugins/my-plugin/tests/my_plugin_spec.lua
What to test¶
Test observable scene model properties, not internal step order:
| Assertion | Why |
|---|---|
isSceneEnabled("My Plugin") |
Scene is registered |
model.crate is not nil |
Crate declared for the menu |
model.requiresCtld equals expected version |
CTLD version guard in place |
All mod types present in model.modTypes |
Asset gate will catch them |
Stock types absent from model.modTypes |
Prevents false positives |
| Step count equals expected value | Sequence is complete |
critical = true on mod-dependent spawn steps |
Scene aborts if mod missing |
Prior art¶
See plugins/_template/tests/template_spec.lua and
plugins/metal-farp/tests/metal_farp_spec.lua.
Running locally¶
Busted is installed by the CI. To run locally, install it with LuaRocks and
run busted tests/ plugins/ from the repo root.
3. DCS visual testing¶
Visual testing requires a live DCS instance and dcs-bridge active.
Setup¶
- Both repos must be cloned side-by-side:
Documents/GitHub/ ├── CTLD/ ← neighbour repo, provides the CTLD runtime └── CTLD_plugins/ ← this repo - Load
mission/Test_CTLD-plugins.mizin DCS. - Take the
uh1-1(UH-1H) pilot slot — scenes spawn 100 m ahead of this unit.
Dev / test loop¶
Each plugin provides three files in plugins/{name}/tests/:
| File | Purpose |
|---|---|
inject_scene.lua |
Plays the scene 100 m ahead of uh1-1 via playSceneAtPos |
cleanup.lua |
Destroys all spawned objects; leaves uh1-1 intact |
RECIPE.md |
Timing checkpoints and full loop procedure |
Injection order (Step 1 — once per session):
../CTLD/CTLD.lua
plugins/{name}/src/CTLD_{name}.lua
Play (Step 2 — repeated):
plugins/{name}/tests/inject_scene.lua
On a problem (Step 3):
- Inject
plugins/{name}/tests/cleanup.lua. - Fix the plugin lua.
- Repeat from Step 1.
Injection methods¶
Use whichever interface you have available:
- MCP (Claude Code): ask Claude to inject the file via the dcs-bridge MCP tool.
- TUI:
dcs-client tui→ paste the file content in the exec panel. - REST API:
POST /api/execwith{"lua": "<file content>"}and theX-Api-Keyheader from../CTLD/dcs-client.yaml.
The cleanup.lua contract¶
cleanup.lua sweeps all coalitions and all object categories (ground
groups, static objects, helicopter groups, etc.) and destroys everything except
the uh1 group. It is intentionally generic — the same file works for any
plugin, regardless of what types the scene spawns.
4. CI checks¶
Every pull request runs the following gates automatically:
| Gate | What it checks |
|---|---|
lua-lint |
Lua 5.1 syntax (luac5.1 -p) on all .lua files |
luacheck |
Static analysis against .luacheckrc |
busted |
All *_spec.lua files under tests/ and plugins/ |
validate-docs |
README front-matter coherent with generated doc pages |
build |
Plugin .lua produces a non-empty dist/ artifact |
Visual animation quality is not checked by CI — it is the acceptance gate for the DCS injection recipe, validated by human observation.