Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 01 — The schema declares the sound editor and the original-name labels

**Status:** todo
**Status:** done
**Lot:** FEAT-CUSTOM-BEACON-SOUNDS

## Problem
Expand All @@ -24,7 +24,9 @@ this lot would report a missing setting at mission start — `FIX-TOOL-I18N-LANG

## Acceptance

- [ ] `Schema.editor("radioSound") == "sound"`; a setting without the field returns `None`.
- [ ] The two label keys are absent from `src/CTLD_config.yaml`, so `validate` on an untouched
pre-lot configuration reports nothing new (test with a `2.0.0-rc6` config).
- [ ] The header comment of the schema documents `editor:`.
- [x] `Schema.editor("radioSound") == "sound"`; a setting without the field returns `None`.
- [x] The two label keys are absent from `src/CTLD_config.yaml` (asserted in `test_schema.py`), so
the completeness rule never demands them; the whole suite still passes on the shipped
catalogue.
- [x] The header comment of the schema documents `editor:` and `hidden:`.
- [x] `/api/schema` carries both, so the UI binds on metadata rather than on a setting name.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 02 — The session holds the sound, not a path

**Status:** todo
**Status:** done
**Lot:** FEAT-CUSTOM-BEACON-SOUNDS

## Problem
Expand All @@ -25,7 +25,8 @@ it, which ticket 04 turns into a blocking error rather than a surprise.

## Acceptance

- [ ] Choosing a file then installing writes those bytes, with the source file deleted in between.
- [ ] Opening a `.miz` with custom sounds, then installing into a **different** mission, reproduces
them — no access to the original file.
- [ ] Loading the defaults after a custom choice leaves no bytes behind.
- [x] Choosing a file then installing writes those bytes, with the source file deleted in between
(`test_a_custom_sound_survives_install_reopen_and_reinstall`).
- [x] Opening a `.miz` with custom sounds, then installing into a **different** mission, reproduces
them byte for byte — the original file is `unlink()`ed and the session reset in between.
- [x] Loading the defaults, a `.yaml`, or resetting the session leaves no bytes behind.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 03 — Install writes the chosen sound under its reserved name

**Status:** todo
**Status:** done
**Lot:** FEAT-CUSTOM-BEACON-SOUNDS

## Problem
Expand All @@ -22,9 +22,10 @@ and whether it was the default or a chosen file.

## Acceptance

- [ ] A custom install puts `CTLD_beacon_custom.ogg` in `l10n/DEFAULT/`, in `mapResource`, and in
the preload trigger — same three places as a default one.
- [ ] `radioSound` in the injected configuration matches the file actually written.
- [ ] Reinstalling over the same mission replaces rather than accumulates (existing idempotence
test, extended to the custom case).
- [ ] Going back to the default restores `beacon.ogg` and leaves the mission playable.
- [x] A custom install puts `CTLD_beacon_custom.ogg` in `l10n/DEFAULT/`, in `mapResource` (via
`sound_key`) and in the preload trigger — the same three places as a bundled one.
- [x] `radioSound` in the injected configuration matches the file actually written; the report now
carries `{setting, file, size, custom}` per sound.
- [x] Reinstalling over the same mission still replaces rather than accumulates (the existing
idempotence tests pass unchanged).
- [x] Going back to the default restores `beacon.ogg`.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 04 — A sound that cannot be produced blocks the install

**Status:** todo
**Status:** done
**Lot:** FEAT-CUSTOM-BEACON-SOUNDS

## Problem
Expand All @@ -23,7 +23,10 @@ Two ways to end up with silent beacons, both invisible until someone flies:

## Acceptance

- [ ] A non-Ogg file is refused at selection; the configuration is unchanged.
- [ ] Opening a `.yaml` that names a custom sound, then targeting a mission without it → blocking
error, install refused.
- [ ] Same configuration, but the target mission already holds the file → no error, install allowed.
- [x] A non-Ogg file is refused at selection (422, `OggS` named in the message) and the
configuration is left pointing at the bundled sound.
- [x] Opening a `.yaml` that names a custom sound, then targeting a mission without it → blocking
`validate.sound.missing`, install refused.
- [x] Same configuration, but the target mission already holds the file → no error, install allowed
and the existing bytes rewritten.
- [x] No size cap; the size is reported instead.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 05 — Default or custom, in the interface

**Status:** todo
**Status:** done
**Lot:** FEAT-CUSTOM-BEACON-SOUNDS

## Problem
Expand All @@ -23,7 +23,9 @@ Both sounds are independent: one may be custom and the other default.

## Acceptance

- [ ] Neither `radioSound` nor `radioSoundFC3` appears as a literal in any component.
- [ ] Reopening a mission with a custom sound shows *Custom* and the original file name.
- [ ] A component test covers the round trip Default → Custom → Default.
- [ ] EN and FR strings, in step (the interface's own parity test).
- [x] Neither setting name appears in any component: the picker is bound to `meta.editor === 'sound'`.
- [x] Reopening a mission with a custom sound shows *Custom*, the original file name, the size and
the reserved name; an unavailable file gets its own warning line.
- [x] `SoundPicker.test.ts` covers Default → Custom → Default, cancellation, and a refusal from the
backend (7 tests). The hidden labels are excluded from the families and from search.
- [x] EN and FR strings in step — `i18n.parity.test.ts` passes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 06 — Document the custom sounds

**Status:** todo
**Status:** done
**Lot:** FEAT-CUSTOM-BEACON-SOUNDS

## Problem
Expand All @@ -20,5 +20,6 @@ file, and both would be wrong the day this ships.

## Acceptance

- [ ] EN and FR say the same thing, FR anchors matching their EN counterparts.
- [ ] No claim about DCS audio formats that has not been verified.
- [x] EN and FR say the same thing; the FR section carries `{ #beacon-sounds }`.
- [x] No claim about DCS audio formats beyond what the code enforces: the docs say `.ogg` and that
a non-Ogg file is refused, and never assert what else DCS might accept.
4 changes: 2 additions & 2 deletions .backlog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ authored **per lot, when the lot is started** (not in batch).

| Lot | Status | Description | Branch |
|-----|--------|-------------|--------|
| `CHORE-UNTRACK-BUILT-ENGINE` | in progress | `CTLD.lua` is generated and committed anyway. `.gitignore` line 5 calls it deliberate — *"available at repo root for DCS missions"* — a bootstrap-era reason that no longer holds: nothing points at it, and **VMCT, the assumed consumer, does not** (its `vendored.yaml` pins `2.0.0-rc3` with *"re-download the CTLD.lua asset from the matching release"* and watches `github-release`). The cost is paid every time: **26 of the 28 merges touching `src/` over 30 days carried the regenerated file** — a one-megabyte generated diff nobody reviews, and a guaranteed conflict between parallel PRs. The trap, measured rather than assumed: `python-quality` runs on ubuntu and never builds the engine, so deleting the file alone would drop the suite from **262 passed** to **234 passed / 27 skipped / 1 failed** while CI stayed green. So: build the engine in that job first (`merge_CTLD.ps1` made portable — two `\` paths), fix `test_inject_into_miz` (it crashes instead of skipping), then untrack. Depends on `FEAT-DEV-BUILD-CHANNEL`, which is what keeps the engine downloadable. No history rewriting (471 blobs = 2.8 MiB packed). | `chore/untrack-built-engine` |
| `FEAT-CUSTOM-BEACON-SOUNDS` | planned | A beacon sound the Mission Maker chooses, instead of a text box naming a file the tool never installs. Grilled with Zip on 2026-08-08: custom is **derived** from `radioSound` (no second key that could disagree with the engine); a chosen file enters the mission under a **reserved name** (**ADR 0012**) because a Mission Maker whose own file is called `beacon.ogg` would otherwise see it silently overwritten; the original name survives as a schema-only label (`FIX-TOOL-I18N-LANG`'s lesson — a catalogue key would make every pre-lot configuration report a missing setting at mission start); the bytes are read at selection and live in the session, so reopening a `.miz` reinstalls them **on another machine with the original file gone**. `OggS` checked, no size cap, nothing deleted from the archive. | `feature/custom-beacon-sounds` |
| `CHORE-UNTRACK-BUILT-ENGINE` | merged (PR #110) | `CTLD.lua` is generated and committed anyway. `.gitignore` line 5 calls it deliberate — *"available at repo root for DCS missions"* — a bootstrap-era reason that no longer holds: nothing points at it, and **VMCT, the assumed consumer, does not** (its `vendored.yaml` pins `2.0.0-rc3` with *"re-download the CTLD.lua asset from the matching release"* and watches `github-release`). The cost is paid every time: **26 of the 28 merges touching `src/` over 30 days carried the regenerated file** — a one-megabyte generated diff nobody reviews, and a guaranteed conflict between parallel PRs. The trap, measured rather than assumed: `python-quality` runs on ubuntu and never builds the engine, so deleting the file alone would drop the suite from **262 passed** to **234 passed / 27 skipped / 1 failed** while CI stayed green. So: build the engine in that job first (`merge_CTLD.ps1` made portable — two `\` paths), fix `test_inject_into_miz` (it crashes instead of skipping), then untrack. Depends on `FEAT-DEV-BUILD-CHANNEL`, which is what keeps the engine downloadable. No history rewriting (471 blobs = 2.8 MiB packed). | `chore/untrack-built-engine` |
| `FEAT-CUSTOM-BEACON-SOUNDS` | merged (PR #112) | A beacon sound the Mission Maker chooses, instead of a text box naming a file the tool never installs. Grilled with Zip on 2026-08-08: custom is **derived** from `radioSound` (no second key that could disagree with the engine); a chosen file enters the mission under a **reserved name** (**ADR 0012**) because a Mission Maker whose own file is called `beacon.ogg` would otherwise see it silently overwritten; the original name survives as a schema-only label (`FIX-TOOL-I18N-LANG`'s lesson — a catalogue key would make every pre-lot configuration report a missing setting at mission start); the bytes are read at selection and live in the session, so reopening a `.miz` reinstalls them **on another machine with the original file gone**. `OggS` checked, no size cap, nothing deleted from the archive. | `feature/custom-beacon-sounds` |
| `FEAT-DEV-BUILD-CHANNEL` | merged (PR #109) | An exe to hand a tester between two releases. Zip's first idea — the exe grafting an arbitrary `CTLD.lua` into a copy of itself — **works** (verified: rc6 + 1.17 MB appended still runs) and was dropped anyway: it pairs a new engine with the exe's older schema and interface, an unsigned exe altered after the build reads as tampered, and `--version` would keep lying. The `build-exe` job already produces a complete exe from a commit in **2 min 06 s** on free public-repo runners; it only lacked a trigger. Built on every merge into `develop`, published as an artifact **and** a floating `dev` pre-release (an artifact answers `401` to an anonymous download), versioned `<ctld version>-<commit hash>`. | `feature/dev-build-channel` |
| [`FIX-FIELD-EXTRACT-CASUALTIES`](FIX-FIELD-EXTRACT-CASUALTIES/PRD.md) | merged (PR #111) | Field extraction (`embarkFromField`) returns the troop count frozen at deploy time instead of the survivor count — an undeclared legacy-parity deviation. Fix counts live DCS units (excluding `SVNT_*` servants) at extraction time; adds troop counts to the "Extract from field" menu labels; auto-despawns an orphaned mortar servant when its operator dies leaving zero real troops. Also fixed a pre-existing bug where `onUnitDead` never fired in-game. | `fix/field-extract-casualties` |
| [`FIX-MENU-DOUBLE-MULTICREW`](FIX-MENU-DOUBLE-MULTICREW/PRD.md) | merged (PR #106) | F10 menu duplication on multi-crew aircraft (CH-47 pilot + copilot); menu loss when one crew member leaves a shared group. | — |
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ Versioning follows [Semantic Versioning](https://semver.org/).
every other bridge-registered death handler in the codebase. A group that loses its last real
trooper while a mortar servant survives is now despawned reactively at that moment.

### Added — beacon sounds a Mission Maker can choose (FEAT-CUSTOM-BEACON-SOUNDS)

- **`ctld-tools` can install your own beacon sound.** The two sound settings (`radioSound`,
`radioSoundFC3`) are no longer free-text boxes naming a file the tool never installed: each gets
a **Default / Custom** picker, and a chosen `.ogg` is written into the `.miz` with its resource
key and preload trigger, exactly like the bundled ones. The file is checked for an `OggS`
signature when picked — a renamed `.mp3` would play nothing in DCS.
- **A custom sound travels in the mission.** Reopening an installed `.miz` recovers it, so the
mission can be reconfigured and reinstalled on another machine after the original file is gone.
A configuration saved as `.yaml` carries only the name, so reopening one asks for the file again
and blocks installation until it is supplied (unless the target mission already holds it).
- **Schema:** a sound chosen through the tool enters the mission under a reserved name
(`CTLD_beacon_custom.ogg` / `CTLD_beaconsilent_custom.ogg`) — see ADR 0012 — with the name it had
on disk kept as a label (`radioSoundOriginalName`, `radioSoundFC3OriginalName`, schema-only, so no
existing configuration reports a missing setting). Typing a file name by hand is unchanged and
still supported for a sound added through the Mission Editor.

### Fixed — F10 menu duplication and multi-crew menu loss (FIX-MENU-DOUBLE-MULTICREW)

- **F10 menu no longer duplicates** when a second crew member joins a multi-crew aircraft
Expand Down
4 changes: 2 additions & 2 deletions docs/mission-maker/configuration.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ aléatoire de 90–120 % de `SOLDIER_WEIGHT`, plus l'équipement et le matériel
|---|---|---|
| `enabledRadioBeaconDrop` | `true` | Autorise le déploiement de beacon |
| `deployedBeaconBattery` | `30` | Durée de vie de la batterie du beacon (minutes) |
| `radioSound` | `"beacon.ogg"` | Fichier son du beacon — l'outil le place dans le `.miz` ; si vous installez à la main, ajoutez-le vous-même, sinon les balises resteront muettes |
| `radioSoundFC3` | `"beaconsilent.ogg"` | Fichier de beacon silencieux pour les appareils FC3, même règle |
| `radioSound` | `"beacon.ogg"` | Fichier son du beacon — l'outil le place dans le `.miz` ; si vous installez à la main, ajoutez-le vous-même, sinon les balises resteront muettes. `CTLD_beacon_custom.ogg` désigne [un son que vous avez choisi](ctld-tools.fr.md#beacon-sounds) |
| `radioSoundFC3` | `"beaconsilent.ogg"` | Fichier de beacon silencieux pour les appareils FC3, même règle (personnalisé : `CTLD_beaconsilent_custom.ogg`) |

### Systèmes AA { #aa-systems }

Expand Down
4 changes: 2 additions & 2 deletions docs/mission-maker/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ randomised 90–120 % of `SOLDIER_WEIGHT`, plus kit and role-specific gear.
|---|---|---|
| `enabledRadioBeaconDrop` | `true` | Allow beacon deployment |
| `deployedBeaconBattery` | `30` | Beacon battery life (minutes) |
| `radioSound` | `"beacon.ogg"` | Beacon sound file — the tool puts it in the `.miz`; if you install by hand, add it yourself or beacons stay silent |
| `radioSoundFC3` | `"beaconsilent.ogg"` | Silent beacon file for FC3 aircraft, same rule |
| `radioSound` | `"beacon.ogg"` | Beacon sound file — the tool puts it in the `.miz`; if you install by hand, add it yourself or beacons stay silent. `CTLD_beacon_custom.ogg` means [a sound you chose](ctld-tools.md#beacon-sounds) |
| `radioSoundFC3` | `"beaconsilent.ogg"` | Silent beacon file for FC3 aircraft, same rule (custom: `CTLD_beaconsilent_custom.ogg`) |

### AA systems

Expand Down
28 changes: 28 additions & 0 deletions docs/mission-maker/ctld-tools.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,34 @@ Les entrées de catalogue — **caisses**, **groupes de troupes**, **capacités
type d'aéronef dans la liste DCS), **zones**, noms des pilotes de transport, poids des véhicules —
s'éditent sous forme de tableaux, en bas de la famille à laquelle elles appartiennent.

### Sons des balises { #beacon-sounds }

Les deux sons de balise — l'audible et le silencieux réservé aux appareils FC3 — sont le seul
endroit où un réglage est un **fichier** : ils ont donc un sélecteur plutôt qu'une zone de texte.
**Par défaut** utilise le son embarqué dans l'outil, **Personnalisé** ouvre un explorateur de
fichiers pour votre propre `.ogg`.

Le fichier que vous choisissez est lu **immédiatement** et voyage dans la mission. C'est plus
important qu'il n'y paraît : rouvrir ce `.miz` plus tard en ramène le son, donc vous pouvez
reconfigurer et réinstaller la mission **sur une autre machine, des mois après, même si vous avez
supprimé le fichier d'origine**.

Deux détails à connaître :

- **Dans la mission, le fichier est renommé** en `CTLD_beacon_custom.ogg` (ou
`CTLD_beaconsilent_custom.ogg`). Votre fichier à vous n'est pas touché, et l'outil conserve son
nom pour vous l'afficher — ce nom réservé est ce qui lui permet de distinguer votre son de celui
d'origine, même si le vôtre s'appelle lui aussi `beacon.ogg`.
- **Un `.yaml` enregistré ne peut pas porter le son**, seulement son nom. Si vous rouvrez une telle
configuration, l'outil signalera le fichier manquant et refusera d'installer tant que vous ne
l'aurez pas resélectionné — mieux vaut ça qu'une mission dont les balises sont muettes sans que
rien ne l'explique. Si la mission visée contient déjà le fichier, il n'y a rien à faire.

!!! tip "Ajouter le son vous-même"
Saisir un nom de fichier fonctionne toujours, pour un son que vous avez ajouté via l'éditeur de
mission : CTLD joue le `l10n/DEFAULT/<nom>` que vous lui indiquez. Le sélecteur est la voie
recommandée ; le champ texte reste là pour une mission câblée à la main.

### Annuler une modification { #undoing-a-change }

Tout réglage que vous modifiez est marqué **modifié**, et la famille reçoit un compteur dans la
Expand Down
27 changes: 27 additions & 0 deletions docs/mission-maker/ctld-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,33 @@ Catalogue entries — **crates**, **troop groups**, **aircraft capabilities** (p
from the DCS list), **zones**, transport pilot names, vehicle weights — are edited as tables, at the
bottom of the family they belong to.

### Beacon sounds

The two beacon sounds — the audible one and the silent one FC3 aircraft use — are the one place
where a setting is a **file**, so they get a picker rather than a text box: **Default** uses the
sound the tool carries, **Custom** opens a file browser for your own `.ogg`.

A file you choose is read **immediately** and travels inside the mission. That matters more than it
sounds: reopening that `.miz` later brings the sound back with it, so you can reconfigure and
reinstall the mission **on another machine, months later, even if you have deleted the file you
originally picked**.

Two details worth knowing:

- **Inside the mission the file is renamed** to `CTLD_beacon_custom.ogg` (or
`CTLD_beaconsilent_custom.ogg`). Your own file is untouched, and the tool keeps its name to show
you — the reserved name is what lets it tell your sound apart from the bundled one, even when
yours happens to be called `beacon.ogg` too.
- **A saved `.yaml` cannot carry the sound**, only its name. Reopen such a configuration and the
tool will say the file is missing and refuse to install until you pick it again — better than a
mission whose beacons are silent for reasons nobody can see. If the mission you are installing
into already holds the file, there is nothing to do.

!!! tip "Adding the sound yourself"
Typing a file name still works, for a sound you added through the Mission Editor: CTLD plays
whatever `l10n/DEFAULT/<name>` you point it at. The picker is the supported path; the text
field remains for a mission wired by hand.

### Undoing a change

Any setting you change is marked **changed**, and the family gets a counter in the left column, so
Expand Down
Loading