Per-type radio-preset projection¶
Audience: developers. How the build projects the channel lists (
channel_lists, the "plan mode" ofpresets.yaml) onto each aircraft type's physical radios, honouring its hardware quirks (channel 0, reserved slots, hardcoded special channels, key-based mapping…), plus channel priority/colour and per-type kneeboards (ADR 0012).Architecture decision: ADR 0010 (extends ADR 0003). Upstream analysis: exploration. Mission-maker side:
presets.yamlformat.
The model in brief¶
The mission-maker declares, once per coalition, channel lists by functional
radio role (channel_lists) — not by physical radio. At build time, a
packer reads each type's real radios from the DCS specs and projects
each list onto the matching radio, applying the type's layout rule.
channel_lists (mission-maker, by role)
│
▼
packer ──reads──► dcs-radio-specs.yaml (per-radio bands/modulation, auto-generated)
│ ──reads──► dcs-radio-layouts.yaml (per-type quirks, hand-maintained)
▼
PresetDefinition ──► existing injector ──► unit["Radio"] + kneeboard
The packer produces PresetDefinition objects and reuses the existing injector,
band validation and kneeboard generation unchanged. An explicit assignment in
presets_assignments (legacy format) stays the manual-override path: it
always wins over the packer.
Source files¶
| File | Role |
|---|---|
dcs-radio-layouts.yaml |
Source of truth for per-type quirks. Hand-maintained. Every primitive is documented in the file header. |
dcs-radio-specs.yaml |
Per-physical-radio frequency ranges / modulation. Auto-generated (poetry run update-radio-specs), never hand-edit. |
presets_manager.py |
The packer and its rules: _assign_roles_by_position (band-based default), _check_layout_radio_count (count guard), _channel_list_for_role (role resolution). |
Radio roles (fixed vocabulary)¶
The role carries the frequency band; a channel lacking that band is dropped from
the list (reported under validate, silent under build).
| Role | Meaning |
|---|---|
primary_1 |
1st V/UHF radio (UHF band) |
primary_2 |
2nd V/UHF radio (VHF band); also the warbirds' single radio |
fm_substitute |
FM as a 2nd radio (helicopters with a single V/UHF) |
fm_supplement |
FM atop two V/UHF radios (attack aircraft) |
fm_secondary |
a 2nd supplemental FM (e.g. OH-58D); defaults to a copy of fm_supplement |
Band-based default (types with no layout entry)¶
A type absent from dcs-radio-layouts.yaml goes through the
_assign_roles_by_position default: each physical radio is classified by its
frequency ranges, radios unambiguously dedicated to one sub-band are assigned
directly — so a deliberately inverted order (the A-10's VHF-first radio 1)
resolves without an explicit entry — and physical order is only a fallback for
genuinely ambiguous combo radios (the F/A-18's two identical ARC-210s).
This is why A-10C and A-10C_2 are deliberately absent from the layout file: their VHF-first ordering and the ARC-210's wide-band ambiguity are already resolved correctly by the default.
Quirk primitives¶
Declared per physical radio (1-based index, specs / .miz order) in
dcs-radio-layouts.yaml:
| Primitive | Effect |
|---|---|
rotate_last_to_head: true |
Channel-0 rotation: the list's last entry moves to the head (slot 1), the rest follows in 2..N. The kneeboard numbers the slots from 00, as the selector does (slot 13 = channel 12). |
keyed_groups: {block_size, bases} |
Key-based mapping (ADR 0012): several roles share one radio, each channel placed by its key (Group = base + key, slot = ((Group − min_base) mod block_size) + 1). Gaps preserved, overflow wraps to slot 1 (the Viggen's Group 100 recycles primary_2's 20th channel). A key beyond the role's share (block_size / number_of_roles) is dropped + WARNING. |
trailing_specials: [{…}, …] |
Special channels at the tail. Each entry is either an airframe constant {freq, mod} (e.g. the Viggen's E/F/G) or plan-sourced {priority: N} — the frequency comes from the channel tagged priority: N (always AM; slot left empty if absent). Optional label = pilot name (Sp1, H…). Maker-overridable via presets_assignments. |
reserved_head_slots: [idx, …] |
Reserved head slot(s) fed by a list index ("M" / "C" slot). [20] = last entry moved to the head; [1, 20] = first duplicated at the head then last moved. An index beyond a shorter list takes the list's last entry: the slot exists in the cockpit whatever the list's length. head_labels: [C, M] names the head slots on the kneeboard; the presets after them are numbered from 01. Mutually exclusive with rotate_last_to_head. |
capacity: <int> |
Radio's physical capacity: the excess is truncated at the tail (silent, debug log). |
Composition order when several primitives coexist on one radio: base content
(keyed_groups or the plain role list) → (plain-list path only) rotation or
reserved head slots (mutually exclusive) → trailing_specials appended (at the
block boundary for keyed_groups, else right after the content) → capacity
truncation.
The packer also checks the declared radio count against the real specs and logs
a WARNING on drift (_check_layout_radio_count) — useful after a DCS patch
changes an aircraft's radio count.
Types with a quirk (current state)¶
These types have an explicit entry in dcs-radio-layouts.yaml; all others go
through the band-based default.
| Type | Radios | Quirk |
|---|---|---|
| Mi-24P | 2 (R-863 V/UHF + R-828 FM) | Radio 1 primary_1 with channel-0 rotation; radio 2 fm_substitute, standard. |
| CH-47Fbl1 | 3 (ARC-186 + ARC-164 + ARC-201D) | Radio 1 fm_substitute with rotation (a secondary AM band fools the band classifier → explicit entry required); radio 2 primary_1 with rotation; radio 3 fm_secondary. |
| OH58D | 4 (UHF, VHF, FM1, FM2) | Radios 1-2: reserved "M" slot (reserved_head_slots: [20]). Radios 3-4: "C" + "M" slots ([1, 20]). |
| AJS37 (Viggen) | 1 (V/UHF, 47 slots) | keyed_groups (Groups 100-139 block: primary_1 keys 1-20 → 101-120, primary_2 keys 1-20 → 121-139 + the Group-100 recycle) + 7 trailing_specials at slots 41-47: Sp1/Sp2/Sp3 + H plan-sourced (priority 1-4), E/F/G hardcoded. Deliberately breaks ADR 0003 iso-functionality (slot 1 = primary_2's 20th, not a dummy). |
The slot-by-slot detail of each type (with comments explaining every choice)
lives directly in
dcs-radio-layouts.yaml.
Channel priority and colour (ADR 0012)¶
Two optional attributes the mission-maker sets on a plan entry (channel_lists):
priority: N— importance rank. Universal: the channel is highlighted on every kneeboard (Pnmarker + orange Name/Freq cells). Viggen routing: priorities 1-4 fill FR22 Special 1/2/3 + FR24 H (band from the role:primary_1→UHF,primary_2→VHF; always AM). One entry per value;priorityis not read fromchannels_collection.color: <name|#RRGGBBAA>— visually groups channels by filling the CH cell (text auto-contrasted). Accepted inchannel_listsandchannels_collection(the plan entry overrides the definition). An unknown name →WARNING+ uncoloured channel. Presentation only; no effect on packing.
Per-type kneeboards (ADR 0012)¶
Ten Flaming Cliffs aircraft have no settable radio and get a kneeboard only: see
kneeboard_only.
The build renders one kneeboard per injected (coalition, type), dropped in
that type's DCS folder: KNEEBOARD/<type>/IMAGES/presets.png (coalition-suffixed
only when the same type flies for both sides). This replaces the old generic
KNEEBOARD/IMAGES/ folder.
The page is always 1536 × 2304, a 2:3: DCS stretches the image to fill the kneeboard window (KNEEBOARD/indicator/init.lua, texture coordinates 0..1), and the cockpit kneeboard measures 0.142 × 0.214.
The title is the type's readable name (the specs name). Each radio is a block titled with its position and the specs set (COMM 1: ARC-210 reads 1. COMM 1 · ARC-210); kneeboard_title in dcs-radio-layouts.yaml replaces it (the AJS-37: FR22 / FR24).
A block stops at its last slot, labels included, and the blocks flow into two columns (_paginate_blocks): a block moves whole to the next column when it fits there, only a block taller than a column is split, and what overflows goes to presets_2.png.
For the AJS-37, the CH column shows pilot labels (100-139 then Sp1/Sp2/Sp3/E/F/G/H).
Every mission with kneeboards also gets KNEEBOARD/IMAGES/00-veaf-calibration.png, the test pattern that puts a resized window back to 2:3.
Adding or fixing a type¶
- Identify the aircraft's physical radios in
dcs-radio-specs.yaml(index, bands) — see also the readable tabledcs-radio-specs.md. - Check whether the band-based default is enough (often yes). If not, add an
entry in
dcs-radio-layouts.yamlwith the index → role mapping and the needed primitives; comment each radio (name + band). - Cover the behaviour with a test in
test/python/presets_injector/(layout fidelity, capacity, the AJS-37 case…).