Fix TOT/start for BARCAPs in other packages.

We were only getting BARCAP results right in BARCAP packages. This
fixes calculations of TOTs and start times for BARCAPs in strike
packages.

The probably needs some refactoring. BARCAP is just the symptomatic
example at the moment, but the real problem is that different mission
profiles exist and we currently only handle one. Making profiles
explicit in mission planning will clean this up, will be needed for
other future mission types, and makes it easier for us to alter
behavior for waypoint and timing decisions based on the aircraft or
mission type.
This commit is contained in:
Dan Albert 2020-10-23 22:28:51 -07:00
parent c3fca6696d
commit 15db12fb21
4 changed files with 57 additions and 44 deletions

View File

@ -1288,8 +1288,9 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
pattern=OrbitAction.OrbitPattern.RaceTrack pattern=OrbitAction.OrbitPattern.RaceTrack
)) ))
self.set_waypoint_tot(waypoint, self.timing.race_track_start) self.set_waypoint_tot(waypoint,
racetrack.stop_after_time(self.timing.race_track_end) self.timing.race_track_start(self.flight))
racetrack.stop_after_time(self.timing.race_track_end(self.flight))
waypoint.add_task(racetrack) waypoint.add_task(racetrack)
return waypoint return waypoint

View File

@ -27,21 +27,22 @@ INGRESS_TYPES = {
FlightWaypointType.INGRESS_STRIKE, FlightWaypointType.INGRESS_STRIKE,
} }
IP_TYPES = {
FlightWaypointType.INGRESS_CAS,
FlightWaypointType.INGRESS_ESCORT,
FlightWaypointType.INGRESS_SEAD,
FlightWaypointType.INGRESS_STRIKE,
FlightWaypointType.PATROL_TRACK,
}
class GroundSpeed: class GroundSpeed:
@staticmethod @staticmethod
def mission_speed(package: Package) -> int: def mission_speed(package: Package) -> int:
speeds = set() speeds = set()
for flight in package.flights: for flight in package.flights:
waypoint = flight.waypoint_with_type(IP_TYPES) # Find a waypoint that matches the mission start waypoint and use
# that for the altitude of the mission. That may not be true for the
# whole mission, but it's probably good enough for now.
waypoint = flight.waypoint_with_type({
FlightWaypointType.INGRESS_CAS,
FlightWaypointType.INGRESS_ESCORT,
FlightWaypointType.INGRESS_SEAD,
FlightWaypointType.INGRESS_STRIKE,
FlightWaypointType.PATROL_TRACK,
})
if waypoint is None: if waypoint is None:
logging.error(f"Could not find ingress point for {flight}.") logging.error(f"Could not find ingress point for {flight}.")
if flight.points: if flight.points:
@ -152,8 +153,10 @@ class TotEstimator:
# Takeoff immediately. # Takeoff immediately.
return 0 return 0
if self.package.primary_task == FlightType.BARCAP: # BARCAP flights do not coordinate with the rest of the package on join
start_time = self.timing.race_track_start # or ingress points.
if flight.flight_type == FlightType.BARCAP:
start_time = self.timing.race_track_start(flight)
else: else:
start_time = self.timing.join start_time = self.timing.join
return start_time - travel_time - self.HOLD_TIME return start_time - travel_time - self.HOLD_TIME
@ -166,7 +169,9 @@ class TotEstimator:
def earliest_tot_for_flight(self, flight: Flight) -> int: def earliest_tot_for_flight(self, flight: Flight) -> int:
"""Estimate fastest time from mission start to the target position. """Estimate fastest time from mission start to the target position.
For CAP missions, this is time to race track start. For BARCAP flights, this is time to race track start. This ensures that
they are on station at the same time any other package members reach
their ingress point.
For other mission types this is the time to the mission target. For other mission types this is the time to the mission target.
@ -177,27 +182,28 @@ class TotEstimator:
The earliest possible TOT for the given flight in seconds. Returns 0 The earliest possible TOT for the given flight in seconds. Returns 0
if an ingress point cannot be found. if an ingress point cannot be found.
""" """
time_to_ingress = self.estimate_waypoints_to_target(flight, IP_TYPES) if flight.flight_type == FlightType.BARCAP:
if time_to_ingress is None: time_to_target = self.estimate_waypoints_to_target(flight, {
logging.warning( FlightWaypointType.PATROL_TRACK
f"Found no ingress types. Cannot estimate TOT for {flight}") })
# Return 0 so this flight's travel time does not affect the rest of
# the package.
return 0
if self.package.primary_task == FlightType.BARCAP:
# The racetrack start *is* the target. The package target is the
# protected objective.
time_to_target = 0
else: else:
time_to_ingress = self.estimate_waypoints_to_target(
flight, INGRESS_TYPES
)
if time_to_ingress is None:
logging.warning(
f"Found no ingress types. Cannot estimate TOT for {flight}")
# Return 0 so this flight's travel time does not affect the rest
# of the package.
return 0
assert self.package.waypoints is not None assert self.package.waypoints is not None
time_to_target = TravelTime.between_points( time_to_target = time_to_ingress + TravelTime.between_points(
self.package.waypoints.ingress, self.package.target.position, self.package.waypoints.ingress, self.package.target.position,
GroundSpeed.mission_speed(self.package)) GroundSpeed.mission_speed(self.package))
return sum([ return sum([
self.estimate_startup(flight), self.estimate_startup(flight),
self.estimate_ground_ops(flight), self.estimate_ground_ops(flight),
time_to_ingress,
time_to_target, time_to_target,
]) ])
@ -281,18 +287,22 @@ class PackageWaypointTiming:
assert self.package.time_over_target is not None assert self.package.time_over_target is not None
return self.package.time_over_target return self.package.time_over_target
@property def race_track_start(self, flight: Flight) -> int:
def race_track_start(self) -> int: if flight.flight_type == FlightType.BARCAP:
if self.package.primary_task == FlightType.BARCAP: return self.target
return self.package.time_over_target
else: else:
# The only other type that (currently) uses race tracks is TARCAP,
# which is sort of in need of cleanup. TARCAP is only valid on front
# lines and they participate in join points and patrol between the
# ingress and egress points rather than on a race track actually
# pointed at the enemy.
return self.ingress return self.ingress
@property def race_track_end(self, flight: Flight) -> int:
def race_track_end(self) -> int: if flight.flight_type == FlightType.BARCAP:
if self.package.primary_task == FlightType.BARCAP:
return self.target + CAP_DURATION * 60 return self.target + CAP_DURATION * 60
else: else:
# For TARCAP. See the explanation in race_track_start.
return self.egress return self.egress
def push_time(self, flight: Flight, hold_point: FlightWaypoint) -> int: def push_time(self, flight: Flight, hold_point: FlightWaypoint) -> int:
@ -303,7 +313,8 @@ class PackageWaypointTiming:
GroundSpeed.for_flight(flight, hold_point.alt) GroundSpeed.for_flight(flight, hold_point.alt)
) )
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[int]: def tot_for_waypoint(self, flight: Flight,
waypoint: FlightWaypoint) -> Optional[int]:
target_types = ( target_types = (
FlightWaypointType.TARGET_GROUP_LOC, FlightWaypointType.TARGET_GROUP_LOC,
FlightWaypointType.TARGET_POINT, FlightWaypointType.TARGET_POINT,
@ -321,7 +332,7 @@ class PackageWaypointTiming:
elif waypoint.waypoint_type == FlightWaypointType.SPLIT: elif waypoint.waypoint_type == FlightWaypointType.SPLIT:
return self.split return self.split
elif waypoint.waypoint_type == FlightWaypointType.PATROL_TRACK: elif waypoint.waypoint_type == FlightWaypointType.PATROL_TRACK:
return self.race_track_start return self.race_track_start(flight)
return None return None
def depart_time_for_waypoint(self, waypoint: FlightWaypoint, def depart_time_for_waypoint(self, waypoint: FlightWaypoint,
@ -329,7 +340,7 @@ class PackageWaypointTiming:
if waypoint.waypoint_type == FlightWaypointType.LOITER: if waypoint.waypoint_type == FlightWaypointType.LOITER:
return self.push_time(flight, waypoint) return self.push_time(flight, waypoint)
elif waypoint.waypoint_type == FlightWaypointType.PATROL: elif waypoint.waypoint_type == FlightWaypointType.PATROL:
return self.race_track_end return self.race_track_end(flight)
return None return None
@classmethod @classmethod

View File

@ -344,7 +344,7 @@ class QLiberationMap(QGraphicsView):
altitude_type = "AGL" if waypoint.alt_type == "RADIO" else "MSL" altitude_type = "AGL" if waypoint.alt_type == "RADIO" else "MSL"
prefix = "TOT" prefix = "TOT"
time = timing.tot_for_waypoint(waypoint) time = timing.tot_for_waypoint(flight, waypoint)
if time is None: if time is None:
prefix = "Depart" prefix = "Depart"
time = timing.depart_time_for_waypoint(waypoint, flight) time = timing.depart_time_for_waypoint(waypoint, flight)

View File

@ -55,11 +55,12 @@ class QFlightWaypointList(QTableView):
waypoints = itertools.chain([takeoff], self.flight.points) waypoints = itertools.chain([takeoff], self.flight.points)
for row, waypoint in enumerate(waypoints): for row, waypoint in enumerate(waypoints):
self.add_waypoint_row(row, waypoint, timing) self.add_waypoint_row(row, self.flight, waypoint, timing)
self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)), self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
QItemSelectionModel.Select) QItemSelectionModel.Select)
def add_waypoint_row(self, row: int, waypoint: FlightWaypoint, def add_waypoint_row(self, row: int, flight: Flight,
waypoint: FlightWaypoint,
timing: PackageWaypointTiming) -> None: timing: PackageWaypointTiming) -> None:
self.model.insertRow(self.model.rowCount()) self.model.insertRow(self.model.rowCount())
@ -71,15 +72,15 @@ class QFlightWaypointList(QTableView):
altitude_item.setEditable(False) altitude_item.setEditable(False)
self.model.setItem(row, 1, altitude_item) self.model.setItem(row, 1, altitude_item)
tot = self.tot_text(waypoint, timing) tot = self.tot_text(flight, waypoint, timing)
tot_item = QStandardItem(tot) tot_item = QStandardItem(tot)
tot_item.setEditable(False) tot_item.setEditable(False)
self.model.setItem(row, 2, tot_item) self.model.setItem(row, 2, tot_item)
def tot_text(self, waypoint: FlightWaypoint, def tot_text(self, flight: Flight, waypoint: FlightWaypoint,
timing: PackageWaypointTiming) -> str: timing: PackageWaypointTiming) -> str:
prefix = "" prefix = ""
time = timing.tot_for_waypoint(waypoint) time = timing.tot_for_waypoint(flight, waypoint)
if time is None: if time is None:
prefix = "Depart " prefix = "Depart "
time = timing.depart_time_for_waypoint(waypoint, self.flight) time = timing.depart_time_for_waypoint(waypoint, self.flight)