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
))
self.set_waypoint_tot(waypoint, self.timing.race_track_start)
racetrack.stop_after_time(self.timing.race_track_end)
self.set_waypoint_tot(waypoint,
self.timing.race_track_start(self.flight))
racetrack.stop_after_time(self.timing.race_track_end(self.flight))
waypoint.add_task(racetrack)
return waypoint

View File

@ -27,21 +27,22 @@ INGRESS_TYPES = {
FlightWaypointType.INGRESS_STRIKE,
}
IP_TYPES = {
FlightWaypointType.INGRESS_CAS,
FlightWaypointType.INGRESS_ESCORT,
FlightWaypointType.INGRESS_SEAD,
FlightWaypointType.INGRESS_STRIKE,
FlightWaypointType.PATROL_TRACK,
}
class GroundSpeed:
@staticmethod
def mission_speed(package: Package) -> int:
speeds = set()
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:
logging.error(f"Could not find ingress point for {flight}.")
if flight.points:
@ -152,8 +153,10 @@ class TotEstimator:
# Takeoff immediately.
return 0
if self.package.primary_task == FlightType.BARCAP:
start_time = self.timing.race_track_start
# BARCAP flights do not coordinate with the rest of the package on join
# or ingress points.
if flight.flight_type == FlightType.BARCAP:
start_time = self.timing.race_track_start(flight)
else:
start_time = self.timing.join
return start_time - travel_time - self.HOLD_TIME
@ -166,7 +169,9 @@ class TotEstimator:
def earliest_tot_for_flight(self, flight: Flight) -> int:
"""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.
@ -177,27 +182,28 @@ class TotEstimator:
The earliest possible TOT for the given flight in seconds. Returns 0
if an ingress point cannot be found.
"""
time_to_ingress = self.estimate_waypoints_to_target(flight, IP_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
if self.package.primary_task == FlightType.BARCAP:
# The racetrack start *is* the target. The package target is the
# protected objective.
time_to_target = 0
if flight.flight_type == FlightType.BARCAP:
time_to_target = self.estimate_waypoints_to_target(flight, {
FlightWaypointType.PATROL_TRACK
})
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
time_to_target = TravelTime.between_points(
time_to_target = time_to_ingress + TravelTime.between_points(
self.package.waypoints.ingress, self.package.target.position,
GroundSpeed.mission_speed(self.package))
return sum([
self.estimate_startup(flight),
self.estimate_ground_ops(flight),
time_to_ingress,
time_to_target,
])
@ -281,18 +287,22 @@ class PackageWaypointTiming:
assert self.package.time_over_target is not None
return self.package.time_over_target
@property
def race_track_start(self) -> int:
if self.package.primary_task == FlightType.BARCAP:
return self.package.time_over_target
def race_track_start(self, flight: Flight) -> int:
if flight.flight_type == FlightType.BARCAP:
return self.target
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
@property
def race_track_end(self) -> int:
if self.package.primary_task == FlightType.BARCAP:
def race_track_end(self, flight: Flight) -> int:
if flight.flight_type == FlightType.BARCAP:
return self.target + CAP_DURATION * 60
else:
# For TARCAP. See the explanation in race_track_start.
return self.egress
def push_time(self, flight: Flight, hold_point: FlightWaypoint) -> int:
@ -303,7 +313,8 @@ class PackageWaypointTiming:
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 = (
FlightWaypointType.TARGET_GROUP_LOC,
FlightWaypointType.TARGET_POINT,
@ -321,7 +332,7 @@ class PackageWaypointTiming:
elif waypoint.waypoint_type == FlightWaypointType.SPLIT:
return self.split
elif waypoint.waypoint_type == FlightWaypointType.PATROL_TRACK:
return self.race_track_start
return self.race_track_start(flight)
return None
def depart_time_for_waypoint(self, waypoint: FlightWaypoint,
@ -329,7 +340,7 @@ class PackageWaypointTiming:
if waypoint.waypoint_type == FlightWaypointType.LOITER:
return self.push_time(flight, waypoint)
elif waypoint.waypoint_type == FlightWaypointType.PATROL:
return self.race_track_end
return self.race_track_end(flight)
return None
@classmethod

View File

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

View File

@ -55,11 +55,12 @@ class QFlightWaypointList(QTableView):
waypoints = itertools.chain([takeoff], self.flight.points)
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)),
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:
self.model.insertRow(self.model.rowCount())
@ -71,15 +72,15 @@ class QFlightWaypointList(QTableView):
altitude_item.setEditable(False)
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.setEditable(False)
self.model.setItem(row, 2, tot_item)
def tot_text(self, waypoint: FlightWaypoint,
def tot_text(self, flight: Flight, waypoint: FlightWaypoint,
timing: PackageWaypointTiming) -> str:
prefix = ""
time = timing.tot_for_waypoint(waypoint)
time = timing.tot_for_waypoint(flight, waypoint)
if time is None:
prefix = "Depart "
time = timing.depart_time_for_waypoint(waypoint, self.flight)