feat(zones): scripted API to add a pickup TRZ on any named object - #129
Conversation
Scripted API to add a pickup-capable TRZ_ troop zone on any named DCS object (zone, unit, static, group, or airbase/FARP) after init, generalizing a FOB-only idea from dev/roadmap.md (grilled 2026-08-26). Grill-with-docs session found no CONTEXT.md/ADR update needed - the existing Anchor/Anchored-zone concept already covers the conditional anchoring decision.
… (FEAT-TROOP-ZONE-SCRIPTED-API ticket 01) Visibility-only rename, no behavior change: the upcoming scripted troop-zone API (ticket 02) needs the same TRZ_ string parser Mission-Editor discovery already uses, so the two paths can never disagree on what a TRZ_ name means. Both existing call sites (_discoverTRZ, _validateZoneNames) updated. 1312 busted tests green.
…-SCRIPTED-API ticket 02) Scripted way to add a pickup-capable TRZ_ troop zone on any named DCS object (Mission Editor zone, unit, static, group, or airbase/FARP) any time after ctld.initialize(). trzName is parsed via the existing TRZ_ convention (coalition/stock/objective flag/target), so it never disagrees with an editor-placed zone meaning the same string. The new zone anchors to the resolved object when it can move (dcsName for a Moving Zone, linkedUnit for a unit/static/group); an airbase/FARP match is fixed, with a 200m default radius for every non-zone match (no radius accessor exists on a unit/static/group/ airbase in this codebase or verifiably in the DCS API). Reuses the existing removeExtractZone/getTroopZone as-is - no new teardown or lookup function needed. New tests/ci/unit/troop_zone_scripted_api_spec.lua (12 cases: each resolution kind, anchor tracking, all three failure modes, removal and lookup reuse). Adds an Airbase stub to tests/ci/helpers/ dcs_stubs.lua (none existed). 1324 busted tests green (was 1312).
…ZONE-SCRIPTED-API ticket 03) EN+FR, in the same four files createExtractZone and registerFOBAsLogistic already live in (neither appears under docs/mission-maker/): docs/developer/api-reference.md/.fr.md (flat method table) and docs/developer/subsystems/zones.md/.fr.md (zone- type table row + runtime-registration snippet, including a FARP example and a cross-link to the existing TRZ naming-convention section). Closes FEAT-TROOP-ZONE-SCRIPTED-API (3/3 tickets).
Used to live-verify FEAT-TROOP-ZONE-SCRIPTED-API's createTroopZoneAtObject against a named unit (as opposed to a trigger zone, static, group, or airbase) - confirmed hasPickup, default 200m radius, linkedUnit anchoring, and F10 menu visibility.
…E-SCRIPTED-API Live-tested createTroopZoneAtObject on a player already parked at the target unit: their F10 menu stayed empty until a manual refresh, since CTLDTroopManager only rebuilds "Troop Commands" on S_EVENT_LAND/TAKEOFF, never on zone creation. Confirmed the same gap already exists for registerFOBAsLogistic (OnLogisticZoneUpdated is published but nothing in src/ subscribes to it) - a pre-existing, cross-cutting characteristic, not a regression from this lot.
Reviewer's GuideThis PR promotes the existing TRZ parser and adds Sequence diagram for creating a scripted pickup troop zonesequenceDiagram
participant MissionMaker
participant CTLDZoneManager
participant ObjectResolver
participant DCSObject
participant CTLDTroopZone
MissionMaker->>CTLDZoneManager: createTroopZoneAtObject(objectName, trzName)
CTLDZoneManager->>CTLDZoneManager: parseTRZ(trzName)
CTLDZoneManager->>ObjectResolver: resolve objectName
ObjectResolver->>DCSObject: getByName(objectName)
DCSObject-->>ObjectResolver: object and position
ObjectResolver-->>CTLDZoneManager: center, radius, anchor
CTLDZoneManager->>CTLDTroopZone: new(parsed fields, center, radius, anchor)
CTLDTroopZone-->>CTLDZoneManager: pickup-capable zone
CTLDZoneManager-->>MissionMaker: true
Flow diagram for resolving and anchoring a scripted troop zoneflowchart TD
A[createTroopZoneAtObject] --> B[parseTRZ]
B -->|invalid name| X[Return false]
B --> C{Resolve named object}
C -->|Trigger zone| D[Use zone radius and dcsName anchor]
C -->|Unit, static, or group| E[Use 200 m radius and linkedUnit anchor]
C -->|Airbase or FARP| F[Use 200 m radius and fixed center]
C -->|No match| X
D --> G[Create CTLDTroopZone]
E --> G
F --> G
G --> H[Store in _troopZones]
H --> I[Use existing getTroopZone or removeExtractZone]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/CTLD_zone.lua" line_range="1570" />
<code_context>
+-- @param trzName string full TRZ_<name>_<coalition>_<stock>_<flag>_<target> name
+-- @return boolean
+function CTLDZoneManager:createTroopZoneAtObject(objectName, trzName)
+ local parsed, err = self:parseTRZ(trzName)
+ if not parsed then
+ ctld.utils.log("ERROR", "CTLDZoneManager:createTroopZoneAtObject — invalid TRZ_ name '%s': %s",
</code_context>
<issue_to_address>
**issue (bug_risk):** `createTroopZoneAtObject(nil, ...)` or a non-string `trzName` raises inside `parseTRZ`/`_split` instead of returning `false` and logging an invalid-name error. The documented failure contract covers an unparseable TRZ name, but the new entry point passes the value directly into string operations without validating its type.
**Triggers:** When a mission script omits an argument or passes a non-string value.
**Suggested fix:** Validate `type(trzName) == "string"` and `type(objectName) == "string"` before parsing and return `false` with the existing error log.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| -- @param trzName string full TRZ_<name>_<coalition>_<stock>_<flag>_<target> name | ||
| -- @return boolean | ||
| function CTLDZoneManager:createTroopZoneAtObject(objectName, trzName) | ||
| local parsed, err = self:parseTRZ(trzName) |
There was a problem hiding this comment.
issue (bug_risk): createTroopZoneAtObject(nil, ...) or a non-string trzName raises inside parseTRZ/_split instead of returning false and logging an invalid-name error. The documented failure contract covers an unparseable TRZ name, but the new entry point passes the value directly into string operations without validating its type.
Triggers: When a mission script omits an argument or passes a non-string value.
Suggested fix: Validate type(trzName) == "string" and type(objectName) == "string" before parsing and return false with the existing error log.
…EN+FR) Mirrors the existing "Deactivating and reactivating a logistic zone" DO SCRIPT precedent already in the mission-maker guide.
Summary
CTLDZoneManager:createTroopZoneAtObject(objectName, trzName)— adds a pickup-capableTRZ_troop zone at runtime on any named DCS object (Mission Editor trigger zone, unit, static, group, or airbase/FARP), any time afterctld.initialize(). Generalizes a FOB-only idea fromdev/roadmap.md(grilled 2026-08-26, widened to "any named object").trzNameis a fullTRZ_<name>_<coalition>_<stock>_<flag>_<target>string, parsed viaparseTRZ(promoted from private_parseTRZ— visibility-only rename, no behavior change), so coalition/pickup stock/objective flag/target come from the same convention as an editor-placed zone.dcsNamefor a Moving Zone,linkedUnitfor a unit/static/group; an airbase/FARP match is fixed. Non-zone matches get a new 200 m default radius (no radius accessor exists on a unit/static/group/airbase anywhere in this codebase or verifiably in the DCS API).removeExtractZone/getTroopZonealready work unchanged for a zone created this way.registerFOBAsLogistic, not a regression from this PR).Backlog
.backlog/FEAT-TROOP-ZONE-SCRIPTED-API/— PRD + 3 tickets, all done. Grilled with grill-with-docs (no CONTEXT.md/ADR update needed: applies the existing "Anchor"/"Anchored zone" concept to a new call site).Test plan
busted tests/ci— 1324 passing (was 1312; +12 new intests/ci/unit/troop_zone_scripted_api_spec.lua)createTroopZoneAtObject("apc-1", "TRZ_apctest_B_50_nil_0")— confirmedhasPickup=true,radius=200,coalition=BLUE,pickMaxStock=50,linkedUnitanchoring (not a static center), zone present ingetTroopZonesForCoalition, and "Load from TRZ_apctest" visible in the F10 menu after a refresh.luacheck --config .luacheckrc src/(not installed locally — relying on CI)luac5.1 -plint (not installed locally — relying on CI)CHANGELOG.md[Unreleased]updateddocs/developer/api-reference.md/.fr.md,docs/developer/subsystems/zones.md/.fr.md.backlog/README.mdindex line updated🤖 Generated with Claude Code
Summary by Sourcery
Enable mission scripts to create pickup-capable troop zones on any named DCS object after initialization using the existing TRZ naming convention.
New Features:
Enhancements:
Documentation:
Tests:
Chores: