Skip to content

feat(zones): scripted API to add a pickup TRZ on any named object - #129

Merged
FullGas1 merged 8 commits into
developfrom
feature/troop-zone-scripted-api
Aug 25, 2026
Merged

feat(zones): scripted API to add a pickup TRZ on any named object#129
FullGas1 merged 8 commits into
developfrom
feature/troop-zone-scripted-api

Conversation

@FullGas1

@FullGas1 FullGas1 commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • CTLDZoneManager:createTroopZoneAtObject(objectName, trzName) — adds a pickup-capable TRZ_ troop zone at runtime on any named DCS object (Mission Editor trigger zone, unit, static, group, or airbase/FARP), any time after ctld.initialize(). Generalizes a FOB-only idea from dev/roadmap.md (grilled 2026-08-26, widened to "any named object").
  • trzName is a full TRZ_<name>_<coalition>_<stock>_<flag>_<target> string, parsed via parseTRZ (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.
  • 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. 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).
  • No new teardown/lookup function: the existing removeExtractZone/getTroopZone already work unchanged for a zone created this way.
  • Live-tested against a running mission — including the F10 menu appearing after a refresh, and a follow-up roadmap note on a pre-existing, cross-cutting gap this surfaced (dynamic zone creation never proactively refreshes an already-parked player's menu — confirmed the same gap already exists for 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 in tests/ci/unit/troop_zone_scripted_api_spec.lua)
  • Live DCS test via dcs-bridge: createTroopZoneAtObject("apc-1", "TRZ_apctest_B_50_nil_0") — confirmed hasPickup=true, radius=200, coalition=BLUE, pickMaxStock=50, linkedUnit anchoring (not a static center), zone present in getTroopZonesForCoalition, 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 -p lint (not installed locally — relying on CI)
  • CHANGELOG.md [Unreleased] updated
  • Developer docs (EN+FR): docs/developer/api-reference.md/.fr.md, docs/developer/subsystems/zones.md/.fr.md
  • .backlog/README.md index 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:

  • Add a scripted API for creating pickup-capable troop zones on named trigger zones, units, statics, groups, and airbases/FARPs at runtime.

Enhancements:

  • Expose TRZ name parsing publicly for reuse by both runtime creation and existing zone discovery.
  • Anchor dynamically created zones to movable objects while retaining fixed positioning for airbases/FARPs and providing a default radius where needed.

Documentation:

  • Document the new runtime pickup-zone API and public TRZ parser in English and French developer and mission-maker guides.

Tests:

  • Add coverage for object resolution, anchoring, radius behavior, validation, duplicate handling, lookup, and removal of scripted troop zones.

Chores:

  • Record the completed feature and the related dynamic-menu-refresh limitation in project backlog and roadmap documentation.

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.
@FullGas1
FullGas1 requested a review from davidp57 as a code owner August 25, 2026 23:31
@sourcery-ai

sourcery-ai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR promotes the existing TRZ parser and adds CTLDZoneManager:createTroopZoneAtObject, which parses a full TRZ name, resolves a named DCS object, creates a pickup-capable troop zone with appropriate radius and movement anchoring, and integrates it with existing zone lookup/removal behavior. The implementation is covered by focused unit tests, documented in English and French, and accompanied by changelog/backlog/roadmap updates.

Sequence diagram for creating a scripted pickup troop zone

sequenceDiagram
    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
Loading

Flow diagram for resolving and anchoring a scripted troop zone

flowchart 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]
Loading

File-Level Changes

Change Details Files
Expose the TRZ parser for reuse by runtime callers while preserving existing parsing behavior.
  • Rename _parseTRZ to public parseTRZ.
  • Update initialization discovery and validation callers plus parser tests.
src/CTLD_zone.lua
tests/ci/unit/zone_manager_spec.lua
docs/developer/api-reference.md
docs/developer/api-reference.fr.md
docs/developer/subsystems/zones.md
docs/developer/subsystems/zones.fr.md
CHANGELOG.md
Add runtime creation of pickup-capable troop zones anchored to named DCS objects.
  • Resolve trigger zones, units, statics, groups, and airbases/FARPs in priority order.
  • Reuse TRZ metadata for coalition, stock, objectives, and flags; reject malformed names, missing objects, and duplicate zones.
  • Anchor moving objects through dcsName or linkedUnit, use trigger-zone radii, and apply a 200 m fallback for other object types.
  • Register zones in the existing troop-zone table so current lookup and removal APIs work unchanged.
src/CTLD_zone.lua
tests/ci/unit/troop_zone_scripted_api_spec.lua
tests/ci/helpers/dcs_stubs.lua
CHANGELOG.md
Document the new scripted API and record the broader feature and discovered menu-refresh limitation.
  • Add English and French API-reference and subsystem documentation with usage examples and anchoring/radius behavior.
  • Add backlog PRD, tickets, and index entry.
  • Mark the former FOB-only roadmap idea as generalized and document the pre-existing lack of proactive F10 refresh for already-parked players.
docs/developer/api-reference.md
docs/developer/api-reference.fr.md
docs/developer/subsystems/zones.md
docs/developer/subsystems/zones.fr.md
.backlog/FEAT-TROOP-ZONE-SCRIPTED-API/PRD.md
.backlog/FEAT-TROOP-ZONE-SCRIPTED-API/tickets/01-parsetrz-visibility-promotion.md
.backlog/FEAT-TROOP-ZONE-SCRIPTED-API/tickets/02-createtroopzoneatobject.md
.backlog/FEAT-TROOP-ZONE-SCRIPTED-API/tickets/03-developer-docs.md
.backlog/README.md
dev/roadmap.md
Add focused unit coverage for object resolution, anchoring, validation, duplicate protection, lookup, and teardown.
  • Stub airbase lookup in the DCS test helpers.
  • Verify all supported object kinds, movement tracking, default radii, pickup state, and failure paths.
tests/ci/unit/troop_zone_scripted_api_spec.lua
tests/ci/helpers/dcs_stubs.lua

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/CTLD_zone.lua
-- @param trzName string full TRZ_<name>_<coalition>_<stock>_<flag>_<target> name
-- @return boolean
function CTLDZoneManager:createTroopZoneAtObject(objectName, trzName)
local parsed, err = self:parseTRZ(trzName)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@FullGas1
FullGas1 merged commit 2f9a867 into develop Aug 25, 2026
9 checks passed
@FullGas1
FullGas1 deleted the feature/troop-zone-scripted-api branch August 26, 2026 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant