Skip to content

Tips, Filters & Scoring Reference

Practical guidance for writing effective AI configs, plus reference tables for move filters, item filters, and score modifiers.


The number one mistake is creating action priorities for setup or status moves with only bias_multiplier. A status move starts at a base score of approximately 1.0. Even bias_multiplier: 10.0 gives you 10.0, which loses to Earthquake at 100.0. Always add score_bonus for non-damaging actions.

{
"id": "setup_swords_dance",
"condition": "q.pokemon.current_hp_percent > 0.7",
"action_type": "move",
"move_filter": { "names": ["swordsdance"] },
"priority": 70,
"bias_multiplier": 1.5,
"score_bonus": 80.0
}

q.matchup.score only considers STAB types and is very coarse (roughly -4 to +4). It often returns 0 for neutral matchups, making conditions like > 0 unreliable.

q.pokemon.under_threat (0.0 to 1.0) is more nuanced — it considers super-effective types, speed comparison, opponent stat boosts, and HP. Prefer q.pokemon.under_threat for setup and switch decisions.

// Less reliable — coarse, often 0 for neutral matchups
"condition": "q.matchup.score > 0"
// More reliable — considers multiple factors
"condition": "q.pokemon.under_threat < 0.3"

Rules are evaluated from highest to lowest priority. Put your most important rules at high priority numbers. Here are recommended tiers:

Priority RangeCategoryExamples
200+Hard overridesTrapped = cannot switch, max boosts = stop boosting
90—100Critical decisionsHeal at very low HP, priority move for the finish
70—85Strategic decisionsSetup when safe, Toxic against walls
50—60Tactical preferencesCoverage bonuses, screen setup
5—20General scoringAccuracy, STAB, type effectiveness

When you specify action_priorities: [...] in a child config, your rules are prepended to the parent’s rules. They do not replace them. Child rules come first and are evaluated first.

If you want to start fresh and discard all inherited rules, use an empty array:

{
"parent": "smart_trainers:expert",
"action_priorities": []
}

This clears the parent’s action priorities entirely. The same behavior applies to move_scoring_rules, switch_conditions, and gimmick_rules.

5. Use bias 0.0 to completely prevent an action type

Section titled “5. Use bias 0.0 to completely prevent an action type”

Setting bias_multiplier: 0.0 combined with a high priority is the way to prevent actions entirely:

{
"id": "never_switch_boosted",
"condition": "q.pokemon.stat_boosts.atk >= 3",
"action_type": "switch",
"priority": 200,
"bias_multiplier": 0.0
}

This prevents the AI from ever switching out a Pokemon that has +3 or more Attack boosts. The score is multiplied by 0, making it impossible to select.

You can enable debug mode to see the AI’s decision process in chat. All scores, rule matches, and final rankings are printed for each turn. This is invaluable when tuning AI configs — you can see exactly why the AI chose a particular action and which rules fired.

When using "names" or "move_ids" in move filters, use Showdown-format IDs: lowercase, no spaces, no hyphens.

Move NameShowdown ID
Swords Danceswordsdance
Dragon Dancedragondance
Will-O-Wispwillowisp
U-turnuturn
Volt Switchvoltswitch

MoLang is the expression language used for all conditions in Smart Trainers configs. Here is a quick reference:

// Comparison operators
q.pokemon.current_hp_percent < 0.5 // less than
q.pokemon.current_hp_percent > 0.5 // greater than
q.pokemon.current_hp_percent == 0.5 // equal
q.pokemon.current_hp_percent >= 0.5 // greater or equal
q.pokemon.current_hp_percent <= 0.5 // less or equal
// Logical operators
q.pokemon.current_hp_percent < 0.5 && q.target.has_status == 0 // AND
q.pokemon.current_hp_percent < 0.3 || q.pokemon.has_status // OR
!q.target.has_status // NOT
// Boolean values: 0 = false, anything > 0 = true
// So q.target.has_status alone works as a boolean check
// Math
q.move.power * q.move.type_effectiveness
q.pokemon.current_hp_percent * 100
q.target.current_hp_percent * 2.5
// Function calls (parentheses)
q.pokemon.has_type('fire')
q.pokemon.has_move('earthquake')
q.target.has_revealed_ability('levitate')
// Constant true condition
"condition": "1"

Move filters are used in action_priorities and gimmick_rules to target specific moves. All fields are optional — omitting a field means “match any.”

{
"move_filter": {
"categories": ["heal", "buff"],
"types": ["fire", "water"],
"names": ["swordsdance", "dragondance"],
"move_ids": ["earthquake"],
"condition": "q.move.power > 80"
}
}
FieldTypeDescription
categoriesstring[]Match moves with any of these AI categories.
typesstring[]Match moves of any of these elemental types.
namesstring[]Match specific move names (Showdown IDs).
move_idsstring[]Same as names — both are checked.
conditionMolangAdditional Molang condition. Gives access to q.move.* queries.

Item filters are used in usageRules within battle bag configs to target specific items.

{
"itemFilter": {
"categories": ["HEALING"],
"items": ["cobblemon:full_restore"]
}
}
FieldTypeDescription
categoriesstring[]Match items with any of these categories.
itemsstring[]Match specific item IDs.
conditionMolangAdditional Molang condition.

Score modifiers are used in move_scoring_rules. Each rule has a score_modifier with a type and value. The value field is always a Molang expression, so it can reference queries dynamically.

TypeBehaviorExample
multiplynew_score = old_score * value\{"type": "multiply", "value": "1.5"\}
addnew_score = old_score + value\{"type": "add", "value": "50"\}
setnew_score = value (ignores old score)\{"type": "set", "value": "0"\}
scriptnew_score = value (Molang expression returns the final value)\{"type": "script", "value": "q.move.power * q.move.type_effectiveness"\}

Because the value field is a Molang expression, you can write dynamic modifiers:

{
"type": "multiply",
"value": "q.move.type_effectiveness"
}

This multiplies the move’s score by its type effectiveness (2.0 for super effective, 0.5 for not very effective, etc.).