Class BasicPathRanker

java.lang.Object
megamek.client.bot.princess.PathRanker
megamek.client.bot.princess.BasicPathRanker
All Implemented Interfaces:
IPathRanker
Direct Known Subclasses:
AerospacePathRanker, InfantryPathRanker, MutualSupportPathRanker, NewtonianAerospacePathRanker, UtilityPathRanker

public class BasicPathRanker extends PathRanker
A path ranker implementation for most units that evaluates and prioritizes movement paths with the highest score.

This class evaluates potential movement paths for units controlled by Princess and assigns utility scores based on tactical considerations. It ranks paths according to their overall desirability, considering multiple weighted factors including:

  • Fall risk - Probability of failing piloting skill rolls and falling
  • Combat effectiveness - Potential damage output vs. damage taken
  • Strategic positioning - Maintaining appropriate distance to enemies
  • Tactical advantage - Facing, movement modifiers, and clustering
  • Self-preservation - Avoiding hazardous terrain and managing retreat
  • Unit cohesion - Maintaining proximity to friendly units

The ranker implements Princess's core movement decision-making logic for most ground units, calculating a final utility score where higher values represent more desirable paths. The relative importance of different factors is determined by the bot's behavior settings (aggression, bravery, mutual support, etc.).

Path evaluation also considers terrain hazards like building collapses, water, magma, ice, swamp, and other that could damage or immobilize the unit.

  • Field Details

    • FACING_MOD_MULTIPLIER

      public static final int FACING_MOD_MULTIPLIER
      See Also:
    • LOG_DECIMAL

      protected final DecimalFormat LOG_DECIMAL
    • LOG_PERCENT

      protected final NumberFormat LOG_PERCENT
    • bestDamageByEnemies

      protected final Map<Integer,Double> bestDamageByEnemies
    • blackIce

      protected int blackIce
    • THREAT_CONTACT_RANGE

      protected static final int THREAT_CONTACT_RANGE
      Position discipline is dormant beyond this range: out of contact there is no exchange to hold and the force should move loose and fast.
      See Also:
    • TEMPO_REFERENCE_MP

      protected static final double TEMPO_REFERENCE_MP
      Reference movement rate that prices "one turn of advance" (TEMPO_REFERENCE_MP * aggression), the yardstick every position-discipline bound is measured against. At default aggression (2.5) a turn of advance is worth 37.5 - the same scale as the stock per-hex aggression gradient over a full move.
      See Also:
    • REPRESENTATIVE_TO_HIT

      protected static final int REPRESENTATIVE_TO_HIT
      The to-hit number the hit-chance-ratio discounts are priced at: gunnery 4 plus a typical spread of range and target modifiers. Only the RATIO of hit chances at this number matters - walking turns 8s into 9s whoever you are - so the midpoint stands in for every shooter without pretending the estimate knows its real to-hit.
      See Also:
    • ESTIMATE_NOISE_MARGIN

      protected static final double ESTIMATE_NOISE_MARGIN
      The indifference band: how much better a destination must score before leaving a held firing position is worth it. The ranker re-estimates every hex every round, and the estimate of the SAME hex moves round to round as enemies shift - measured on 30-game mirror runs, a unit standing still sees its own hex's score change by a median of about five points per round. A destination that beats the current hex by less than that is indistinguishable from the noise, and chasing it pays the attacker movement modifier for nothing. The margin is the measured median (estimate_wobble.py, re-measured whenever the estimators change), added to the stationary path's hold credit: real improvements - fresh cover against a new threat, a range advantage - typically score well past it and still win.
      See Also:
    • HOLD_CREDIT_ATTACK_CAP_FACTOR

      protected static final double HOLD_CREDIT_ATTACK_CAP_FACTOR
      How much of a turn of advance the hold credit may reach under each posture: an attacking force keeps the credit modest so a good hex never outbids the advance, while a defending force holds harder. Both keep the credit below one turn of advance at default aggression; a final clamp in calculatePositionHoldMod(megamek.common.moves.MovePath, megamek.common.game.Game, megamek.client.bot.princess.BasicPathRanker.FiringPhysicalDamage, double, double) holds that bound across the whole aggression slider, where the fixed ESTIMATE_NOISE_MARGIN would otherwise overtake the shrinking yardstick.
      See Also:
    • HOLD_CREDIT_DEFEND_CAP_FACTOR

      protected static final double HOLD_CREDIT_DEFEND_CAP_FACTOR
      See Also:
  • Constructor Details

    • BasicPathRanker

      public BasicPathRanker(Princess owningPrincess)
  • Method Details

    • calculateAerospaceMod

      protected double calculateAerospaceMod(MovePath path, Game game, List<Entity> enemies)
      Doctrine seam for atmospheric aerospace movement, overridden by CASPAR's aerospace ranker.

      Returns 0 here, so the stock utility total is exactly what it was before this seam existed. It is a seam rather than a term because none of the modifiers above has any concept of altitude: they price distance, facing and cover on a flat board, while what decides an air-to-air engagement is whether the two units are close enough in altitude to shoot at all (TW p.241).

      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
    • attackerMovementDamageDiscount

      protected double attackerMovementDamageDiscount(MovePath path)
      How much of its raw damage-at-range a unit keeps after its own movement makes it a worse shot.

      The unmoved-enemy estimate above is a range-table lookup with no to-hit roll in it, so the one certain cost of moving - the attacker movement modifier, +1 walked, +2 ran, +3 jumped - did not exist in it, and a short step read as free. This prices it back in as a hit-chance ratio at the REPRESENTATIVE_TO_HIT midpoint: standing keeps everything, walking keeps about two-thirds, running two-fifths, a standard jump one-fifth. The modifier comes from the same engine call the server fires with (Compute.getAttackerMovementModifier(megamek.common.game.Game, int)), so infantry's exemption, the dual-cockpit dedicated gunner, and the jumping-jack abilities are all priced without naming them.

      Parameters:
      path - the path being evaluated
      Returns:
      the fraction of the raw damage estimate the path's movement mode leaves the unit
    • incomingFireTerrainDiscount

      protected double incomingFireTerrainDiscount(MovePath path)
      How much of an unmoved enemy's raw damage the terrain at this path's destination lets through.

      The twin of attackerMovementDamageDiscount(megamek.common.moves.MovePath): the raw damage-at-range estimate above has no to-hit roll, so the cover the destination hex gives - the woods and partial-cover modifiers the full fire-control guess prices against already-moved enemies - did not exist in it, and a covered hex and an open one read the same. This discounts that raw estimate by the hit-chance ratio the destination's cover implies, from the per-round HexPropertiesMap labels. Only cover a unit can FIGHT from is credited: woodline edges and partial cover count; deep woods - concealing but blind - deliberately do not, so the discount never coaxes a unit into ground it cannot shoot from.

      Parameters:
      path - the path being evaluated
      Returns:
      the fraction of the raw enemy damage estimate the destination's cover lets through
    • hexProperties

      protected megamek.client.bot.princess.HexPropertiesMap hexProperties(Game game, int boardId)
      The per-hex terrain labels for a board, computed once per round and shared by every path of every mover on it.
    • resolvePosture

      protected CombatPosture resolvePosture(Game game, int boardId)
      The posture the force fights under this round on the given board, resolved once per round per board and shared by every unit there. Only units on that board have a say: entity lists are game-wide, and in a multi-board game mixing boards would make the closing rate meaningless. When the answer changes, the bot says so in the chat with its reason, so an observer can follow the force's intent without reading logs.
    • resolvedPostureFor

      @Nullable protected CombatPosture resolvedPostureFor(Game game, int boardId)
      The posture already resolved for this board this round, without resolving one if none has been.

      Deliberately does not call resolvePosture(megamek.common.game.Game, int): that announces the force's intent in the chat when it changes, so resolving merely to write a log line would make the bot say things it had not otherwise decided.

      Overrides:
      resolvedPostureFor in class PathRanker
      Parameters:
      game - the current game
      boardId - the board to report on
      Returns:
      the resolved posture, or null
    • isWithdrawing

      protected boolean isWithdrawing(Entity unit)
      Whether a unit has left the fighting line - pulling back under forced withdrawal, or ordered somewhere else entirely (a destination edge or a player waypoint). Such a unit earns no hold credit (it has somewhere to be, however good its current hex), and its say in where the formation's centre lies is sharply reduced - not removed, so the centre moves continuously as units leave the line (see MutualSupportPathRanker's withdrawing centre weight).
    • getPSRList

      protected List<TargetRoll> getPSRList(MovePath path)
      Overrides:
      getPSRList in class PathRanker
    • calculateAggressionMod

      protected double calculateAggressionMod(Entity movingUnit, MovePath path, Game game)
      Calculates an aggression modifier that penalizes paths keeping the unit far from enemies.

      This method implements the tactical preference for closing with the enemy based on:

      • The distance to the closest enemy from the path's final position
      • The AI's configured hyper-aggression value

      The aggression modifier follows this formula:

       aggressionMod = distanceToClosestEnemy * hyperAggressionValue
       

      Since this value is subtracted in the final utility calculation, higher values represent stronger penalties for staying distant from enemies. A high hyperAggressionValue will strongly push units toward closing with enemies regardless of other tactical considerations.

      Parameters:
      movingUnit - The entity being moved
      path - The movement path being evaluated
      game - The current game state
      Returns:
      An aggression modifier value (higher is worse) to be used in path ranking
    • calculateCloseRangeIncentive

      protected double calculateCloseRangeIncentive(Entity movingUnit, double distToEnemy)
      A reward for advancing toward point-blank range, for units that do more damage up close than they do at their current range - short-range gunners and, most importantly, melee brawlers. The ranged damage estimate credits a unit's hatchet, sword or kick only on the turn it ends adjacent to an enemy, so a brawler otherwise sees no reason to close and will pace at range (issue #7627: an Axman would not wade a river to reach hatchet range). This incentive is the unit's point-blank damage premium scaled by how far it has closed toward contact, so it grows smoothly as the unit advances and is zero for units that already deliver their best damage at range.
      Parameters:
      movingUnit - the unit being moved
      distToEnemy - the path's ending distance to the closest enemy
      Returns:
      the closing incentive to add to the path's utility (never negative)
    • rankPaths

      public TreeSet<RankedPath> rankPaths(List<MovePath> movePaths, Game game, int maxRange, double fallTolerance, List<Entity> enemies, List<Entity> friends)
      Specified by:
      rankPaths in interface IPathRanker
      Overrides:
      rankPaths in class PathRanker
    • calculateMutualSupportMod

      protected double calculateMutualSupportMod(Coords friendsCoords, MovePath path)
      Calculates a mutual support modifier that penalizes paths taking the unit away from friendly forces.

      This method implements the tactical preference for maintaining formation with friendly units based on:

      • The distance from the path's final position to the center of friendly forces
      • The AI's configured mutual support value

      The mutual support modifier follows this formula:

       mutualSupportMod = distanceToFriends * mutualSupportValue
       

      Since this value is subtracted in the final utility calculation, higher values represent stronger penalties for straying from the friendly force. If no friendly forces are present (friendsCoords is null), the method returns 0, applying no penalty.

      Parameters:
      friendsCoords - The coordinate representing the center of friendly forces, or null if no friends
      path - The movement path being evaluated
      Returns:
      A mutual support modifier value (higher is worse) to be used in path ranking
    • calculateFacingMod

      protected double calculateFacingMod(Entity movingUnit, Game game, MovePath path, @Nullable Coords enemyMedianPosition, @Nullable Coords closestEnemyPosition, boolean squareUpOnClosestEnemy)
      Calculates a facing modifier that penalizes paths where the unit is not facing threats.

      This method evaluates how well the unit's final facing aligns with the direction it should optimally face to confront enemies. The method:

      • Determines the ideal facing based on the position of the closest enemies
      • Calculates how far off the unit's final facing is from this ideal
      • Applies a penalty proportional to how far off the facing is
      • Considers armor distribution to bias facing toward better-protected sides

      Facing differences are measured in hexside rotations (0-3), where:

      • 0 = Perfect facing toward threat
      • 1 = Off by one hexside (60°)
      • 2 = Off by two hexsides (120°)
      • 3 = Facing directly away (180°)

      The facing penalty is calculated as: 50 * (facingDiff - 1), with a minimum of 0. This means perfect facing has no penalty, while the worst facing incurs a -100 penalty.

      Parameters:
      movingUnit - The entity being moved
      game - The current game state
      path - The movement path being evaluated
      enemyMedianPosition - The coordinates of the median position of enemies, or null to use the board center
      closestEnemyPosition - The coordinates of the closest enemy, or null to use the board center
      Returns:
      A facing modifier value (higher is worse) to be used in path ranking
    • calculateSelfPreservationMod

      protected double calculateSelfPreservationMod(Entity movingUnit, MovePath path, Game game)
      Calculates a self-preservation modifier that encourages units to retreat when appropriate.

      This method applies penalties or bonuses based on the unit's movement toward safety:

      • For units in forced withdrawal or moving to a destination, encourages movement toward home edge
      • Applies a significant bonus (ARRIVED_AT_DESTINATION_FACTOR) if the path reaches the home edge
      • Applies a penalty proportional to distance from home edge otherwise

      The method only applies these modifiers when the unit is in forced withdrawal or deliberately moving to a designated location. For normal combat operations, it returns 0 to indicate self-preservation is not a priority factor.

      Parameters:
      movingUnit - The entity being moved
      path - The movement path being evaluated
      game - The current game state
      Returns:
      A self-preservation modifier value (negative values are better) to be used in path ranking
    • calculateOffBoardMod

      protected double calculateOffBoardMod(MovePath path)
      Calculates a modifier that penalizes paths risking an aerospace unit flying off the board.

      This method evaluates the risk of aerospace units inadvertently leaving the game board on subsequent turns due to their movement characteristics. The method:

      • Analyzes the path's final position, velocity, and facing
      • Calculates whether the unit might be forced off-board on the following turns
      • Returns a multiplier that severely penalizes paths with high off-board risk

      For non-aerospace units, this method always returns 0 as they don't face this particular risk. For aerospace units, a non-zero return value serves as a multiplier to other penalties, effectively eliminating dangerous paths from consideration.

      Parameters:
      path - The movement path being evaluated
      Returns:
      A multiplier value (0 for no risk, positive for risk of flying off-board)
    • checkBlackIce

      protected void checkBlackIce(Game game)
    • rankPath

      protected RankedPath rankPath(MovePath path, Game game, int maxRange, double fallTolerance, List<Entity> enemies, Coords friendsCoords)
      Evaluates and ranks movement paths for MegaMek units based on multiple strategic factors. Returns a utility score where higher values represent more desirable paths.

      The utility score calculation combines several weighted factors:

         utility = -fallMod + braveryMod - aggressionMod - mutualSupportMod + movementMod
                   - crowdingTolerance - facingMod - selfPreservationMod - (utility * offBoardMod)
       

      Key Components:

      • fallMod: Penalty for paths with risk of failing piloting rolls
        • Calculated as pilotingFailure * fallShame
        • Higher values = worse paths (more likely to fall)
      • braveryMod: Reward for paths balancing damage output vs. incoming damage
        • Based on potential damage to enemies vs. expected damage received
        • Higher values = better paths (can do more damage than take)
      • aggressionMod: Penalty for staying far from enemies
        • Calculated as distanceToEnemy * aggressionValue
        • Higher values = worse paths (too far from enemies when aggression is high)
      • mutualSupportMod: Penalty for moving away from friendly units
        • Calculated as distanceToFriends * mutualSupportValue
        • Higher values = worse paths (isolated from allies)
      • facingMod: Penalty for not facing toward enemies
        • Based on facing direction relative to enemies
        • Higher values = worse paths (facing away from threats)
      • selfPreservationMod: Penalty for not moving toward retreat edge when needed
        • Special case: if reaching the home edge, gives a large bonus of ARRIVED_AT_DESTINATION_FACTOR
        • Higher values = worse paths (not retreating when damaged)
      • movementMod: Reward for using movement effectively
        • Based on multiple strategic factors
        • Higher values = better paths (using speed effectively)
      • crowdingTolerance: Penalty for ending in crowded areas
        • Based on nearby threats relative to maximum weapon range
        • Higher values = worse paths (too many enemies nearby)
      • offBoardMod: Penalty for aerospace units at risk of flying off the board
        • Applies as a multiplier to existing penalties
        • Higher values = worse paths (likely to fly off the board)

      The function uses behavior settings like bravery, aggression, and mutual support to adjust the relative importance of these factors based on the AI's configured personality.

      Specified by:
      rankPath in class PathRanker
      Parameters:
      path - The movement path to be evaluated
      game - The current game state
      maxRange - Entity max weapon range
      fallTolerance - a maximum PSR failure chance to be acceptable
      enemies - List of enemy units visible
      friendsCoords - Center of Gravity of friendly units (average coordinate position)
      Returns:
      A double representing the utility/desirability of the path (higher is better)
    • doctrineScores

      protected Map<String,Double> doctrineScores()
      The reasoning behind a subclass's own modifiers, which becomes extra columns in the BotLogger TSV.

      The modifier values themselves are already recorded, but a number on its own only says how much a term weighed, not why. Answering "why did the bot do this" after the fact needs the inputs that produced it, and a subclass that replaces a modifier is the only thing that knows what those are. Anything left only in trace logging is effectively unavailable: it is off by default and rotates away within a minute of a company-scale game.

      Returns:
      named values to record alongside the path's modifiers
    • calculatePositionHoldMod

      protected double calculatePositionHoldMod(MovePath path, Game game, BasicPathRanker.FiringPhysicalDamage damageEstimate, double expectedDamageTaken, double successProbability)
      Position persistence: a unit already standing on ground that gives a positive exchange earns credit for keeping it, so it stops shuffling between equivalent hexes for a movement modifier it does not need - the two-step that walks a firing line out of its positions.

      Quality is the exchange the stationary path itself was just evaluated at - expected damage dealt, weighted by the chance of standing to deliver it, minus expected damage taken - raised by heatSinkSustainBoost(megamek.common.units.Entity, megamek.common.game.Game, megamek.common.moves.MovePath) when the hex lets a hot unit sustain more. The exchange already appears once in the bravery term for every path equally; counting it again here, only for standing still, is the deliberate asymmetry that makes a good position sticky. Ground that gives nothing (or worse) holds nothing: the credit never anchors a unit in a losing exchange.

      Dormant outside THREAT_CONTACT_RANGE: on the approach there is no exchange to hold and the force should move loose and fast. Withdrawing units are leaving, not holding. The credit is capped so it never exceeds one turn of advance, harder under DEFEND than ATTACK (HOLD_CREDIT_DEFEND_CAP_FACTOR, HOLD_CREDIT_ATTACK_CAP_FACTOR).

      Parameters:
      path - the path being ranked (a copy, safe to inspect)
      game - the current game
      damageEstimate - the damage this unit is estimated to deal from the path's destination
      expectedDamageTaken - the damage it is estimated to take there, path hazards included
      successProbability - the chance the path completes without a failed piloting roll
      Returns:
      the utility credit, added to the path's utility
    • isLosRange

      protected boolean isLosRange(Game game)
    • isExtremeRange

      protected boolean isExtremeRange(Game game)
    • getBraveryMod

      protected double getBraveryMod(double successProbability, BasicPathRanker.FiringPhysicalDamage damageEstimate, double expectedDamageTaken)
      Calculates a bravery modifier for path evaluation based on potential damage and risk.

      This method determines how "brave" the AI should be when considering a path by weighing:

      • The probability of successfully completing the move (avoiding falls)
      • The maximum potential damage the unit can inflict from the final position
      • The expected damage the unit might receive

      The bravery modifier follows this formula:

       braveryMod = (successProbability * maximumDamageDone * braveryValue) - expectedDamageTaken
       

      Higher bravery values make the AI more willing to accept risks if it can deal significant damage. A positive bravery modifier indicates the path is tactically advantageous despite potential risks.

      Parameters:
      successProbability - The probability (0.0 to 1.0) of making all required piloting rolls
      damageEstimate - Container with estimates of damage that can be done
      expectedDamageTaken - Expected damage to be received in this position
      Returns:
      A bravery modifier value (higher is better) to be used in path ranking
    • calculateMovementMod

      protected double calculateMovementMod(MovePath pathCopy, Game game, List<Entity> enemies, StringBuilder formula)
      Calculates a movement modifier that rewards paths making units harder to hit. This serves as a key positive modifier in the overall utility calculation.

      The function encourages units to:

      • Keep moving when tactically beneficial
      • Choose paths with optimal movement types
      • Use movement as a defensive strategy

      The modifier is calculated as:

         movementFactor = TMM × (selfPreservation + favorHigherTMM)
       

      Where:

      • TMM: Target Movement Modifier - higher values make units harder to hit
      • selfPreservation: AI setting for valuing survival
      • favorHigherTMM: AI setting specifically for valuing movement as defense

      The modifier is applied when either:

      • No enemies are visible (encouraging exploration/positioning)
      • OR favorHigherTMM setting is enabled (encouraging defensive movement)
      Parameters:
      pathCopy - The movement path to evaluate
      game - The current game state
      enemies - List of enemy units visible
      formula - StringBuilder to append the formula for logging
      Returns:
      A positive double value representing the movement modifier
    • calculateCrowdingTolerance

      protected double calculateCrowdingTolerance(MovePath movePath, List<Entity> enemies, double maxRange, StringBuilder formula)
      Calculates a crowding tolerance modifier that penalizes paths ending in densely populated areas.

      This method evaluates the tactical risks of positioning in areas with many units nearby:

      • Counts friendly units within a short radius (depends on antiCrowding setting)
      • Counts enemy units within a medium radius (typically 60% of max weapon range)
      • Applies penalties based on these counts and the AI's antiCrowding preference

      The crowding penalty increases as more units (friendly or enemy) are present near the final position. This encourages units to maintain tactical spacing rather than clumping together, which helps avoid:

      • Becoming easy targets for area effect weapons
      • Blocking each other's line of fire
      • Creating movement bottlenecks

      The crowding penalty is only applied to Meks and Tanks, as other unit types have different tactical positioning requirements.

      Parameters:
      movePath - The movement path being evaluated
      enemies - List of enemy units visible
      maxRange - Entity's maximum weapon range
      formula - StringBuilder to append the formula explanation for logging
      Returns:
      A crowding tolerance value (higher is worse) to be used in path ranking
    • evaluateAsMoved

      protected boolean evaluateAsMoved(Entity enemy)
      Worker function that determines if a given enemy entity should be evaluated as if it has moved.
    • initUnitTurn

      public void initUnitTurn(Entity unit, Game game)
      Calculate who all other units would shoot at if I weren't around
      Specified by:
      initUnitTurn in interface IPathRanker
      Overrides:
      initUnitTurn in class PathRanker
    • calcDamageToStrategicTargets

      protected BasicPathRanker.FiringPhysicalDamage calcDamageToStrategicTargets(MovePath path, Game game, FireControlState fireControlState, BasicPathRanker.FiringPhysicalDamage damageStructure)
    • distanceToClosestEnemy

      public double distanceToClosestEnemy(Entity me, Coords position, Game game)
      Gives the distance to the closest enemy unit, or -1 if none exist. The reason being that the closest enemy unit may be 0 away.
      Parameters:
      me - Entity who has enemies
      position - Coords from which the closest enemy is found
      game - The current Game
    • checkPathForHazards

      public double checkPathForHazards(MovePath path, Entity movingUnit, Game game)