Skip to main content
The TeamValidator class handles team validation and learnset checking for Pokemon Showdown. It ensures teams comply with format rules, move legality, stat constraints, and event restrictions.

Constructor

Creates a new TeamValidator instance for a specific format.
new TeamValidator(format: string | Format, dex?: ModdedDex)
format
string | Format
Format name (e.g., “gen9ou”, “gen8vgc2022”) or Format object
dex
ModdedDex
ModdedDex instance. Defaults to the standard Dex.
import { TeamValidator, Teams } from './sim';

const validator = new TeamValidator('gen9ou');
const team = Teams.unpack('Garchomp||choiceband|roughskin|outrage,earthquake,stoneedge,firefang|Jolly|,252,,,4,252|||||');

const problems = validator.validateTeam(team);
if (problems) {
  console.log('Invalid team:', problems);
} else {
  console.log('Team is valid!');
}

Properties

format
Format
The Format object for this validator
dex
ModdedDex
The ModdedDex instance used for validation (adjusted for the format)
gen
number
The generation number for this format
ruleTable
RuleTable
The parsed rule table for this format
minSourceGen
number
Minimum generation a Pokemon must be from (e.g., 6 if Pentagon required)

Methods

validateTeam()

Validates an entire team for format legality.
validateTeam(
  team: PokemonSet[] | null,
  options?: {
    removeNicknames?: boolean,
    skipSets?: { [name: string]: { [key: string]: boolean } }
  }
): string[] | null
team
PokemonSet[] | null
Team to validate (null for formats with auto-generated teams)
options
object
Validation options
removeNicknames
boolean
Remove nicknames and use species names instead
skipSets
{ [name: string]: { [key: string]: boolean } }
Skip validation for specific sets
return
string[] | null
Array of problem strings if invalid, or null if valid
import { TeamValidator, Teams } from './sim';

const validator = new TeamValidator('gen9ou');
const team = Teams.import(`
Garchomp @ Choice Band  
Ability: Rough Skin  
EVs: 252 Atk / 4 SpD / 252 Spe  
Jolly Nature  
- Outrage  
- Earthquake  
- Stone Edge  
- Fire Fang  
`);

const problems = validator.validateTeam(team);
if (problems) {
  problems.forEach(problem => console.log(problem));
} else {
  console.log('Valid team!');
}
Team validation checks team size limits, species clauses, tier restrictions, and calls validateSet() for each Pokemon.

validateSet()

Validates a single Pokemon set.
validateSet(
  set: PokemonSet,
  teamHas: AnyObject
): string[] | null
set
PokemonSet
Pokemon set to validate
teamHas
AnyObject
Object tracking what the team has (for clause checking). Modified in place.
return
string[] | null
Array of problem strings if invalid, or null if valid
import { TeamValidator } from './sim';

const validator = new TeamValidator('gen9ou');
const set = {
  name: 'Garchomp',
  species: 'Garchomp',
  item: 'Choice Band',
  ability: 'Rough Skin',
  moves: ['Outrage', 'Earthquake', 'Stone Edge', 'Fire Fang'],
  nature: 'Jolly',
  evs: { hp: 0, atk: 252, def: 0, spa: 0, spd: 4, spe: 252 },
  ivs: { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 },
  level: 100,
  gender: ''
};

const teamHas = {};
const problems = validator.validateSet(set, teamHas);

if (problems) {
  console.log('Invalid set:', problems);
} else {
  console.log('Valid set!');
  console.log('Team has:', teamHas); // Tracks species, abilities, items, etc.
}

baseValidateTeam()

Base team validation logic without custom format overrides.
baseValidateTeam(
  team: PokemonSet[] | null,
  options?: {
    removeNicknames?: boolean,
    skipSets?: { [name: string]: { [key: string]: boolean } }
  }
): string[] | null
Same parameters and return type as validateTeam(), but skips format-specific validateTeam overrides.
import { TeamValidator } from './sim';

const validator = new TeamValidator('gen9ou');
const team = [/* ... */];

// Uses base validation only
const problems = validator.baseValidateTeam(team);

validateStats()

Validates EVs, IVs, Hidden Power, and stat-related legality.
validateStats(
  set: PokemonSet,
  species: Species,
  setSources: PokemonSources,
  pokemonGoProblems: string[] | null
): string[]
set
PokemonSet
Pokemon set to validate
species
Species
Species data
setSources
PokemonSources
Possible sources for this Pokemon
pokemonGoProblems
string[] | null
Pokemon GO validation problems
return
string[]
Array of stat-related problems
const set = {
  species: 'Garchomp',
  evs: { hp: 252, atk: 252, def: 252, spa: 0, spd: 0, spe: 0 }, // 756 total
  ivs: { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 },
  // ... other properties
};

const problems = validator.validateStats(set, species, setSources, null);
// Returns: ["Garchomp has 756 total EVs, which is more than this format's limit of 510."]
validateStats() enforces:
  • EV limits (default 510 total, 252 per stat)
  • IV ranges (0-31, or 0-30 for Gen 1-2 DVs)
  • Hidden Power type matching IVs
  • Legendary 3-perfect-IV requirement (Gen 6+)
  • Gen 1-2 DV consistency (HP, gender, shininess)
  • Bottle Cap hyper training legality

getValidationSpecies()

Determines the out-of-battle and tier species for validation.
getValidationSpecies(set: PokemonSet): {
  outOfBattleSpecies: Species,
  tierSpecies: Species
}
set
PokemonSet
Pokemon set to analyze
return
{ outOfBattleSpecies: Species, tierSpecies: Species }
Object containing both species forms
outOfBattleSpecies
Species
Species used for learnset/event checking
tierSpecies
Species
Species used for tier/banlists (includes Mega/Primal forms)
const set = {
  species: 'Garchomp',
  item: 'Garchompite',
  // ...
};

const { outOfBattleSpecies, tierSpecies } = validator.getValidationSpecies(set);
console.log(outOfBattleSpecies.name); // "Garchomp"
console.log(tierSpecies.name);        // "Garchomp-Mega" (if obtainableformes rule)

getEventOnlyData()

Retrieves event data for event-only Pokemon.
getEventOnlyData(
  species: Species,
  noRecurse?: boolean
): { species: Species, eventData: EventInfo[] } | null
species
Species
Species to check
noRecurse
boolean
Don’t check prevo for event data
return
{ species: Species, eventData: EventInfo[] } | null
Event data object, or null if not event-only
const dex = validator.dex;
const mew = dex.species.get('Mew');
const eventData = validator.getEventOnlyData(mew);

if (eventData) {
  console.log(`${eventData.species.name} has ${eventData.eventData.length} events`);
  eventData.eventData.forEach((event, i) => {
    console.log(`Event ${i}: Gen ${event.generation}, Level ${event.level}`);
  });
}

PokemonSources Class

The PokemonSources class represents possible sources for obtaining a Pokemon with a specific set.
class PokemonSources {
  sources: PokemonSource[];      // Specific sources (e.g., "7S0", "8E")
  sourcesBefore: number;         // Any source from this gen or earlier
  sourcesAfter: number;          // Requires this gen or later
  isHidden: boolean | null;      // Has Hidden Ability
  limitedEggMoves?: ID[] | null; // Egg moves requiring specific fathers
  moveEvoCarryCount: number;     // Moves requiring evolution
  dreamWorldMoveCount: number;   // Dream World moves
  // ... additional properties
}

Constructor

new PokemonSources(sourcesBefore?: number, sourcesAfter?: number)
sourcesBefore
number
default:0
Allow all sources from this generation or earlier
sourcesAfter
number
default:0
Require sources from this generation or later
// Allow any Gen 8 or earlier source
const sources = new PokemonSources(8, 0);

// Require Gen 6+ sources (Pentagon)
const gen6plus = new PokemonSources(0, 6);

Methods

add()

Adds a specific source to the set.
add(source: PokemonSource, limitedEggMove?: ID | null): void
const sources = new PokemonSources();
sources.add('8S0');      // Event source
sources.add('7E');       // Egg source
sources.add('6L5');      // Level-up move in Gen 6

addGen()

Adds all sources from a generation or earlier.
addGen(sourceGen: number): void
const sources = new PokemonSources();
sources.addGen(7); // Allow all Gen 7 and earlier sources

minSourceGen() / maxSourceGen()

Gets the minimum or maximum generation in the source set.
minSourceGen(): number
maxSourceGen(): number
const sources = new PokemonSources();
sources.add('6L1');
sources.add('8M2');

console.log(sources.minSourceGen()); // 6
console.log(sources.maxSourceGen()); // 8

intersectWith()

Intersects this source set with another (for combining move restrictions).
intersectWith(other: PokemonSources): void
const sources1 = new PokemonSources();
sources1.add('7E');
sources1.add('8V');

const sources2 = new PokemonSources();
sources2.add('8V');
sources2.add('8L1');

// Keep only common sources
sources1.intersectWith(sources2);
// sources1.sources now contains only '8V'

Source Strings

Pokemon sources are encoded as strings:
// Format: [GEN][TYPE][INDEX] [SPECIES]
// Examples:
"8M2"      // TM/TR move in Gen 8
"7L1"      // Level-up move at level 1 in Gen 7
"6E"       // Egg move in Gen 6
"7S0"      // Event #0 in Gen 7
"5D"       // Dream World in Gen 5
"8V"       // Virtual Console/Let's Go transfer
"1ET"      // Tradeback egg in Gen 1

Validation Examples

Basic Team Validation

import { TeamValidator, Teams } from './sim';

const validator = new TeamValidator('gen9ou');

const teamString = `
Garchomp @ Choice Band  
Ability: Rough Skin  
EVs: 252 Atk / 4 SpD / 252 Spe  
Jolly Nature  
- Outrage  
- Earthquake  
- Stone Edge  
- Fire Fang  

Great Tusk @ Leftovers  
Ability: Protosynthesis  
EVs: 252 HP / 4 Atk / 252 Spe  
Jolly Nature  
- Earthquake  
- Rapid Spin  
- Ice Spinner  
- Knock Off  
`;

const team = Teams.import(teamString);
const problems = validator.validateTeam(team);

if (problems) {
  console.log('Team validation failed:');
  problems.forEach(problem => console.log(`  - ${problem}`));
} else {
  console.log('Team is legal!');
}

Validating Event Pokemon

import { TeamValidator, Teams } from './sim';

const validator = new TeamValidator('gen8ou');

// Victini with V-create (event-exclusive move)
const team = Teams.import(`
Victini @ Choice Band  
Ability: Victory Star  
EVs: 252 Atk / 4 SpD / 252 Spe  
Jolly Nature  
- V-create  
- Bolt Strike  
- U-turn  
- Trick  
`);

const problems = validator.validateTeam(team);
if (problems) {
  console.log('Event validation:', problems);
  // Will check if Victini's event allows this move combination
}

Checking Move Legality

import { TeamValidator, Teams } from './sim';

const validator = new TeamValidator('gen9ou');

// Illegal: Garchomp can't learn both Dragon Dance and Outrage
const illegalTeam = Teams.import(`
Garchomp @ Choice Band  
Ability: Rough Skin  
EVs: 252 Atk / 4 SpD / 252 Spe  
Jolly Nature  
- Outrage  
- Dragon Dance  
- Earthquake  
- Stone Edge  
`);

const problems = validator.validateTeam(illegalTeam);
if (problems) {
  console.log('Move legality issues:');
  problems.forEach(problem => console.log(`  ${problem}`));
}

VGC Format Validation

import { TeamValidator, Teams } from './sim';

// VGC 2023 Series 1
const validator = new TeamValidator('gen9vgc2023series1');

const vgcTeam = Teams.import(`
Flutter Mane @ Choice Specs  
Ability: Protosynthesis  
Level: 50  
EVs: 4 HP / 252 SpA / 252 Spe  
Timid Nature  
IVs: 0 Atk  
- Shadow Ball  
- Moonblast  
- Icy Wind  
- Thunderbolt  

Iron Hands @ Assault Vest  
Ability: Quark Drive  
Level: 50  
EVs: 252 HP / 252 Atk / 4 SpD  
Adamant Nature  
- Fake Out  
- Close Combat  
- Thunder Punch  
- Heavy Slam  
`);

const problems = validator.validateTeam(vgcTeam);
if (!problems) {
  console.log('VGC team is legal!');
}

Static Methods

fillStats()

Fills missing stats with default values.
static TeamValidator.fillStats(
  stats: Partial<StatsTable> | null,
  fillValue: number
): StatsTable
stats
Partial<StatsTable> | null
Partial stats object to fill
fillValue
number
Value to use for missing stats
return
StatsTable
Complete stats object with all six stats
import { TeamValidator } from './sim';

// Fill EVs with 0
const evs = TeamValidator.fillStats({ atk: 252, spe: 252 }, 0);
// Result: { hp: 0, atk: 252, def: 0, spa: 0, spd: 0, spe: 252 }

// Fill IVs with 31
const ivs = TeamValidator.fillStats(null, 31);
// Result: { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 }

Error Handling

import { TeamValidator, Teams } from './sim';

try {
  const validator = new TeamValidator('invalidformat');
} catch (error) {
  console.error('Invalid format:', error.message);
  // Error: format 'invalidformat' should be a 'Format', but was a 'undefined'
}

const validator = new TeamValidator('gen9ou');
const team = Teams.unpack('InvalidData');

if (!team) {
  console.error('Failed to parse team');
} else {
  const problems = validator.validateTeam(team);
  if (problems) {
    console.log('Validation errors:');
    problems.forEach((problem, i) => {
      console.log(`${i + 1}. ${problem}`);
    });
  }
}

See Also

  • Teams - Team format conversion and generation
  • Dex - Pokemon data access
  • Formats - Format definitions and rules

Build docs developers (and LLMs) love