Class AerospacePathRanker

All Implemented Interfaces:
IPathRanker

public class AerospacePathRanker extends BasicPathRanker
Movement doctrine for aerospace units flying in an atmosphere.

The stock ranker prices distance, facing and cover on a flat board. None of that decides an air-to-air engagement. What decides it is altitude: two fighters more than a hex or so apart vertically cannot shoot at each other at all until they are far enough apart horizontally to clear the dead zone (TW p.241), and over a ground mapsheet "far enough" means seventeen hexes for one level of separation and thirty-three for two. A flight that spreads across altitudes fights as a collection of singletons that cannot help each other, and a flight holding the wrong altitude cannot fight at all.

Certainty is the other half

Matching an altitude is only worth anything if the opponent is still going to be at it. Aerospace units move last and as their own turn class, interleaved between players, so when this ranker runs some enemy fighters have committed to an altitude this turn and some have not. The ones that have are worth reacting to; the ones that have not are a guess. Every credit below is weighted by which it is.

That distinction does not exist in the stock ranker, which reports every airborne aero on a ground map as already moved whether it has or not - see evaluateAsMoved(megamek.common.units.Entity).

  • Constructor Details

    • AerospacePathRanker

      public AerospacePathRanker(Princess owningPrincess)
  • Method Details

    • getMovePathSuccessProbability

      protected double getMovePathSuccessProbability(MovePath movePath)
      Airborne aerospace paths do not answer to the ground units' fall machinery.

      This method feeds two consumers in the stock ranker, both built for falling: paths whose success product drops below the fall tolerance are culled before ranking, and survivors are charged fallShame - up to UNIT_DESTRUCTION_FACTOR when the product reaches zero. An airborne aerospace unit's piloting rolls are control rolls, and this ranker already prices them at crash scale, graded by altitude (controlRiskPenalty(megamek.common.moves.MovePath), maneuverRiskPenalty(megamek.common.moves.MovePath, megamek.common.game.Game, megamek.client.bot.princess.AerospaceVenue)) - a failed roll at altitude 8 is a scare, at altitude 2 a crater. Left in force, the ground pricing charged every maneuver path twice and read a post-Hammerhead stall as certain destruction (482 points against a winning path at 120) - which is why CASPAR ranked maneuver paths for days and never flew one.

      Overrides:
      getMovePathSuccessProbability in class PathRanker
      Parameters:
      movePath - The movement path to evaluate
      Returns:
      The probability (0.0 to 1.0) that all required piloting rolls will succeed
    • rankPaths

      public TreeSet<RankedPath> rankPaths(List<MovePath> movePaths, Game game, int maxRange, double fallTolerance, List<Entity> enemies, List<Entity> friends)
      The debrief: after every auction, one durable record of the decision and the roads not taken.

      Aerospace is complex enough that the story is in the alternatives - the mek was in this hex, the chosen path ended in that one, and a maneuver that overflew the target lost by nine points. The per-path TSV holds all of it but rotates away mid-game; this line survives in the bot log, one per aero turn, greppable by DEBRIEF. Every engagement gets its debrief, and the data is the story.

      Specified by:
      rankPaths in interface IPathRanker
      Overrides:
      rankPaths in class BasicPathRanker
    • calculateAerospaceMod

      protected double calculateAerospaceMod(MovePath path, Game game, List<Entity> enemies)
      Prices the geometry the stock terms cannot see: whether this pose can shoot anybody, whether it does so from an arc they cannot answer, and what it is risking to get there.
      Overrides:
      calculateAerospaceMod in class BasicPathRanker
      Parameters:
      path - the path being ranked
      game - the current game
      enemies - the enemies being weighed against this path
      Returns:
      the doctrine adjustment to this path's utility, 0 in the stock ranker
    • resolveAerospacePosture

      protected CombatPosture resolveAerospacePosture(Game game, int boardId, AerospaceVenue venue)
      Works out whether this flight is attacking or defending, from the aircraft alone.

      Kept apart from the force posture the ground code resolves, for two reasons. A mixed force's Meks and its fighters are not fighting the same battle - the ground line can be standing off while the flight is pressing an attack over it - and the closing rate that decides the question is measured at a completely different scale in the air, so pooling the two would answer neither.

      Parameters:
      game - the current game
      boardId - the board the flight is fighting over
      venue - which set of atmospheric rules is in force, which sets the measuring scale
      Returns:
      the posture this flight fights under this round
    • resolvedPostureFor

      protected CombatPosture resolvedPostureFor(Game game, int boardId)
      Reports the flight's own posture in preference to the ground force's, so the log says what this aircraft was actually flying under.
      Overrides:
      resolvedPostureFor in class BasicPathRanker
      Parameters:
      game - the current game
      boardId - the board to report on
      Returns:
      the resolved posture, or null
    • evaluateAsMoved

      protected boolean evaluateAsMoved(Entity enemy)
      Reports an enemy as already moved only when it actually has.

      The stock ranker adds isAirborneAeroOnGroundMap() to this test, which makes every enemy fighter over a ground map report as committed whether it has moved or not. The bot then reads a stale altitude as settled fact, matches it, and watches the opponent move somewhere else - and because the flag never changes, it cannot tell that case apart from a genuine read on an opponent who has committed. Dropping the clause is what makes reacting possible at all.

      NewtonianAerospacePathRanker already omits it; the shortcut is the ground-map ranker's alone.

      Overrides:
      evaluateAsMoved in class BasicPathRanker
    • doctrineScores

      protected Map<String,Double> doctrineScores()
      Records why the doctrine scored this path the way it did, as extra TSV columns.

      The totals alone cannot answer "why did the fighter go there". These are the inputs: the altitude it chose, how many enemies it could actually shoot from there, how many of those had already committed to an altitude, and what the path risked to arrive.

      Overrides:
      doctrineScores in class BasicPathRanker
      Returns:
      named values to record alongside the path's modifiers