fix(troop): field extraction reflects real casualties (FIX-FIELD-EXTRACT-CASUALTIES) - #111
Merged
Merged
Conversation
…e count embarkFromField now counts a dropped group's currently alive DCS units (excluding SVNT_* mortar servants) instead of preferring the headcount frozen at deploy time (stored.total). Ten troops dropped, three killed, seven re-embarked - matching the legacy monolith's live-unit-count extraction behavior instead of the undeclared parity deviation. _findNearestDropped and _findAllNearbyDropped now exclude a dropped group reduced to zero real troops (mortar operator dead, servant still standing) from extraction candidates. Ticket: FIX-FIELD-EXTRACT-CASUALTIES/01 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bash(*) already covers every Bash-tool invocation; the PowerShell tool had no rule at all and re-prompted on every retry, including repeated busted test runs this session. Adds PowerShell(busted*) only - a read-only test runner, not a shell wildcard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The single-group direct button and the multi-group submenu now show each nearby dropped group's current logical troop count alongside its name (and distance, in the submenu), reusing the logical-count helper from ticket 01. Lets a pilot choose which group to extract based on what it will actually cost in capacity. New i18n keys synced and translated (EN/FR/ES/KO); troop-transport docs (EN+FR) updated to the new label format. Ticket: FIX-FIELD-EXTRACT-CASUALTIES/02 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The narrower PowerShell(busted*) rule still left every other PowerShell invocation (build script, ad-hoc checks) prompting for approval mid-session. Mirrors the existing Bash(*) posture already established for this project. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…servant onUnitDead was registered as a raw S_EVENT_DEAD handler (bridge:register(tm, world.event.S_EVENT_DEAD, "onUnitDead")) but written to expect a plain unit name string. Every other bridge-registered death handler in this codebase (onTransportDead, CTLDVehicleSpawner:onDead, CTLDZoneManager:onDead, CTLDFOBManager:onDead) unwraps event.initiator first - onUnitDead did not, so it never matched a real dead unit in a live mission. This silently disabled its own bookkeeping and the JTAC deregistration-on-death path that depends on it. Fixed to unwrap event.initiator, matching the established pattern. Updated the one live-DCS scenario that called onUnitDead directly with a bare name. With that fixed, onUnitDead now also despawns a dropped group's residual DCS units when a death brings its logical troop count to zero while it still has units standing (a mortar servant orphaned by its operator's death) - it no longer lingers on the battlefield unable to be extracted. Ticket: FIX-FIELD-EXTRACT-CASUALTIES/03 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reviewer's GuideField extraction now uses a live logical troop count (excluding mortar servants) for capacity and weight, the F10 "Extract from field" menu shows per-group troop counts, and the onUnitDead handler is fixed to process real DCS death events and despawn orphaned servant-only groups, with tests, docs, changelog, backlog, and i18n updated accordingly. Sequence diagram for field extraction using live logical troop count and updated menu labelssequenceDiagram
actor Pilot
participant F10Menu as F10Menu
participant CTLDTroopManager as CTLDTroopManager
participant CTLDTroopGroup as CTLDTroopGroup
Pilot->>F10Menu: Open Embark_Extract_Troops
F10Menu->>CTLDTroopManager: refreshMenuSection(playerObj)
CTLDTroopManager->>CTLDTroopManager: _findAllNearbyDropped(unit, coalition)
loop for each nearby group
CTLDTroopManager->>CTLDTroopManager: _countLogicalUnits(group)
CTLDTroopManager-->>F10Menu: logicalCount for menu label
end
F10Menu-->>Pilot: Show Extract commands with troop counts
Pilot->>F10Menu: Select Extract for group
F10Menu->>CTLDTroopManager: embarkFromField(unit)
CTLDTroopManager->>CTLDTroopManager: _findNearestDropped(unit, coalition)
CTLDTroopManager->>CTLDTroopManager: _countLogicalUnits(nearest.group)
CTLDTroopManager->>CTLDTroopGroup: getAliveCount()
CTLDTroopManager-->>Pilot: Troops embarked based on live logicalCount
Sequence diagram for onUnitDead handling and orphan servant cleanupsequenceDiagram
participant DCSWorld as DCSWorld
participant CTLDDCSEventBridge as CTLDDCSEventBridge
participant CTLDTroopManager as CTLDTroopManager
participant CTLDTroopGroup as CTLDTroopGroup
participant CTLDJTACManager as CTLDJTACManager
participant DCSGroup as DCSGroup
DCSWorld-->>CTLDDCSEventBridge: S_EVENT_DEAD(event)
CTLDDCSEventBridge->>CTLDTroopManager: onUnitDead(event)
CTLDTroopManager->>DCSWorld: event.initiator:getName()
CTLDTroopManager->>CTLDTroopManager: _findGroupByAliveUnit(unitName)
CTLDTroopManager-->>CTLDTroopGroup: grp
CTLDTroopManager->>CTLDTroopGroup: _removeDeadUnit(unitName)
alt [unit was JTAC]
CTLDTroopManager->>CTLDJTACManager: deregisterJTAC(unitName)
end
alt [grp.state == DEPLOYED and grp:getLogicalCount() == 0 and grp:getAliveCount() > 0]
CTLDTroopManager->>CTLDTroopGroup: getLogicalCount()
CTLDTroopManager->>CTLDTroopGroup: getAliveCount()
CTLDTroopGroup->>DCSGroup: destroy()
CTLDTroopManager->>CTLDTroopManager: _removeFromDropped(grp.coalitionId, groupName)
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- You now have two separate implementations of the “logical troop count” filter (
CTLDTroopManager:_countLogicalUnitsandCTLDTroopGroup:getLogicalCount); consider consolidating this into a single shared helper to avoid future divergence if the naming convention or filter rules change. - The servant exclusion pattern uses
name:match("^SVNT")while the spec describes servants asSVNT_*; tightening this to match the full prefix (e.g.^SVNT_) would reduce the chance of accidentally excluding unrelated units whose names happen to start withSVNT. - In
onUnitDead, failures ininitiator:getName()are silently ignored; adding a debug log in thenot ok or not unitNamebranch would make it easier to diagnose event-shape issues or unexpected nil initiators in live missions.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- You now have two separate implementations of the “logical troop count” filter (`CTLDTroopManager:_countLogicalUnits` and `CTLDTroopGroup:getLogicalCount`); consider consolidating this into a single shared helper to avoid future divergence if the naming convention or filter rules change.
- The servant exclusion pattern uses `name:match("^SVNT")` while the spec describes servants as `SVNT_*`; tightening this to match the full prefix (e.g. `^SVNT_`) would reduce the chance of accidentally excluding unrelated units whose names happen to start with `SVNT`.
- In `onUnitDead`, failures in `initiator:getName()` are silently ignored; adding a debug log in the `not ok or not unitName` branch would make it easier to diagnose event-shape issues or unexpected nil initiators in live missions.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
…o-ops Addresses sourcery-ai review on PR #111: - _syncFromDCSGroup, CTLDTroopManager:_countLogicalUnits, and CTLDTroopGroup:getLogicalCount each re-implemented the same "^SVNT" servant-name check. Extracted into one shared _isServantUnitName predicate used by all three. - onUnitDead now logs (DEBUG) when it skips a nil/malformed event instead of failing silently, to help diagnose event-shape issues in a live mission. Did not tighten the match to "^SVNT_": CTLDObjectRegistry.spawnObject names servant units "SVNT-<uid>" (hyphen, via string.format("%s-%d", namePrefix, uid)) - requiring a trailing underscore would stop matching real servant units and reintroduce the bug this lot fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Collaborator
Author
|
Thanks for the review! Addressed in 1d6c247:
🤖 Addressed by Claude Code |
FullGas1
added a commit
that referenced
this pull request
Aug 8, 2026
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
embarkFromField) now counts live survivors, not the headcount frozen at deploy time — matching the legacy monolith's live-unit-count behavior instead of an undeclared parity deviation. Ten troops dropped, three killed, seven re-embarked. Cosmetic mortar-servant units (SVNT_*) stay excluded from the count; the mortar unit itself always counts.Extract: Bravo (7 troops)/Bravo (7 troops, 25m)), so a pilot can choose which group to extract based on what it will actually cost in capacity. A group reduced to zero real troops no longer appears.onUnitDeadis registered as a rawS_EVENT_DEADbridge handler but was written to expect a plain unit-name string instead of unwrappingevent.initiator(unlike every other death handler in the codebase) — so it never matched a real dead unit in a live mission, silently disabling both its own bookkeeping and JTAC deregistration-on-death. Fixed, and used to add the ticket's own reactive cleanup: a dropped group reduced to zero real troops while a mortar servant survives (operator killed, servant alive) is now despawned immediately instead of lingering on the battlefield.Details
.backlog/FIX-FIELD-EXTRACT-CASUALTIES/PRD.md+ 3 tickets (dependency order, all delivered in this PR).docs/pilot/troop-transport.md+.fr.mdupdated to the new label format.CHANGELOG.md [Unreleased]updated.tests/dcs/pilotPassive/scenarioTroopsFullCycle_v2.luare-run via dcs-bridge after adapting itsonUnitDeadcall to the corrected signature — step 6 (JTAC death /onUnitDeadchecks, F-T6.3-F-T6.7) passes cleanly. An unrelated, pre-existing failure surfaced at step 7 (JTAC target reacquisition, F-T7.2) — flagged separately, not touched here.Test plan
busted tests/ci/— 1297/1297 passingmerge_CTLD.ps1) — clean, i18n dicts in syncluacheck --config .luacheckrc src/— not available locally, relying on CI🤖 Generated with Claude Code
Summary by Sourcery
Align field troop extraction with live survivor counts, surface accurate troop numbers in extraction menus, and fix event handling so death-driven cleanup and JTAC deregistration behave correctly.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests:
Chores: