Skip to content

Scoring Deep Dive

This page covers the complete decision-making pipeline, traces through a concrete scoring example, and walks through building a Gym Leader AI step by step.

Every turn, the AI follows this 8-step pipeline in order:

  1. Skill Check Roll against the skill level. If the roll fails, pick a random move and skip everything below. Skill level 5 never fails. Skill level 0 always fails (pure random).

  2. Score Moves For each available move:

    • If use_damage_calc is enabled: score = estimated damage % x 100
    • Otherwise: score = base power (minimum 1.0 for status moves)
    • Apply status_move_bias if the move is a status move
    • Apply move_scoring_rules (in definition order)
    • Final: score x move_bias
  3. Score Switches For each available switch-in:

    • Base score = 15.0
    • Add matchup bonus (type advantage vs current opponent)
    • Add HP bonus (healthier candidates score higher)
    • Apply switch_conditions (in definition order)
    • Final: score x switch_bias
  4. Score Items For each available bag item x eligible target:

    • Base score = item’s baseScore
    • Apply healing urgency (HP-based multiplier)
    • Apply revive bonus, status cure bonus
    • Apply item usage rules from bag config
    • Final: score x item_bias
  5. Apply Action Priorities For each scored action x each priority rule (sorted by priority, highest first):

    • Check condition (MoLang)
    • Check action type match
    • Check move filter (if applicable)
    • If all pass: score = score x bias_multiplier + score_bonus
  6. Evaluate Gimmick Rules For each gimmick rule (sorted by priority, first match wins):

    • Check gimmick is available (Mega/Tera/Dynamax/Z-Move)
    • Evaluate condition (MoLang)
    • If passes: apply bias_multiplier to moves (filtered by move_filter if set)
    • Attach gimmick ID to the final move response
  7. Apply Personality Modifiers

    • Aggression: boost/penalize damaging vs status moves
    • Risk Tolerance: boost/penalize risky moves (low accuracy, recoil)
    • Setup Preference: boost/penalize setup moves
    • Switchiness: boost/penalize switch actions
    • Item Conservatism: penalize item usage
  8. Select Best Action

    • Find the highest score
    • Collect all actions within max_select_margin of the best
    • Weighted random pick (higher-scoring candidates are more likely)

Let’s trace through a concrete example. Garchomp (Dragon/Ground) has Earthquake, Dragon Claw, Swords Dance, and Stone Edge against a Tyranitar (Rock/Dark).

Config: gen4_cynthia (skill 5, use_damage_calc: true, move_bias: 1.1, status_move_bias: 1.0)

1. Base score with damage calc: ~55% damage -> 55.0
2. Move scoring rules:
- STAB bonus (is_stab = true): 55.0 x 1.5 = 82.5
- Type effectiveness (Ground vs Rock/Dark = 2.0): 82.5 x 2.0 = 165.0
- Accuracy (100): no change
3. Move bias: 165.0 x 1.1 = 181.5
4. Action priorities: no special rules match
5. Personality: slight aggression boost
Final: ~185

Earthquake scores very high because it has STAB (Garchomp is Ground-type) and is super effective against Tyranitar (Ground hits Rock for 2x). The damage calc estimates 55% damage, and multipliers stack on top.

1. Base score: 1.0 (status moves start at 1.0)
2. Move scoring rules:
- STAB: no (status move)
- Type effectiveness: neutral (doesn't apply meaningfully)
- Status redundant: no (not status-inflicting)
3. Status move bias: 1.0 x 1.0 = 1.0
4. Move bias: 1.0 x 1.1 = 1.1
5. Action priorities:
- "cynthia_swords_dance": HP > 80%? yes. under_threat < 0.3? yes.
- Score: 1.1 x 3.5 + 130.0 = 133.85
6. Personality: setup preference boost
Final: ~140

Despite the massive score_bonus of 130.0, Swords Dance still scores below Earthquake. This is the correct behavior — when Garchomp has a super effective STAB move that deals over half the opponent’s HP, it should attack rather than set up.

Earthquake wins (185 > 140). The AI attacks because Earthquake deals super effective STAB damage. But if the opponent were a neutral matchup with lower damage output, Swords Dance could win — the conditions (HP > 80%, under_threat < 0.3) would still pass, and without the 2x type effectiveness multiplier on Earthquake, the scores would be much closer.

Let’s build a Fire-type Gym Leader AI step by step, starting from a preset and adding custom behavior.

Create data/mypack/battle_ai/fire_gym.json:

{
"parent": "smart_trainers:hard",
"max_select_margin": 0.2,
"personality": {
"enabled": true,
"base_personality": {
"aggression": 0.75,
"risk_tolerance": 0.5,
"setup_preference": 0.6,
"switchiness": 0.3,
"item_conservatism": 0.4
},
"trait_variance": 0.1
}
}

This inherits from hard (skill 4, type awareness, hazard setup) and adds an aggressive personality that likes setup moves. The max_select_margin of 0.2 means the AI picks from actions within 20% of the best score, giving it a slight unpredictability while still playing well.

What this inherits from hard:

  • Skill level 4 (only random 5% of the time)
  • Full type effectiveness scoring
  • Hazard awareness
  • Ability immunity checks
  • All of hard’s action priorities, move scoring rules, and switch conditions

Now add rules that make this feel like a Fire-type specialist:

{
"parent": "smart_trainers:hard",
"max_select_margin": 0.2,
"personality": {
"enabled": true,
"base_personality": {
"aggression": 0.75,
"setup_preference": 0.6,
"switchiness": 0.3
},
"trait_variance": 0.1
},
"action_priorities": [
{
"id": "setup_sunny_day",
"condition": "!q.field.has_weather('SunnyDay') && q.pokemon.current_hp_percent > 0.7",
"action_type": "move",
"move_filter": { "names": ["sunnyday"] },
"priority": 85,
"bias_multiplier": 3.0,
"score_bonus": 100.0
},
{
"id": "solar_beam_in_sun",
"condition": "q.field.has_weather('SunnyDay')",
"action_type": "move",
"move_filter": { "names": ["solarbeam"] },
"priority": 80,
"bias_multiplier": 2.5,
"score_bonus": 80.0
},
{
"id": "will_o_wisp_physical",
"condition": "q.target.is_physical_attacker && !q.target.has_status",
"action_type": "move",
"move_filter": { "names": ["willowisp"] },
"priority": 75,
"bias_multiplier": 2.0,
"score_bonus": 70.0
}
]
}

What these rules do:

  • setup_sunny_day (priority 85): When the sun isn’t up and we’re healthy (>70% HP), strongly prefer Sunny Day. The score_bonus: 100.0 makes this status move competitive with attacks.
  • solar_beam_in_sun (priority 80): Once Sunny Day is active, boost Solar Beam’s score. It fires instantly in sun and provides Grass-type coverage against Water/Ground/Rock types that threaten Fire Pokemon.
  • will_o_wisp_physical (priority 75): Burn physical attackers that don’t already have a status condition. Cuts their Attack in half, which is extremely valuable for a Fire Gym Leader.

Because action_priorities is a non-empty array, these three rules are prepended to hard’s existing rules. The Gym Leader gets all of hard’s strategic behavior plus these Fire-specific priorities.

Create data/mypack/battle_bags/fire_gym.json:

{
"enabled": true,
"maxItemsPerBattle": 2,
"items": [
{
"itemId": "cobblemon:hyper_potion",
"quantity": 2,
"category": "HEALING",
"baseScore": 3.0
}
],
"usageRules": [
{
"id": "heal_ace",
"condition": "q.pokemon.current_hp_percent < 0.35",
"itemFilter": { "categories": ["HEALING"] },
"priority": 20,
"biasMultiplier": 4.0,
"scoreBonus": 140.0
}
]
}

The Gym Leader carries 2 Hyper Potions and can use up to 2 items per battle. The usage rule gives a massive scoreBonus of 140.0 when the active Pokemon drops below 35% HP, making the heal competitive with attacking moves. This creates the classic Gym Leader behavior of healing their ace Pokemon at a critical moment.

Set the NPC’s AI config to mypack:fire_gym and battle bag to mypack:fire_gym.

The Gym Leader now:

  • Plays at skill level 4 with full type awareness (inherited from hard)
  • Sets up Sunny Day when healthy
  • Uses Solar Beam for coverage in sun
  • Burns physical attackers with Will-o-Wisp
  • Heals its Pokemon when they drop below 35% HP
  • Has an aggressive, setup-oriented personality with slight variance between battles