diff --git a/.backlog/CTLD-TOOLS-FINALIZE/PRD.md b/.backlog/CTLD-TOOLS-FINALIZE/PRD.md new file mode 100644 index 0000000..340bbd8 --- /dev/null +++ b/.backlog/CTLD-TOOLS-FINALIZE/PRD.md @@ -0,0 +1,57 @@ +Status: ready + +# PRD — CTLD-TOOLS-FINALIZE + +> Finalisation du programme `ctld-tools` (voir [ADR 0009](../../dev/adr/0009-external-yaml-authoring-ctld-tools.md)), +> demandée par le maintainer après les lots 1–3. + +## Problem Statement + +Three loose ends after `CTLD-TOOLS-USERCONFIG`: + +1. The engine-config workflow used **committed generated Lua + a drift check**. Editing the config + meant a manual `gen-config` step; forgetting it produced a CI round-trip. The maintainer prefers + the build to regenerate the Lua itself. +2. `ctld-tools.exe` was wired into `release.yml` but **never actually built/tested**, and only as an + isolated best-effort step. +3. The Mission Maker had no **dedicated documentation page** for `ctld-tools` — only a tip. + +## Solution + +- **gen-au-build**: `merge_CTLD.ps1` regenerates `src/CTLD_config_defaults.lua` from + `CTLD_config.yaml` via `ctld-tools gen-config` on every build. The generated Lua is a git-ignored + artifact. Consumers that need it (build, busted loader, release) generate it; a `conftest.py` + fixture does so for the Python tests. The drift check is dropped. +- **`ctld-tools.exe`**: built and verified (PyInstaller with lupa + the datamine bundled), and + attached to each Release by a **separate `build-exe` job** so a packaging failure never blocks the + `CTLD.lua` release. +- **Docs**: a dedicated bilingual `mission-maker/ctld-tools.md` page, detailed with examples. + +## Implementation Decisions + +- `merge_CTLD.ps1` calls `poetry run ctld-tools gen-config` before merging; aborts with a clear + message if poetry is missing. The build (and the busted job) therefore require Python + poetry. +- `ci.yml` `build` (Windows) and `busted` (Ubuntu) jobs, and `release.yml`, add `setup-python` + + `poetry install --without dev --without build`; busted also runs `gen-config` before the suite. +- `src/CTLD_config_defaults.lua` removed from git tracking and added to `.gitignore`. +- `release.yml` gains a `build-exe` job (`needs: release`, `windows-latest`): PyInstaller `--onefile + --collect-all lupa --add-data dcs_types.json`, then `gh release upload`. +- ADR 0009 and the developer build docs updated to describe gen-au-build (superseding committed+drift). + +## Testing Decisions + +- A `conftest.py` session fixture regenerates `CTLD_config_defaults.lua` from the YAML before the + Python tests, so the parity/reference/e2e tests keep working without a committed file. The drift + test (`test_committed_generated_lua_is_up_to_date`) is removed. +- The `.exe` build was validated locally: `scaffold` + `validate` run from the packaged exe (proving + lupa and the embedded `dcs_types.json` work). The `build-exe` CI job re-verifies on each release. +- The `busted` job proves the loader still loads the freshly-generated defaults. + +## Out of Scope + +- `.miz` injection, TUI (roadmap). +- Any change to the `user-config.yaml` schema or the runtime API. + +## Further Notes + +- Closes the `ctld-tools` program. Remaining roadmap items: `.miz` injection, interactive TUI. diff --git a/.backlog/README.md b/.backlog/README.md index e1f5541..0e07a75 100644 --- a/.backlog/README.md +++ b/.backlog/README.md @@ -16,6 +16,7 @@ authored **per lot, when the lot is started** (not in batch). |-----|--------|-------------|--------| | `FEAT-USERCONFIG-API` | ✅ merged (PR #45) | Replace broken Section 2 of `CTLD_userConfig.lua` with a safe MM API (`ctld.userSetup` callbacks + helpers), relocate `injectAACrates` to bootstrap, fix all parity bugs in userConfig template. | `feature/userconfig-api` | | `CTLD-TOOLS-CONFIG` | ✅ merged (PR #46) | Lot 2 of `ctld-tools` ([ADR 0009](../dev/adr/0009-external-yaml-authoring-ctld-tools.md)): engine defaults moved out of `CTLD_config.lua` into `src/CTLD_config.yaml` (source of truth, sectioned mm-facing/advanced); isolated poetry package `tools/ctld-tools` (typer/ruamel/lupa, VMCT stack) with `extract` + `gen-config`; committed generated `CTLD_config_defaults.lua` + parity/drift guards + `python-quality` CI. `load()` copies `ctld.__configDefaults`. | `feature/ctld-tools-config` | +| `CTLD-TOOLS-FINALIZE` | ✅ merged (PR #48) | Finalize `ctld-tools`: **gen-au-build** (`merge_CTLD.ps1` regenerates `CTLD_config_defaults.lua` via `ctld-tools`, now a git-ignored artifact; CI/release gain setup-python; drift check dropped) + build & attach **`ctld-tools.exe`** (separate isolated job, verified) + dedicated MM doc page `ctld-tools.md` (EN+FR). | `feature/ctld-tools-finalize` | | `CTLD-TOOLS-USERCONFIG` | ✅ merged (PR #47) | Lot 3 of `ctld-tools` ([ADR 0009](../dev/adr/0009-external-yaml-authoring-ctld-tools.md)): MM volet — `validate` (user-config.yaml against the reference + datamine, clear report with suggestions) + `gen-user` (compile add/remove/patch ops into `CTLD_userConfig.lua` calling the `ctld.userSetup` helpers, targeting crates/troops **by name**) + `gen-user --scaffold`; embedded `dcs_types.json`; `ctld-tools.exe` attached to Releases (isolated). e2e-tested. | `feature/ctld-tools-userconfig` | | `DOC-README-CLEANUP` | ✅ merged (PR #21) | Fix README title, restructure Crate Operations sub-sections, relocate AA System Construction, replace Developer Guide with Documentation links. | — | | `CATCH-UP-PILOT-SCENARIOS` | ✅ merged (PR #24) | Ran the never-executed `pilotPassive`/`pilotActive` scenarios. Audit found the "34 `ia`" was almost all mistagged: 66/66 now pass in a headless sweep (`--no-ai --reset-before-each`), real pilot burden ≈ 2 short menu flights (both PASS). Fixed a pile of test-harness defects + cross-scenario contamination (soft reset `_reset_state.lua`), + one product fix (`refreshMenuSection` flight-state override). Optional remainders: `auto-slow` AI battery (covered by F-176..182), mt16/warehouse, L6 manual (ticket 08). | — | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a0471b..9e78a98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,15 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + - name: Install ctld-tools (the build regenerates CTLD_config_defaults.lua via gen-config) + working-directory: tools/ctld-tools + shell: bash + run: | + pip install poetry + poetry install --without dev --without build - name: Build CTLD.lua run: powershell -ExecutionPolicy Bypass -File tools\build\merge_CTLD.ps1 shell: pwsh @@ -99,6 +108,15 @@ jobs: COVERAGE_FLOOR: '59' steps: - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + - name: Generate engine defaults (the busted loader dofile's CTLD_config_defaults.lua) + working-directory: tools/ctld-tools + run: | + pip install poetry + poetry install --without dev --without build + poetry run ctld-tools gen-config --yaml ../../src/CTLD_config.yaml --out ../../src/CTLD_config_defaults.lua - name: Install Lua + LuaRocks + busted + luacov run: | sudo apt-get install -y lua5.1 liblua5.1-dev luarocks diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 02b957f..752aefd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,6 +16,16 @@ jobs: steps: - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + - name: Install ctld-tools (the build regenerates CTLD_config_defaults.lua via gen-config) + working-directory: tools/ctld-tools + shell: bash + run: | + pip install poetry + poetry install --without dev --without build + - name: Build CTLD.lua run: powershell -ExecutionPolicy Bypass -File tools\build\merge_CTLD.ps1 shell: pwsh @@ -51,18 +61,34 @@ jobs: --title "CTLD v${{ steps.ver.outputs.version }}" \ "${notes[@]}" "${pre[@]}" - # Build and attach the Mission Maker tool ctld-tools.exe. Isolated from the - # deliverable: continue-on-error so a packaging hiccup never blocks the release - # of CTLD.lua. Uploaded to the release that the previous step just created. - - name: Set up Python - uses: actions/setup-python@v6 + # A floating "published-latest" tag tracks the last STABLE release. Only a real + # release advances it; a pre-release (rc) leaves production users on the previous + # stable. "published-latest" is not matched by the 'published-v*' trigger, so this + # push does not re-trigger the workflow. + - name: Advance published-latest (stable only) + if: steps.ver.outputs.prerelease != 'true' + run: | + git tag -f published-latest "${{ github.ref_name }}" + git push origin -f published-latest + shell: pwsh + + # ───────────────────────────────────────────────────────────── + # Build the Mission Maker tool ctld-tools.exe (PyInstaller, lupa + datamine + # bundled) and attach it to the release. Separate job: if packaging fails, the + # CTLD.lua release (job above) is already published — only the exe is missing. + # ───────────────────────────────────────────────────────────── + build-exe: + name: Build & attach ctld-tools.exe + needs: release + runs-on: windows-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 with: python-version: "3.13" - continue-on-error: true - - name: Build ctld-tools.exe - id: exe - continue-on-error: true working-directory: tools/ctld-tools shell: bash run: | @@ -72,22 +98,8 @@ jobs: --collect-all lupa \ --add-data "ctld_tools/data/dcs_types.json;ctld_tools/data" \ ctld_tools/__main__.py - - name: Attach ctld-tools.exe to the release - if: steps.exe.outcome == 'success' - continue-on-error: true shell: bash env: GH_TOKEN: ${{ github.token }} run: gh release upload "${{ github.ref_name }}" tools/ctld-tools/dist/ctld-tools.exe - - # A floating "published-latest" tag tracks the last STABLE release. Only a real - # release advances it; a pre-release (rc) leaves production users on the previous - # stable. "published-latest" is not matched by the 'published-v*' trigger, so this - # push does not re-trigger the workflow. - - name: Advance published-latest (stable only) - if: steps.ver.outputs.prerelease != 'true' - run: | - git tag -f published-latest "${{ github.ref_name }}" - git push origin -f published-latest - shell: pwsh diff --git a/.gitignore b/.gitignore index db4e9e0..0156a21 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,7 @@ tools/dcs-bridge/venv/ # ── Python bytecode cache (tools/ scripts are run directly, never packaged) ── __pycache__/ -*.pyc \ No newline at end of file +*.pyc + +# ── Generated engine defaults (gen-au-build via ctld-tools; regenerated by the build) ── +/src/CTLD_config_defaults.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bc7d38..1985580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ Versioning follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Tooling — ctld-tools finalize: gen-au-build, `.exe` distribution, MM docs (CTLD-TOOLS-FINALIZE) + +- **gen-au-build**: `merge_CTLD.ps1` now regenerates `src/CTLD_config_defaults.lua` from + `CTLD_config.yaml` via `ctld-tools` on **every build** — it is a git-ignored artifact, no longer + committed. The `build` + `busted` CI jobs and `release.yml` gain `setup-python` + poetry; the drift + check is dropped (the file is always fresh). Dev workflow is now simply "edit the YAML, rebuild". +- **`ctld-tools.exe`** is built (PyInstaller, lupa + datamine bundled) and attached to each Release + by a **separate `build-exe` job**, isolated so a packaging issue never blocks the `CTLD.lua` + release. Verified end to end (the exe runs `validate` with the embedded Lua runtime + type set). +- **Docs**: dedicated `mission-maker/ctld-tools.md` page (EN + FR), with the full `user-config.yaml` + format, commands and examples (block + flow), linked in the site nav. + ### Tooling — Mission Maker YAML authoring: `validate` + `gen-user` (CTLD-TOOLS-USERCONFIG) - **`ctld-tools` gains the MM volet**: `validate` (checks a `user-config.yaml` against the reference diff --git a/dev/adr/0009-external-yaml-authoring-ctld-tools.md b/dev/adr/0009-external-yaml-authoring-ctld-tools.md index 4f0eaa4..f6f6259 100644 --- a/dev/adr/0009-external-yaml-authoring-ctld-tools.md +++ b/dev/adr/0009-external-yaml-authoring-ctld-tools.md @@ -9,11 +9,12 @@ Accepted (CTLD-TOOLS-CONFIG, 2026-07-20). isolated **poetry** sub-project `tools/ctld-tools/`, **typer** CLI, **ruamel.yaml**, **lupa** (runs `CTLD_config.lua` in-process to read the defaults; `ctld.tr` stubbed to identity), pytest + ruff + mypy, workflow `python-quality.yml`. A **single** package `ctld_tools`. -- **Generated Lua is committed, not build-generated.** Following the VEAF pattern - (`dcsUnits.lua`, `dcs_types.lua`), `src/CTLD_config_defaults.lua` is committed and merged as-is; - the build stays pure PowerShell (no poetry in the Windows build). A **drift check** in the - quality gate (`gen-config` output == committed file) guards staleness. This supersedes the - "gen-config as a build step" wording. +- **Generated Lua is build-generated (gen-au-build), not committed.** `merge_CTLD.ps1` runs + `ctld-tools gen-config` before merging, so `src/CTLD_config_defaults.lua` is always fresh from the + YAML; it is a **git-ignored build artifact**. The build (and the busted CI job, whose loader loads + it) therefore require Python + poetry. This keeps the dev workflow to "edit the YAML, rebuild" with + no manual regen step. (An earlier iteration committed the file with a drift check; dropped in + favour of gen-au-build per maintainer preference.) - **Merge order**: the generated defaults module evaluates `ctld.tr(...)` at load time, so it is merged **after the `CTLD_i18n_*` modules** (not merely after `CTLD_config.lua`). - **`CTLDConfig:load()`** copies `ctld.__configDefaults` into `self.settings`; the `TEMPLATES` diff --git a/docs/developer/building-and-testing.fr.md b/docs/developer/building-and-testing.fr.md index 12cdae0..b9b5514 100644 --- a/docs/developer/building-and-testing.fr.md +++ b/docs/developer/building-and-testing.fr.md @@ -40,22 +40,21 @@ la sortie existe et n'est pas vide, et téléverse `CTLD.lua` en tant qu'artefac ## Configuration moteur (`ctld-tools`) Les valeurs par défaut du moteur sont des **données**, pas du code : elles vivent dans -`src/CTLD_config.yaml` (source de vérité unique, sectionnée `mm_facing` / `advanced`). Un module -généré `src/CTLD_config_defaults.lua` définit `ctld.__configDefaults`, que `CTLDConfig:load()` -recopie dans ses settings. **Éditez le YAML, jamais le Lua généré.** Les deux fichiers sont committés. +`src/CTLD_config.yaml` (source de vérité unique, sectionnée `mm_facing` / `advanced`). Au moment du +build, `merge_CTLD.ps1` régénère `src/CTLD_config_defaults.lua` depuis le YAML (via +`ctld-tools gen-config`) ; ce fichier définit `ctld.__configDefaults`, que `CTLDConfig:load()` +recopie dans ses settings. **Éditez le YAML** — le Lua généré est un **artefact de build (ignoré par +git)**, jamais édité à la main ni committé. -Après modification de `src/CTLD_config.yaml`, régénérez le Lua et committez les deux : - -``` -cd tools/ctld-tools -poetry install -poetry run ctld-tools gen-config --yaml ../../src/CTLD_config.yaml --out ../../src/CTLD_config_defaults.lua -``` +Pour changer un défaut : éditez `src/CTLD_config.yaml`, rebuild (`merge_CTLD.ps1` régénère le Lua +automatiquement), committez le YAML. **Le build nécessite désormais Python** : lancez `poetry install` +dans `tools/ctld-tools` une fois (le merge appelle `ctld-tools` ; il s'arrête avec un message clair si +poetry est absent). `tools/ctld-tools/` est un projet poetry isolé (typer, ruamel.yaml, lupa, pytest + ruff + mypy), -suivant les conventions Python de VMCT. Le job CI `python-quality` applique un **garde de parité** -(régénérer depuis le YAML reproduit les settings d'origine, wrappers `ctld.tr` inclus) et un -**contrôle de dérive** (le Lua généré committé doit être égal à un `gen-config` frais). +suivant les conventions Python de VMCT. Le job CI `python-quality` applique un **garde de parité** : +régénérer depuis le YAML reproduit les settings d'origine, wrappers `ctld.tr` inclus (une référence +figée, détectée avec un traducteur distinctif). ## Exécuter les tests (busted, sans DCS) diff --git a/docs/developer/building-and-testing.md b/docs/developer/building-and-testing.md index dd16fe8..e7bda5b 100644 --- a/docs/developer/building-and-testing.md +++ b/docs/developer/building-and-testing.md @@ -39,22 +39,20 @@ output exists and is non-empty, and uploads `CTLD.lua` as a build artifact. ## Engine configuration (`ctld-tools`) The engine defaults are **data**, not code: they live in `src/CTLD_config.yaml` (the single source -of truth, sectioned `mm_facing` / `advanced`). A generated module `src/CTLD_config_defaults.lua` -defines `ctld.__configDefaults`, which `CTLDConfig:load()` copies into its settings. **Edit the YAML, -never the generated Lua.** Both files are committed. +of truth, sectioned `mm_facing` / `advanced`). At build time `merge_CTLD.ps1` regenerates +`src/CTLD_config_defaults.lua` from the YAML (via `ctld-tools gen-config`); that file defines +`ctld.__configDefaults`, which `CTLDConfig:load()` copies into its settings. **Edit the YAML** — the +generated Lua is a **build artifact (git-ignored)**, never hand-edited or committed. -After changing `src/CTLD_config.yaml`, regenerate the Lua and commit both: - -``` -cd tools/ctld-tools -poetry install -poetry run ctld-tools gen-config --yaml ../../src/CTLD_config.yaml --out ../../src/CTLD_config_defaults.lua -``` +To change a default: edit `src/CTLD_config.yaml`, rebuild (`merge_CTLD.ps1` regenerates the Lua +automatically), commit the YAML. **The build now needs Python**: run `poetry install` in +`tools/ctld-tools` once (the merge calls `ctld-tools`; it aborts with a clear message if poetry is +missing). `tools/ctld-tools/` is an isolated poetry project (typer, ruamel.yaml, lupa, pytest + ruff + mypy), -following the VMCT Python conventions. The `python-quality` CI job enforces a **parity guard** -(regenerating from the YAML reproduces the original settings, `ctld.tr` wrappers included) and a -**drift check** (the committed generated Lua must equal a fresh `gen-config`). +following the VMCT Python conventions. The `python-quality` CI job enforces a **parity guard**: +regenerating from the YAML reproduces the original settings, `ctld.tr` wrappers included (a frozen +reference caught with a distinctive translator). ## Running tests (busted, no DCS) diff --git a/docs/mission-maker/ctld-tools.fr.md b/docs/mission-maker/ctld-tools.fr.md new file mode 100644 index 0000000..34551d1 --- /dev/null +++ b/docs/mission-maker/ctld-tools.fr.md @@ -0,0 +1,118 @@ +# Configurer CTLD avec `ctld-tools` + +`ctld-tools` est un petit outil en ligne de commande qui permet de configurer CTLD **sans écrire de +Lua**. Vous décrivez vos changements dans un court **`user-config.yaml`** — en désignant crates et +groupes de troupes **par leur nom** — et l'outil valide le fichier puis génère le +`CTLD_userConfig.lua` que votre mission charge. + +Vous n'avez jamais à retrouver le poids d'une crate, et les erreurs sont détectées chez vous (avec +des suggestions) au lieu de l'être dans DCS. + +## Récupérer l'outil + +Téléchargez **`ctld-tools.exe`** depuis la page [GitHub Releases](https://github.com/VEAF/CTLD/releases) +— il est attaché à chaque release. Pas besoin de Python. (Les développeurs peuvent aussi le lancer +depuis les sources avec `poetry` ; voir la doc développeur.) + +Placez-le où vous voulez ; lancez-le depuis un terminal dans le dossier de votre mission. + +## Le flux de travail + +``` +ctld-tools gen-user --scaffold --out user-config.yaml # 1. squelette commenté +# ... éditez user-config.yaml ... # 2. décrivez vos changements +ctld-tools validate --yaml user-config.yaml --src chemin/vers/src # 3. vérifiez +ctld-tools gen-user --yaml user-config.yaml --src chemin/vers/src --out CTLD_userConfig.lua # 4. générez +# ... chargez CTLD_userConfig.lua avant CTLD.lua dans l'éditeur de mission ... # 5. utilisez +``` + +`--src` pointe vers le dossier `src/` de CTLD (le catalogue de référence) : l'outil le lit pour +résoudre les noms et savoir quelles crates, groupes de troupes et types d'unités DCS existent. + +## Le format `user-config.yaml` + +Quatre sections optionnelles de premier niveau. **N'incluez que ce que vous changez.** Les crates et +groupes de troupes sont désignés **par leur nom**. + +### `settings` — valeurs simples + +```yaml +settings: + numberOfTroops: 8 + slingLoad: true +``` + +### `crates` — ajouter / retirer / modifier + +```yaml +crates: + add: + - section: Support # sous-menu F10 + name: Ural Ammo # libellé affiché dans le menu + unit: Ural-375 # nom de type DCS (validé) + side: 1 # 1=RED, 2=BLUE, omis=les deux + cratesRequired: 2 + weight_kg: 2000 # masse de la crate en kg (aussi sa clé unique) + remove: + - Heavy Tank - Abrams # par nom — pas de poids à retrouver + patch: + - name: Humvee - TOW # change un champ, garde le reste + cratesRequired: 3 +``` + +### `troops` — ajouter / retirer + +```yaml +troops: + add: + - name: Recon Team + inf: 3 + jtac: 1 + remove: + - 5x - Mortar Squad +``` + +### `arrays` — ajouter à des réglages de type liste + +```yaml +arrays: + transportPilotNames: [helicargo_custom_1] + troopZones: + - [pickzone_north, green, -1, yes, 0] +``` + +!!! tip "Block ou flow — au choix" + Tout ce qui précède est en style *block* (indenté, lisible). Vous pouvez aussi écrire le style + *flow* compact ; c'est le même YAML : + ```yaml + crates: + add: + - { section: Support, name: Ural Ammo, unit: Ural-375, side: 1, weight_kg: 2000 } + ``` + +## Commandes + +| Commande | Rôle | +|---|---| +| `gen-user --scaffold --out user-config.yaml` | Écrit un fichier de départ commenté. | +| `validate --yaml user-config.yaml --src src` | Vérifie le fichier ; affiche les constats, code non-zéro en cas d'erreur. | +| `gen-user --yaml user-config.yaml --src src --out CTLD_userConfig.lua` | Compile en Lua (valide d'abord, refuse en cas d'erreur). | + +**Ce que la validation vérifie :** chaque `unit` est un vrai type DCS ; une crate que vous +`remove`/`patch` existe (et n'est pas ambiguë) ; une crate `add` a un `weight_kg` unique ; les +groupes de troupes et réglages-listes existent. Un nom inconnu reçoit une suggestion *« vouliez-vous +dire … ? »*. + +## Chargement dans l'éditeur de mission + +Le `CTLD_userConfig.lua` généré se charge exactement comme le modèle écrit à la main : + +1. **MISSION START → DO SCRIPT FILE** → `CTLD_userConfig.lua` +2. **MISSION START → DO SCRIPT FILE** → `CTLD.lua` + +Le trigger du user-config doit venir **avant** le trigger CTLD. + +!!! note "Vous préférez écrire le Lua à la main ?" + C'est toujours possible — voir [Configuration](configuration.md). `ctld-tools` est la voie + recommandée pour la plupart des missions, mais le modèle Lua reste pleinement pris en charge pour + les utilisateurs avancés. diff --git a/docs/mission-maker/ctld-tools.md b/docs/mission-maker/ctld-tools.md new file mode 100644 index 0000000..317380a --- /dev/null +++ b/docs/mission-maker/ctld-tools.md @@ -0,0 +1,115 @@ +# Configuring CTLD with `ctld-tools` + +`ctld-tools` is a small command-line tool that lets you configure CTLD **without writing Lua**. You +describe your changes in a short **`user-config.yaml`** — referring to crates and troop groups **by +name** — and the tool validates it and generates the `CTLD_userConfig.lua` your mission loads. + +You never look up crate weights, and mistakes are caught at your desk (with suggestions) instead of +in DCS. + +## Get the tool + +Download **`ctld-tools.exe`** from the [GitHub Releases](https://github.com/VEAF/CTLD/releases) page +— it is attached to each release. No Python needed. (Developers can also run it from source with +`poetry`; see the developer docs.) + +Put it anywhere; run it from a terminal in your mission folder. + +## The workflow + +``` +ctld-tools gen-user --scaffold --out user-config.yaml # 1. commented starter +# ... edit user-config.yaml ... # 2. describe your changes +ctld-tools validate --yaml user-config.yaml --src path/to/src # 3. check it +ctld-tools gen-user --yaml user-config.yaml --src path/to/src --out CTLD_userConfig.lua # 4. generate +# ... load CTLD_userConfig.lua before CTLD.lua in the Mission Editor ... # 5. use it +``` + +`--src` points at the CTLD `src/` folder (the reference catalogue): the tool reads it to resolve +names, and to know which crates, troop groups and DCS unit types exist. + +## The `user-config.yaml` format + +Four optional top-level sections. **Only include what you change.** Crates and troop groups are +targeted **by name**. + +### `settings` — simple values + +```yaml +settings: + numberOfTroops: 8 + slingLoad: true +``` + +### `crates` — add / remove / patch + +```yaml +crates: + add: + - section: Support # F10 sub-menu + name: Ural Ammo # label shown in the menu + unit: Ural-375 # DCS type name (validated) + side: 1 # 1=RED, 2=BLUE, omit=both + cratesRequired: 2 + weight_kg: 2000 # crate mass in kg (also its unique key) + remove: + - Heavy Tank - Abrams # by name — no weight to look up + patch: + - name: Humvee - TOW # change one field, keep the rest + cratesRequired: 3 +``` + +### `troops` — add / remove + +```yaml +troops: + add: + - name: Recon Team + inf: 3 + jtac: 1 + remove: + - 5x - Mortar Squad +``` + +### `arrays` — append to list settings + +```yaml +arrays: + transportPilotNames: [helicargo_custom_1] + troopZones: + - [pickzone_north, green, -1, yes, 0] +``` + +!!! tip "Block or flow — your choice" + Everything above is *block* style (indented, readable). You can also write the compact *flow* + style; it's the same YAML: + ```yaml + crates: + add: + - { section: Support, name: Ural Ammo, unit: Ural-375, side: 1, weight_kg: 2000 } + ``` + +## Commands + +| Command | What it does | +|---|---| +| `gen-user --scaffold --out user-config.yaml` | Write a commented starter file. | +| `validate --yaml user-config.yaml --src src` | Check the file; prints findings, exits non-zero on error. | +| `gen-user --yaml user-config.yaml --src src --out CTLD_userConfig.lua` | Compile to Lua (runs `validate` first, refuses on error). | + +**What validation checks:** every `unit` is a real DCS type; a crate you `remove`/`patch` exists +(and is unambiguous); a crate you `add` has a unique `weight_kg`; troop groups and array settings +exist. Unknown names get a *"did you mean …?"* suggestion. + +## Loading in the Mission Editor + +The generated `CTLD_userConfig.lua` loads exactly like the hand-written template: + +1. **MISSION START → DO SCRIPT FILE** → `CTLD_userConfig.lua` +2. **MISSION START → DO SCRIPT FILE** → `CTLD.lua` + +The user-config trigger must come **before** the CTLD trigger. + +!!! note "Prefer to hand-write Lua?" + You still can — see [Configuration](configuration.md). `ctld-tools` is the recommended path for + most missions, but the Lua template stays fully supported for power users. diff --git a/mkdocs.yml b/mkdocs.yml index 3440aee..a2b4b32 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -79,6 +79,7 @@ nav: - Mission Maker: - Overview: mission-maker/index.md - Configuration: mission-maker/configuration.md + - Configure with ctld-tools: mission-maker/ctld-tools.md - Zone setup: mission-maker/zones.md - Scenes & FOB: mission-maker/scenes-fob.md - Crate catalogue: mission-maker/crates-catalogue.md diff --git a/src/CTLD_config_defaults.lua b/src/CTLD_config_defaults.lua deleted file mode 100644 index 4ab2c52..0000000 --- a/src/CTLD_config_defaults.lua +++ /dev/null @@ -1,1027 +0,0 @@ --- GENERATED by ctld-tools (gen-config) — DO NOT EDIT. --- Source of truth: ctld-config.yaml. Regenerate via the build. -ctld = ctld or {} -ctld.__configDefaults = { - AASystemCrateStacking = false, - AASystemLimitBLUE = 20, - AASystemLimitRED = 20, - AIZones = { - { - "aizone1", - "none", - 2, - "P", - -1 - }, - { - "aizone2", - "none", - 2, - "D" - }, - { - "aizone3", - "none", - 1, - "P", - -1 - }, - { - "aizone4", - "none", - 1, - "D", - "G" - } - }, - CIV_WEIGHT = 2, - JTAC_LIMIT_BLUE = 10, - JTAC_LIMIT_RED = 10, - JTAC_WEIGHT = 15, - JTAC_allow9Line = true, - JTAC_allowSmokeRequest = true, - JTAC_allowStandbyMode = true, - JTAC_droneAltitude = 4000, - JTAC_droneRadius = 1000, - JTAC_dropEnabled = true, - JTAC_jtacStatusF10 = true, - JTAC_laseIntervalSeconds = 15, - JTAC_laseSpotCorrections = true, - JTAC_location = true, - JTAC_lock = "all", - JTAC_maxDistance = 10000, - JTAC_searchIntervalSeconds = 10, - JTAC_smokeColour_BLUE = 1, - JTAC_smokeColour_RED = 4, - JTAC_smokeMarginOfError = 50, - JTAC_smokeOffset_x = 0.0, - JTAC_smokeOffset_y = 2.0, - JTAC_smokeOffset_z = 0.0, - JTAC_smokeOn_BLUE = false, - JTAC_smokeOn_RED = false, - JTAC_targetDeconfliction = true, - KIT_WEIGHT = 20, - MANPAD_WEIGHT = 18, - MG_WEIGHT = 10, - MORTAR_WEIGHT = 26, - RIFLE_WEIGHT = 5, - RPG_WEIGHT = 7.6, - SOLDIER_WEIGHT = 80, - aaLaunchers = 3, - addPlayerAircraftByType = true, - aiZones = {}, - allowRandomAiTeamPickups = false, - autoUnpackRadiusParachute = 1000, - beaconAutoRefreshLayer = false, - beaconIconColor = { - 1.0, - 0.5, - 0.0, - 1.0 - }, - beaconIconRadius = 25, - beaconLayerEnabled = false, - beaconRefreshInterval = 60, - beaconTextSize = 12, - capabilitiesByType = { - ["76MD"] = { - canParachuteDrop = false, - canSlingload = false, - canTransportWholeVehicle = true, - convertNativeLoadToCTLD = false, - cratesEnabled = true, - loadableVehiclesBLUE = { - "M1045 HMMWV TOW", - "M1043 HMMWV Armament", - "Hummer" - }, - loadableVehiclesRED = { - "BRDM-2", - "BTR_D" - }, - maxCratesOnboard = 20, - maxTroopsOnboard = 80, - maxVehicleWeight = 20000, - maxWholeVehiclesOnboard = 2, - troopsEnabled = true, - useNativeDcsCargoSystem = false - }, - ["C-130J-30"] = { - canParachuteDrop = true, - canSlingload = false, - canTransportWholeVehicle = true, - convertNativeLoadToCTLD = false, - cratesEnabled = true, - loadableVehiclesBLUE = { - "M1045 HMMWV TOW", - "M1043 HMMWV Armament", - "Hummer" - }, - loadableVehiclesRED = { - "BRDM-2", - "BTR_D" - }, - maxCratesOnboard = 22, - maxTroopsOnboard = 80, - maxVehicleWeight = 20000, - maxWholeVehiclesOnboard = 2, - troopsEnabled = true, - useNativeDcsCargoSystem = true - }, - ["CH-47Fbl1"] = { - canParachuteDrop = true, - canSlingload = true, - canTransportWholeVehicle = false, - convertNativeLoadToCTLD = true, - cratesEnabled = true, - loadableVehiclesBLUE = { - "M1045 HMMWV TOW", - "M1043 HMMWV Armament", - "Hummer" - }, - loadableVehiclesRED = { - "BRDM-2", - "BTR_D" - }, - maxCratesOnboard = 8, - maxTroopsOnboard = 40, - maxVehicleWeight = 11000, - maxWholeVehiclesOnboard = 1, - troopsEnabled = true, - useNativeDcsCargoSystem = true - }, - Hercules = { - canParachuteDrop = false, - canSlingload = false, - canTransportWholeVehicle = true, - convertNativeLoadToCTLD = false, - cratesEnabled = true, - loadableVehiclesBLUE = { - "M1045 HMMWV TOW", - "M1043 HMMWV Armament", - "Hummer" - }, - loadableVehiclesRED = { - "BRDM-2", - "BTR_D" - }, - maxCratesOnboard = 1, - maxTroopsOnboard = 30, - maxVehicleWeight = 20000, - maxWholeVehiclesOnboard = 2, - troopsEnabled = true, - useNativeDcsCargoSystem = false - }, - ["Mi-24P"] = { - canParachuteDrop = false, - canSlingload = false, - canTransportWholeVehicle = false, - convertNativeLoadToCTLD = false, - cratesEnabled = true, - maxCratesOnboard = 1, - maxTroopsOnboard = 10, - maxWholeVehiclesOnboard = 0, - troopsEnabled = true, - useNativeDcsCargoSystem = true - }, - ["Mi-8MT"] = { - canParachuteDrop = false, - canSlingload = true, - canTransportWholeVehicle = true, - convertNativeLoadToCTLD = false, - cratesEnabled = true, - maxCratesOnboard = 2, - maxTroopsOnboard = 16, - maxWholeVehiclesOnboard = 0, - troopsEnabled = true, - useNativeDcsCargoSystem = true - }, - ["SK-60"] = { - canParachuteDrop = false, - canSlingload = false, - canTransportWholeVehicle = false, - convertNativeLoadToCTLD = false, - cratesEnabled = true, - maxCratesOnboard = 1, - maxTroopsOnboard = 4, - maxWholeVehiclesOnboard = 0, - troopsEnabled = true, - useNativeDcsCargoSystem = false - }, - ["UH-1H"] = { - canParachuteDrop = true, - canSlingload = true, - canTransportWholeVehicle = true, - convertNativeLoadToCTLD = true, - cratesEnabled = true, - loadableVehiclesBLUE = { - "M1045 HMMWV TOW", - "M1043 HMMWV Armament", - "Hummer" - }, - loadableVehiclesRED = { - "BRDM-2", - "BTR_D" - }, - maxCratesOnboard = 1, - maxTroopsOnboard = 8, - maxVehicleWeight = 1360, - maxWholeVehiclesOnboard = 1, - troopsEnabled = true, - useNativeDcsCargoSystem = true - }, - ["UH-60L"] = { - canParachuteDrop = false, - canSlingload = true, - canTransportWholeVehicle = false, - convertNativeLoadToCTLD = false, - cratesEnabled = true, - maxCratesOnboard = 1, - maxTroopsOnboard = 12, - maxWholeVehiclesOnboard = 0, - troopsEnabled = true, - useNativeDcsCargoSystem = false - } - }, - crateSpacing = 5, - ctldLogPath = "", - debug = false, - debugScreenLog = false, - debugScreenLogDuration = 10, - demineRadius = 150, - deployedBeaconBattery = 30, - disableAllSmoke = false, - dynamicZoneRadius = 200, - enableAllCrates = true, - enableAutoOrbitingFlyingJtacOnTarget = true, - enableCrates = true, - enableFARPRepack = true, - enableFastRopeInsertion = true, - enableHoverSlingload = true, - enablePackingVehicles = true, - enableSmokeDrop = true, - enabledFOBBuilding = true, - enabledRadioBeaconDrop = true, - extractableGroups = { - "extract1", - "extract2", - "extract3", - "extract4", - "extract5", - "extract6", - "extract7", - "extract8", - "extract9", - "extract10", - "extract11", - "extract12", - "extract13", - "extract14", - "extract15", - "extract16", - "extract17", - "extract18", - "extract19", - "extract20", - "extract21", - "extract22", - "extract23", - "extract24", - "extract25" - }, - fastRopeMaximumHeight = 18.28, - fobDestructionThreshold = 0.5, - fobLogisticZoneRadius = 150, - fobMinDistanceFromZones = 500, - fobTroopPickupRadius = 150, - groundAglThreshold = 5.0, - groundVehicleWeights = { - ["BRDM-2"] = 7000, - BTR_D = 8000, - Hummer = 2400, - ["M1043 HMMWV Armament"] = 2500, - ["M1045 HMMWV TOW"] = 5000 - }, - hoverTime = 10, - loadCrateFromMenu = true, - loadableGroups = { - { - at = 2, - inf = 6, - mg = 2, - name = ctld.tr("Standard Group") - }, - { - aa = 3, - inf = 2, - name = ctld.tr("Anti Air") - }, - { - at = 6, - inf = 2, - name = ctld.tr("Anti Tank") - }, - { - mortar = 6, - name = ctld.tr("Mortar Squad") - }, - { - inf = 4, - jtac = 1, - name = ctld.tr("JTAC Group") - }, - { - inf = 4, - jtac = 2, - name = ctld.tr("JTAC Group 2") - }, - { - jtac = 1, - name = ctld.tr("Single JTAC") - }, - { - at = 4, - inf = 12, - mg = 4, - name = ctld.tr("2x - Standard Groups") - }, - { - aa = 6, - inf = 4, - name = ctld.tr("2x - Anti Air") - }, - { - at = 12, - inf = 4, - name = ctld.tr("2x - Anti Tank") - }, - { - at = 4, - inf = 12, - mg = 4, - mortar = 12, - name = ctld.tr("2x - Standard Groups + 2x Mortar") - }, - { - at = 6, - inf = 18, - mg = 6, - name = ctld.tr("3x - Standard Groups") - }, - { - aa = 9, - inf = 6, - name = ctld.tr("3x - Anti Air") - }, - { - at = 18, - inf = 6, - name = ctld.tr("3x - Anti Tank") - }, - { - mortar = 18, - name = ctld.tr("3x - Mortar Squad") - }, - { - mortar = 30, - name = ctld.tr("5x - Mortar Squad") - } - }, - location_DMS = false, - logisticUnits = { - "logistic1", - "logistic2", - "logistic3", - "logistic4", - "logistic5", - "logistic6", - "logistic7", - "logistic8", - "logistic9", - "logistic10" - }, - maxDistanceFromCrate = 5.5, - maxDropHeight = 7.5, - maxExtractDistance = 125, - maxSlingloadSpeed = 50, - maxTransportWeight = 0, - maximumDistanceLogistic = 200, - maximumDistancePackableUnitsSearch = 200, - maximumHoverHeight = 12.0, - maximumSearchDistance = 3000, - minimumHoverHeight = 7.5, - modTypes = {}, - nbLimitSpawnedTroops = { - 0, - 0 - }, - numberOfTroops = 10, - parachuteDescentRateCrates = 5, - parachuteDescentRateTroops = 5, - parachuteDescentRateVehicles = 8, - parachuteInertiaFactor = 0.3, - parachuteLateralDriftMax = 80, - parachuteLateralDriftMin = 10, - parachuteMinAltitudeCrates = 152, - parachuteMinAltitudeTroops = 152, - parachuteMinAltitudeVehicles = 152, - radioSound = "beacon.ogg", - radioSoundFC3 = "beaconsilent.ogg", - reconEnabled = false, - reconF10Menu = true, - reconIconScale = 1.0, - reconMinAltitude = 50, - reconRefreshInterval = 10, - reconSearchRadius = 5000, - showMinefieldOnF10Map = true, - slingLoad = false, - smokeAutoResume = false, - smokeAutoResumeInterval = 270, - smokeRefreshInterval = 300, - spawnDistanceInCircle = 10, - spawnableCrates = { - Artillery = { - { - cratesRequired = 3, - desc = ctld.tr("MLRS"), - side = 2, - unit = "MLRS", - weight = 1002.01 - }, - { - cratesRequired = 3, - desc = ctld.tr("SpGH DANA"), - side = 2, - unit = "SpGH_Dana", - weight = 1002.02 - }, - { - cratesRequired = 3, - desc = ctld.tr("T155 Firtina"), - side = 2, - unit = "T155_Firtina", - weight = 1002.03 - }, - { - cratesRequired = 3, - desc = ctld.tr("Howitzer"), - side = 2, - unit = "M-109", - weight = 1002.04 - }, - { - cratesRequired = 3, - desc = ctld.tr("SPH 2S19 Msta"), - side = 1, - unit = "SAU Msta", - weight = 1002.11 - } - }, - ["Combat Vehicles"] = { - { - cratesRequired = 3, - desc = ctld.tr("Humvee - MG"), - side = 2, - unit = "M1043 HMMWV Armament", - weight = 1000.01 - }, - { - cratesRequired = 5, - desc = ctld.tr("Humvee - TOW"), - side = 2, - unit = "M1045 HMMWV TOW", - weight = 1000.02 - }, - { - cratesRequired = 2, - desc = ctld.tr("Light Tank - MRAP"), - side = 2, - unit = "MaxxPro_MRAP", - weight = 1000.03 - }, - { - cratesRequired = 3, - desc = ctld.tr("Med Tank - LAV-25"), - side = 2, - unit = "LAV-25", - weight = 1000.04 - }, - { - cratesRequired = 4, - desc = ctld.tr("Heavy Tank - Abrams"), - side = 2, - unit = "M-1 Abrams", - weight = 1000.05 - }, - { - cratesRequired = 8, - desc = ctld.tr("BTR-D"), - side = 1, - unit = "BTR_D", - weight = 1000.11 - }, - { - cratesRequired = 7, - desc = ctld.tr("BRDM-2"), - side = 1, - unit = "BRDM-2", - weight = 1000.12 - } - }, - Drone = { - { - desc = ctld.tr("MQ-9 Repear - JTAC"), - isJTAC = true, - side = 2, - spawnAs = "AIRPLANE", - specificParams = { - alti = 3000, - orbitRadiusNoLase = 2000, - orbitRadiusOnLase = 1000, - speed = 150 - }, - unit = "MQ-9 Reaper", - weight = 1006.01 - }, - { - desc = ctld.tr("RQ-1A Predator - JTAC"), - isJTAC = true, - side = 1, - spawnAs = "AIRPLANE", - specificParams = { - alti = 3000, - orbitRadiusNoLase = 2000, - orbitRadiusOnLase = 1000, - speed = 150 - }, - unit = "RQ-1A Predator", - weight = 1006.11 - } - }, - ["SAM short range"] = { - { - cratesRequired = 3, - desc = ctld.tr("M1097 Avenger"), - side = 2, - unit = "M1097 Avenger", - weight = 1003.01 - }, - { - cratesRequired = 2, - desc = ctld.tr("M48 Chaparral"), - side = 2, - unit = "M48 Chaparral", - weight = 1003.02 - }, - { - cratesRequired = 3, - desc = ctld.tr("Roland ADS"), - side = 2, - unit = "Roland ADS", - weight = 1003.03 - }, - { - cratesRequired = 3, - desc = ctld.tr("Gepard AAA"), - side = 2, - unit = "Gepard", - weight = 1003.04 - }, - { - cratesRequired = 3, - desc = ctld.tr("LPWS C-RAM"), - side = 2, - unit = "HEMTT_C-RAM_Phalanx", - weight = 1003.05 - }, - { - cratesRequired = 3, - desc = ctld.tr("9K33 Osa"), - side = 1, - unit = "Osa 9A33 ln", - weight = 1003.11 - }, - { - cratesRequired = 3, - desc = ctld.tr("9P31 Strela-1"), - side = 1, - unit = "Strela-1 9P31", - weight = 1003.12 - }, - { - cratesRequired = 3, - desc = ctld.tr("9K35M Strela-10"), - side = 1, - unit = "Strela-10M3", - weight = 1003.13 - }, - { - cratesRequired = 3, - desc = ctld.tr("9K331 Tor"), - side = 1, - unit = "Tor 9A331", - weight = 1003.14 - }, - { - cratesRequired = 3, - desc = ctld.tr("2K22 Tunguska"), - side = 1, - unit = "2S6 Tunguska", - weight = 1003.15 - } - }, - Support = { - { - cratesRequired = 2, - desc = ctld.tr("Hummer - JTAC"), - isJTAC = true, - side = 2, - unit = "Hummer", - weight = 1001.01 - }, - { - cratesRequired = 2, - desc = ctld.tr("M-818 Ammo Truck"), - side = 2, - unit = "M 818", - weight = 1001.02 - }, - { - cratesRequired = 2, - desc = ctld.tr("M-978 Tanker"), - side = 2, - unit = "M978 HEMTT Tanker", - weight = 1001.03 - }, - { - desc = ctld.tr("SKP-11 - JTAC"), - isJTAC = true, - side = 1, - unit = "SKP-11", - weight = 1001.11 - }, - { - cratesRequired = 2, - desc = ctld.tr("Ural-375 Ammo Truck"), - side = 1, - unit = "Ural-375", - weight = 1001.12 - }, - { - cratesRequired = 2, - desc = ctld.tr("KAMAZ Ammo Truck"), - side = 1, - unit = "KAMAZ Truck", - weight = 1001.13 - }, - { - cratesRequired = 3, - desc = ctld.tr("EWR Radar"), - unit = "FPS-117", - weight = 1001.21 - } - } - }, - spawnableCratesModels = { - dynamic = { - canCargo = true, - category = "Cargos", - type = "ammo_cargo" - }, - load = { - canCargo = true, - category = "Cargos", - type = "ammo_cargo" - }, - sling = { - canCargo = true, - category = "Cargos", - shape_name = "bw_container_cargo", - type = "container_cargo" - } - }, - transportPilotNames = { - "helicargo1", - "helicargo2", - "helicargo3", - "helicargo4", - "helicargo5", - "helicargo6", - "helicargo7", - "helicargo8", - "helicargo9", - "helicargo10", - "helicargo11", - "helicargo12", - "helicargo13", - "helicargo14", - "helicargo15", - "helicargo16", - "helicargo17", - "helicargo18", - "helicargo19", - "helicargo20", - "helicargo21", - "helicargo22", - "helicargo23", - "helicargo24", - "helicargo25", - "MEDEVAC #1", - "MEDEVAC #2", - "MEDEVAC #3", - "MEDEVAC #4", - "MEDEVAC #5", - "MEDEVAC #6", - "MEDEVAC #7", - "MEDEVAC #8", - "MEDEVAC #9", - "MEDEVAC #10", - "MEDEVAC #11", - "MEDEVAC #12", - "MEDEVAC #13", - "MEDEVAC #14", - "MEDEVAC #15", - "MEDEVAC #16", - "MEDEVAC RED #1", - "MEDEVAC RED #2", - "MEDEVAC RED #3", - "MEDEVAC RED #4", - "MEDEVAC RED #5", - "MEDEVAC RED #6", - "MEDEVAC RED #7", - "MEDEVAC RED #8", - "MEDEVAC RED #9", - "MEDEVAC RED #10", - "MEDEVAC RED #11", - "MEDEVAC RED #12", - "MEDEVAC RED #13", - "MEDEVAC RED #14", - "MEDEVAC RED #15", - "MEDEVAC RED #16", - "MEDEVAC RED #17", - "MEDEVAC RED #18", - "MEDEVAC RED #19", - "MEDEVAC RED #20", - "MEDEVAC RED #21", - "MEDEVAC BLUE #1", - "MEDEVAC BLUE #2", - "MEDEVAC BLUE #3", - "MEDEVAC BLUE #4", - "MEDEVAC BLUE #5", - "MEDEVAC BLUE #6", - "MEDEVAC BLUE #7", - "MEDEVAC BLUE #8", - "MEDEVAC BLUE #9", - "MEDEVAC BLUE #10", - "MEDEVAC BLUE #11", - "MEDEVAC BLUE #12", - "MEDEVAC BLUE #13", - "MEDEVAC BLUE #14", - "MEDEVAC BLUE #15", - "MEDEVAC BLUE #16", - "MEDEVAC BLUE #17", - "MEDEVAC BLUE #18", - "MEDEVAC BLUE #19", - "MEDEVAC BLUE #20", - "MEDEVAC BLUE #21", - "transport1", - "transport2", - "transport3", - "transport4", - "transport5", - "transport6", - "transport7", - "transport8", - "transport9", - "transport10", - "transport11", - "transport12", - "transport13", - "transport14", - "transport15", - "transport16", - "transport17", - "transport18", - "transport19", - "transport20", - "transport21", - "transport22", - "transport23", - "transport24", - "transport25" - }, - troopPickupAtFOB = true, - troopZones = { - { - "pickzone1", - "blue", - -1, - "yes", - 0 - }, - { - "pickzone2", - "red", - -1, - "yes", - 0 - }, - { - "pickzone3", - "none", - -1, - "yes", - 0 - }, - { - "pickzone4", - "none", - -1, - "yes", - 0 - }, - { - "pickzone5", - "none", - -1, - "yes", - 0 - }, - { - "pickzone6", - "none", - -1, - "yes", - 0 - }, - { - "pickzone7", - "none", - -1, - "yes", - 0 - }, - { - "pickzone8", - "none", - -1, - "yes", - 0 - }, - { - "pickzone9", - "none", - 5, - "yes", - 1 - }, - { - "pickzone10", - "none", - 10, - "yes", - 2 - }, - { - "pickzone11", - "blue", - 20, - "no", - 2 - }, - { - "pickzone12", - "red", - 20, - "no", - 1 - }, - { - "pickzone13", - "none", - -1, - "yes", - 0 - }, - { - "pickzone14", - "none", - -1, - "yes", - 0 - }, - { - "pickzone15", - "none", - -1, - "yes", - 0 - }, - { - "pickzone16", - "none", - -1, - "yes", - 0 - }, - { - "pickzone17", - "none", - -1, - "yes", - 0 - }, - { - "pickzone18", - "none", - -1, - "yes", - 0 - }, - { - "pickzone19", - "none", - 5, - "yes", - 0 - }, - { - "pickzone20", - "none", - 10, - "yes", - 0, - 1000 - }, - { - "USA Carrier", - "blue", - 10, - "yes", - 0, - 1001 - } - }, - wpZones = { - { - "wpzone1", - "green", - "yes", - 2 - }, - { - "wpzone2", - "blue", - "yes", - 2 - }, - { - "wpzone3", - "orange", - "yes", - 2 - }, - { - "wpzone4", - "none", - "yes", - 2 - }, - { - "wpzone5", - "none", - "yes", - 2 - }, - { - "wpzone6", - "none", - "yes", - 1 - }, - { - "wpzone7", - "none", - "yes", - 1 - }, - { - "wpzone8", - "none", - "yes", - 1 - }, - { - "wpzone9", - "none", - "yes", - 1 - }, - { - "wpzone10", - "none", - "no", - 0 - } - } -} diff --git a/tools/build/merge_CTLD.ps1 b/tools/build/merge_CTLD.ps1 index 18fbdf2..43f763c 100644 --- a/tools/build/merge_CTLD.ps1 +++ b/tools/build/merge_CTLD.ps1 @@ -14,6 +14,26 @@ $distDir = Join-Path $repoRoot "dist" $userCfgSrc = Join-Path $srcDir "CTLD_userConfig.lua" $userCfgDest = Join-Path $distDir "CTLD_userConfig.lua" +# Regenerate the engine defaults from the YAML source of truth (gen-au-build). +# The generated CTLD_config_defaults.lua is a build artifact (git-ignored). ctld-tools +# is an isolated poetry project — run `poetry install` in tools/ctld-tools once. +$ctldToolsDir = Join-Path $repoRoot "tools\ctld-tools" +$configYaml = Join-Path $srcDir "CTLD_config.yaml" +$defaultsLua = Join-Path $srcDir "CTLD_config_defaults.lua" +Write-Host "Generating CTLD_config_defaults.lua from CTLD_config.yaml (ctld-tools)..." +Push-Location $ctldToolsDir +try { + & poetry run ctld-tools gen-config --yaml $configYaml --out $defaultsLua + if ($LASTEXITCODE -ne 0) { throw "gen-config returned $LASTEXITCODE" } +} +catch { + Write-Host "[ERROR] Could not generate CTLD_config_defaults.lua via ctld-tools." + Write-Host " Run 'poetry install' in tools/ctld-tools first (needs Python + poetry)." + Pop-Location + exit 1 +} +Pop-Location + # UTF-8 without BOM encoder (works on PS 5 and PS 7) $utf8NoBOM = [System.Text.UTF8Encoding]::new($false) diff --git a/tools/ctld-tools/.gitignore b/tools/ctld-tools/.gitignore index 8a8ce43..05e1f2f 100644 --- a/tools/ctld-tools/.gitignore +++ b/tools/ctld-tools/.gitignore @@ -8,3 +8,4 @@ dist/ build/ coverage.xml .coverage +*.spec diff --git a/tools/ctld-tools/tests/conftest.py b/tools/ctld-tools/tests/conftest.py new file mode 100644 index 0000000..a959d41 --- /dev/null +++ b/tools/ctld-tools/tests/conftest.py @@ -0,0 +1,19 @@ +"""Ensure the generated engine defaults exist before the tests run. + +Since gen-au-build, `src/CTLD_config_defaults.lua` is a build artifact (git-ignored), +not committed. Tests that load the config (via lupa) need it, so regenerate it once +per session from the YAML source of truth. +""" + +from pathlib import Path + +import pytest + +from ctld_tools.genconfig import generate_file + +_REPO = Path(__file__).resolve().parents[3] + + +@pytest.fixture(scope="session", autouse=True) +def _ensure_generated_defaults(): + generate_file(_REPO / "src" / "CTLD_config.yaml", _REPO / "src" / "CTLD_config_defaults.lua") diff --git a/tools/ctld-tools/tests/test_parity.py b/tools/ctld-tools/tests/test_parity.py index e3a9069..ccca99f 100644 --- a/tools/ctld-tools/tests/test_parity.py +++ b/tools/ctld-tools/tests/test_parity.py @@ -16,7 +16,6 @@ REPO = Path(__file__).resolve().parents[3] SRC = REPO / "src" YAML = SRC / "CTLD_config.yaml" -COMMITTED_LUA = SRC / "CTLD_config_defaults.lua" FIXTURE = Path(__file__).parent / "data" / "reference_settings.json" DISTINCTIVE = 'function(key, default) return "<>" end' @@ -46,13 +45,3 @@ def test_loaded_config_matches_reference(reference): same settings as the original inline defaults (the migration parity guard).""" loaded = load_default_settings(SRC, tr=DISTINCTIVE) assert loaded == reference - - -def test_committed_generated_lua_is_up_to_date(tmp_path): - """The committed src/CTLD_config_defaults.lua must equal a fresh gen-config.""" - out = tmp_path / "fresh.lua" - generate_file(YAML, out) - assert out.read_text(encoding="utf-8") == COMMITTED_LUA.read_text(encoding="utf-8"), ( - "src/CTLD_config_defaults.lua is stale — regenerate with " - "`ctld-tools gen-config --yaml src/CTLD_config.yaml --out src/CTLD_config_defaults.lua`" - )