Architecture¶
Structure du dépôt¶
src/ Source modules (pure Lua 5.1, one class per file)
core/ Foundations: class.lua, CTLD_objectRegistry, CTLD_modValidator,
CTLDParachuteEffect
scenes/ Scene data files (auto-registered at load time)
legacy/ Legacy v1 API wrappers (thin delegates, deprecated)
CTLD_*.lua Domain managers (config, i18n, utils, menu, zone, troop, crate,
vehicle, fob, aasystem, beacon, recon, jtac, player, core…)
CTLD_bootstrap.lua Engine bootstrap — ctld.initialize() + auto-start guard (merged last)
CTLD_userConfig.lua Mission-config template — shipped as a separate file, NOT merged
tools/
build/ Build tooling: merge_CTLD.ps1, listToMerge.txt, generate_i18n_dicts.ps1
tests/
ci/ busted tests (no DCS required)
helpers/ DCS stubs + module loader (init.lua, loader.lua)
unit/ *_spec.lua unit tests
functional/ *_spec.lua functional tests
dcs/ DCS integration-test scenarios (require a live mission)
docs/ Published documentation (this site)
assets/ Runtime audio (beacon.ogg)
missions/ Demo and test .miz files
migration/
source/ Reference — original monolithic v1 CTLD.lua (read-only, immutable)
CTLD.lua Generated deliverable — never hand-edit (rebuilt from src/)
Idiome Manager / singleton¶
Les classes sont construites avec le micro-framework OOP minimal de src/core/class.lua :
MyClass = class() -- create a class
Child = class(MyClass) -- subclass (single inheritance)
function MyClass:init(...) end -- constructor, called by :new()
local obj = MyClass:new(...) -- allocate instance + run init()
class(base) définit la table de classe comme son propre __index, de sorte que la résolution
des méthodes d'instance retombe sur la classe ; passer un base chaîne __index vers le parent.
Les managers de domaine sont des singletons. Ils déclarent _instance et exposent une
fabrique getInstance() qui contourne :new() et appelle init() au premier accès :
CTLDZoneManager = class()
CTLDZoneManager._instance = nil
function CTLDZoneManager.getInstance()
if not CTLDZoneManager._instance then
local o = setmetatable({}, CTLDZoneManager)
o:init()
CTLDZoneManager._instance = o
end
return CTLDZoneManager._instance
end
La configuration est en lecture seule via ctld.gs("paramName") — jamais config:getSetting().
Séquence d'init de CTLDCoreManager¶
CTLDCoreManager:init() s'exécute une seule fois au démarrage de la mission et déroule ces phases
dans l'ordre :
| Phase | Méthode | Description |
|---|---|---|
| INIT-B | _initMMCrates() |
Scanne les statics de la coalition à la recherche des objets de cargo placés par le MM |
| INIT-C | _initMMJTACs() |
Scanne les groupes de la coalition à la recherche des groupes JTAC placés par le MM |
| INIT-D | CTLDVehicleSpawner:scanMMVehicles() |
Scanne les groupes terrestres de la coalition à la recherche des vehicles placés par le MM |
| INIT-E | _initExtractableGroups() |
Enregistre les noms de extractableGroups dans CTLDTroopManager._droppedGroups |
| INIT-A | _initAITransports() |
Construit les listes d'équipes IA et démarre la boucle d'auto-pickup/dropoff |
Détail INIT-E : lit ctld.gs("extractableGroups"), appelle Group.getByName() pour chaque
entrée, et insère le nom du groupe dans CTLDTroopManager._droppedGroups[coalition]. Les groupes
introuvables sont journalisés en WARN et ignorés. Il n'y a pas d'activation tardive (iso-legacy)
ni d'entrée _droppedTemplates — embarkFromField utilise un repli à 130 kg/unité.
Ajouter un nouveau module¶
- Créer
src/CTLD_mymodule.luaen utilisant l'idiome class/singleton ci-dessus (CTLDMyManager = class(),_instance,getInstance()). - Ajouter le nom de fichier à
tools/build/listToMerge.txtdans l'ordre des dépendances (les fondations d'abord, puis les managers de domaine, puis les scenes, puisCTLD_core.lua, puislegacy/,CTLD_bootstrap.luaen dernier). - Ajouter la même entrée
dofileàtests/ci/helpers/loader.lua. - Écrire des specs busted dans
tests/ci/unit/mymodule_spec.lua(test-first — voir Build et tests).
L'ordre de merge dans listToMerge.txt fait foi : les scenes sont listées après tous les managers
pour que l'auto-injection de model.crate se résolve, et CTLD_core.lua (l'orchestrateur) vient
après les scenes qu'il instancie.
Bibliothèques internes¶
core/class.lua — base OOP¶
Le système de classes à héritage simple utilisé partout dans CTLD (voir l'idiome ci-dessus).
class() crée une classe dont le __index est elle-même ; class(base) chaîne vers un parent.
core/CTLD_objectRegistry.lua — magasin de descripteurs de spawn¶
CTLDObjectRegistry est un registre statique qui associe des clés de template à des descripteurs de
spawn de group/unit DCS. Il gère des descripteurs, pas des instances.
CTLDObjectRegistry.register(key, descriptor) -- add a template
CTLDObjectRegistry.spawnObject(key, coa, country, x, z, hdg, opts)
-- → DCS Group object | nil
Les scenes enregistrent les descripteurs de leurs composants au moment du dofile ; les templates de troops et de vehicles sont enregistrés par leurs managers à l'init.
Validation d'assets — au design-time, pas de sonde runtime¶
CTLD ne spawn plus d'objets pour tester la présence d'un type DCS. Les noms de types sont validés
au dev/CI contre un jeu datamine de types stock vendorisé (tests/data/dcs_types.lua) :
CTLDTypeCollector(core/CTLD_typeCollector.lua) est la source de vérité unique pour quels types DCS une mission configure — registry STATIC (desc.type) et GROUND (desc.units[].unitType(coalitionId)),spawnableCrates, templates AA,loadableGroups— et lesquels sont des types hors-stock (mod) déclarés :model.modTypesdes scènes ∪ le settingmodTypes.- Les gates busted (
scene_asset_gate_spec— échec dur ;config_types_lint_spec— rapport lenient) le consomment pour attraper les types ni stock ni déclarés. - Le compagnon asset-check optionnel (
tools/companion/, buildé endist/CTLD_asset_check.lua) est l'équivalent runtime au dev-time : le mission-maker le charge après CTLD pendant le développement et il WARN sur les types configurés inconnus — une simple lecture de table, sans spawn, sans event.
Une scène ou une mission déclare ses types mod pour que la validation reste stricte sur le reste :
someScene.modTypes = { "Some_Mod_Type" } -- sur un modèle de scène
advanced:
modTypes: # pour la config crate/troop/AA d'une mission
- Some_Mod_Type
Note historique (ADR 0007) : les versions antérieures spawnaient une sonde (static/group) par type au démarrage (
CTLD_modValidator) et ne pouvaient pas sonder les mods heliport du tout —getDesc().life == 0que le mod soit installé ou non. Cette sonde a été retirée : elle gaspillait des ressources et émettait des eventsS_EVENT_BIRTH/destroy parasites vus par les handlers custom.
core/CTLDParachuteEffect.lua¶
Helper d'effet visuel de parachute utilisé lors du largage de crates/troops depuis l'altitude.
CTLD_utils.lua — fonctions utilitaires¶
Fonctions clés disponibles sous ctld.utils.* :
| Fonction | Rôle |
|---|---|
log(level, fmt, ...) |
Écrit dans CTLD.log (niveaux : DEBUG, INFO, WARN, ERROR) |
getDistance(caller, p1, p2) |
Distance 2D au sol entre deux points {x,z} |
getHeadingInRadians(caller, unit, magnetic) |
Cap de l'unit en radians |
inAir(unit) |
Vrai si l'unit est en vol (garde-fou AGL + vélocité) |
getNextMarkId() |
Alloue le prochain mark ID DCS unique depuis ctld._markIdCounter |
getNextUniqId() |
Alloue le prochain ID d'entité unique depuis ctld.utils.UniqIdCounter |
drawQuad(coalitionId, pts, msg) |
Dessine un polygone à 4 points sur la carte F10 |
buildWP(caller, pt, type, speed) |
Construit une table de waypoint DCS |
getSecureDistanceFromUnit(unitName) |
Rayon minimal de dégagement de spawn depuis la bbox d'une unit |
dynAddStatic(coalitionId, data) |
Wrapper de coalition.addStaticObject avec résolution du pays |