protected double rankUnfishedCompletable(Completable completable, LegacyAiScoreContext ctx) {
   double rating = 0.0;
   double points = getUnfinishedCompletablePoints(completable, ctx);
   for (Player p : ctx.getMajorOwners()) {
     rating += reducePoints(points, p);
   }
   return rating;
 }
  protected double getUnfinishedCityPoints(City city, LegacyAiScoreContext ctx) {
    double chanceToClose = ctx.getChanceToClose();

    if (chanceToClose > MIN_CHANCE && ctx.getMajorOwners().contains(getPlayer())) {
      openCount[OPEN_COUNT_CITY]++;
    }

    // legacy heuristic
    CityScoreContext cityCtx = (CityScoreContext) ctx.getCompletableScoreContext();
    if (chanceToClose < MIN_CHANCE) {
      return cityCtx.getPoints(false) + 3.0 * chanceToClose;
    } else {
      return cityCtx.getPoints(true) - 3.0 * (1.0 - chanceToClose);
    }
  }
  protected double getUnfinishedRoadPoints(Road road, LegacyAiScoreContext ctx) {
    double chanceToClose = ctx.getChanceToClose();
    ;

    if (chanceToClose > MIN_CHANCE && ctx.getMajorOwners().contains(getPlayer())) {
      openCount[OPEN_COUNT_ROAD]++;
    }

    // legacy heuristic
    RoadScoreContext roadCtx = (RoadScoreContext) ctx.getCompletableScoreContext();
    if (chanceToClose < MIN_CHANCE) {
      return roadCtx.getPoints(false) + 3.0 * chanceToClose;
    } else {
      return roadCtx.getPoints(true) - 3.0 * (1.0 - chanceToClose);
    }
  }
  protected double rankBuilder(Builder builder, LegacyAiScoreContext ctx) {
    if (!ctx.getMajorOwners().contains(getPlayer())) {
      return -3.0; // builder in enemy object penalty
    }
    if (ctx.getChanceToClose() < 0.55) return 0.0;
    double rating = 0.0;
    // builder placed in object
    if (builder.getFeature() instanceof City) {
      rating += 1.5;
    } else {
      rating += 0.5;
    }

    TradersAndBuildersGame tb = getGame().getTradersAndBuildersGame();
    // builder used on object
    if (tb.getBuilderState() == BuilderState.ACTIVATED) {
      rating += 3.5;
    }
    return rating;
  }