The Tactical Units Tracking System exports all visible units from DCS World (aircraft, ground forces, ships, etc.), sends them encrypted to the Android app, and displays them as markers on the map.
Function collect_nearby_units() collects:
- All visible units from
LoGetWorldObjects() - Position (lat/lon/alt), heading, speed
- Category (aircraft, helicopter, ground, ship, structure)
- Coalition (0 = neutral, 1 = red, 2 = blue)
- Distance and bearing to the player
Integration:
- Called automatically every frame
- Data is added to the
nearbyUnitsarray inside the FlightData JSON - Encrypted transmission via UDP (ECDH + AES-GCM)
TacticalUnitEntity:
- Stores the current status of each unit
isActiveflag: 1 = visible, 0 = visual contact lost- Timestamps:
firstSeenAt,lastSeenAt,lastUpdateAt
TacticalUnitHistoryEntity:
- Stores position history for track replay
- Foreign key to
TacticalUnitEntity - Automatic cascade deletion
Migration v6 → v7:
- New tables
tactical_unitsandtactical_unit_history - Indexes for performance (dcs_id, category, coalition, is_active)
Function processNearbyUnits():
- Compare received units with the DB
- New units: INSERT + history entry
- Known units: UPDATE position + history entry
- Lost units: Mark as
isActive = 0(remain in DB)
Lifecycle:
- Runs automatically on every received FlightData packet
- Uses coroutines for DB operations (non-blocking)
TacticalUnitsRepository:
- High-level API for unit management
- Filtering system (category, coalition, active/inactive)
- Statistics (counts per category and coalition)
- Cleanup functions (old inactive units, old history)
TacticalUnitsViewModel:
- UI state management
- Reactive flows for the units list
- Filter logic (categories, coalitions, search)
TacticalUnitsListScreen:
- List of all tracked units
- Search (name, group)
- Filter dialog (category, coalition, active/inactive)
- Unit cards with status badges
- Statistics display
FAB Button:
- TrackChanges icon (radar-like)
- Position:
defaultX=0.95f,defaultY=0.15f - Integrated in the MapViewer FAB overlay
-
DCS:
- Copy
Export.luatoSaved Games/DCS/Scripts/ - Start
forward_parsed_udp.py
- Copy
-
Android App:
- Enable DataPad (Settings)
- Perform the ECDH handshake
- Open the Tactical Units list via the FAB
- DCS collects units →
Export.luawrites JSON - Python forwards → encrypted UDP packets
- App receives → DataPadManager processes the data
- DB stores →
TacticalUnitEntity+ history entries - Map shows → markers on the map (via tactical icons: enemy = red, BLUFOR = blue, civilian = yellow)
- List shows →
TacticalUnitsListScreen
- Categories: aircraft, helicopter, ground, ship, structure
- Coalitions: Neutral, Red, Blue
- Status: Active (visible), Lost (visual contact lost)
- Search: by name or group
- Auto-Cleanup: old inactive units (7 days)
- History: remove old history entries (14 days)
- Manual: "Delete All" in the UI
- ECDH Key Exchange: secure key agreement
- AES-GCM Encryption: all data encrypted
- Device Authorization: only authorized devices (see
authorized_devices.json) - DoS Protection: rate limiting (per IP, global)
-
The app ships a prepackaged map database (
assets/databases/map_data.db) which may be updated between releases. The app detects a newer asset DB by checking the SQLitePRAGMA user_version. -
The
DatabaseUpdateManager(seeapp/src/main/java/com/example/checklist_interactive/data/tactical/DatabaseUpdateManager.kt) performs the check on startup usingcheckForDatabaseUpdate()and exposes state for the UI:assetDbVersion,currentDbVersion, andshowUpdateDialog. -
When a newer asset version is detected (and a previous tracked version exists), the app shows a
DatabaseUpdateDialog(seeapp/src/main/java/com/example/checklist_interactive/ui/tactical/DatabaseUpdateDialog.kt) offering three options:- Merge: import new locations from the asset DB while preserving user data (non-destructive).
- Clean: perform a clean import — delete the installed internal DB and recreate it from the asset (destructive but safe when explicitly requested).
- Skip: dismiss the dialog and update the tracked version so the dialog won't reappear.
-
First-run behavior: on first run the manager records the asset version but does not show the dialog (to avoid surprising new users).
-
Safety & destructive migration: destructive recovery is disabled by default. Code-level callers must explicitly allow destructive migration when creating the DB (see
TacticalDatabase.getInstance(..., allowDestructiveMigration = true)). Prefer using the Clean option in the dialog for an explicit, user-confirmed wipe-and-import. -
Implementation notes:
importDatabaseMerge()andimportDatabaseClean()implement the two import modes; merge is currently a non-destructive implementation that updates version tracking (detailed merge logic is TODO), while clean closes the DB instance, deletes the internal DB file, recreates the DB from assets, and updates version tracking on success. -
Migration note: the app provides explicit Room migrations in
TacticalDatabase(seeMIGRATION_9_10which adds ahealthcolumn totactical_units). If the installed DB schema is incompatible, the app will not automatically delete the user's DB unless destructive migration is allowed — prefer manual/explicit clean import for recovery. -
Troubleshooting:
- If you expect an update dialog but don't see it: confirm the app has tracked a previous version (first-run records the version silently), and ensure the asset
user_versionis higher than the tracked version. - To force a clean import programmatically: call
DatabaseUpdateManager.importDatabaseClean()or recreate the DB withTacticalDatabase.recreateInstance(context).
- If you expect an update dialog but don't see it: confirm the app has tracked a previous version (first-run records the version silently), and ensure the asset
- DB Indexes: optimized for fast queries
- Coroutines: non-blocking DB operations
- StateFlow: reactive UI updates
- History Limit: configurable (default 14 days)
- ✅ Export.lua nearbyUnits collection
- ✅ Extend tactical entities
- ✅ Database migration v6 → v7
- ✅ DataPadManager processing
- ✅ Create repository
- ✅ UI screen + ViewModel
- ✅ Add FAB button
- ✅ Map integration (markers on map) - live tracking with automatic updates
- ⏳ Navigation integration (screen routing)
- ⏳ Unit detail view (with history display)
Units are now displayed live automatically as markers on the map:
Features:
- ✅ Live updates: markers move automatically with unit positions
- ✅ Auto-remove: inactive units disappear from the map immediately
- ✅ Coalition colors: Neutral (gray), Red (red), Blue (blue)
- ✅ Category icons: Aircraft, Helicopter, Ground, Ship
- ✅ Heading display: markers rotate to match unit heading
- ✅ Details on click: name, category, coalition, speed, altitude, group
- ✅ Toggle control: only shown when entity tracking is enabled
Implementation:
// In MapViewer.kt:
LaunchedEffect(mapState.mapView, tacticalUnitsRepository, dataPadManager.isEntityTrackingEnabled) {
repo.getAllActiveUnits().collect { units ->
// Update markers: new units → create markers, inactive units → remove markers
// Existing units → update position + heading
}
}// In Navigation Graph:
composable("tactical_units") {
val viewModel: TacticalUnitsViewModel = viewModel(
factory = TacticalUnitsViewModelFactory(
TacticalUnitsRepository(LocalContext.current)
)
)
TacticalUnitsListScreen(
viewModel = viewModel,
onNavigateBack = { navController.popBackStack() },
onUnitClick = { unit ->
// Navigate to detail view or center map on the unit
}
)
}- Is DataPad enabled? (Settings)
- Was the ECDH handshake successful? (Connection status)
- Is DCS running and is
Export.luaactive? - Is
forward_parsed_udp.pyrunning?
- Run cleanup (Settings in
TacticalUnitsListScreen) - Or manually: "Delete All Units"
- Run history cleanup
- Delete old inactive units
- Check database size
Part of ChecklistInteractive — see the main LICENSE