From 25e5813fccf3ed84c9e3174b47c9c157aef9a97a Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Fri, 20 Mar 2026 20:35:02 -0400 Subject: [PATCH 1/2] AI frame count tests --- include/config/ai.h | 8 +++ src/battle_ai_main.c | 24 ++++---- test/battle/ai/ai.c | 136 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 12 deletions(-) diff --git a/include/config/ai.h b/include/config/ai.h index 50abcbd472..e2708ed372 100644 --- a/include/config/ai.h +++ b/include/config/ai.h @@ -1,6 +1,14 @@ #ifndef GUARD_CONFIG_AI_H #define GUARD_CONFIG_AI_H +// Frame count references; tests inflate by ~5 frames, actual frame counts are lower +#define AI_FRAME_CEILING_SINGLES_NO_FLAGS 8 +#define AI_FRAME_CEILING_SINGLES_SMART_TRAINER 18 +#define AI_FRAME_CEILING_DOUBLES_NO_FLAGS 44 +#define AI_FRAME_CEILING_DOUBLES_SMART_TRAINER 77 +#define AI_FRAME_CEILING_STEVEN_MULTI 37 +#define AI_FRAME_CEILING_STEVEN_MULTI_SMART_TRAINER 46 + // For the details on what specific factors the switching functions are considering, go read the corresponding function inside ShouldSwitch in src/battle_ai_switch_items.c // These configuration options control how likely the AI is to switch if it determines that a switch meets all of its criteria // Think of them almost like success rates; if the AI has determined that it needs to switch out to hit Wonder Guard, how often do you want it to actually take that course of action? Etc. diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 78aac2bdc3..dad6871384 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -144,15 +144,19 @@ static s32 (*const sBattleAiFuncTable[])(enum BattlerId, enum BattlerId, enum Mo void AIDebugTimerStart() { // Set delay timer to count how long it takes for AI to choose action/move - gBattleStruct->aiDelayTimer = gMain.vblankCounter1; - CycleCountStart(); + if (TESTING || DEBUG_AI_DELAY_TIMER) + gBattleStruct->aiDelayTimer = gMain.vblankCounter1; + if (!TESTING) + CycleCountStart(); } void AIDebugTimerEnd() { // We add to existing to compound multiple calls - gBattleStruct->aiDelayFrames += gMain.vblankCounter1 - gBattleStruct->aiDelayTimer; - gBattleStruct->aiDelayCycles += CycleCountEnd(); + if (TESTING || DEBUG_AI_DELAY_TIMER) + gBattleStruct->aiDelayFrames += gMain.vblankCounter1 - gBattleStruct->aiDelayTimer; + if (!TESTING) + gBattleStruct->aiDelayCycles += CycleCountEnd(); } void BattleAI_SetupItems(void) @@ -384,8 +388,7 @@ void ComputeBattlerDecisions(enum BattlerId battler) gAiLogicData->aiCalcInProgress = TRUE; - if (DEBUG_AI_DELAY_TIMER) - AIDebugTimerStart(); + AIDebugTimerStart(); // Setup battler and prediction data BattleAI_SetupAIData(0xF, battler); @@ -406,8 +409,7 @@ void ComputeBattlerDecisions(enum BattlerId battler) BattlerChooseNonMoveAction(); ModifySwitchAfterMoveScoring(battler); - if (DEBUG_AI_DELAY_TIMER) - AIDebugTimerEnd(); + AIDebugTimerEnd(); gAiLogicData->aiCalcInProgress = FALSE; } @@ -729,8 +731,7 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData) gAiLogicData->aiCalcInProgress = TRUE; - if (DEBUG_AI_DELAY_TIMER) - AIDebugTimerStart(); + AIDebugTimerStart(); aiData->weatherHasEffect = HasWeatherEffect(); weather = AI_GetWeather(); @@ -767,8 +768,7 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData) aiData->predictingMove = RandomPercentage(RNG_AI_PREDICT_MOVE, PREDICT_MOVE_CHANCE); } - if (DEBUG_AI_DELAY_TIMER) - AIDebugTimerEnd(); + AIDebugTimerEnd(); gAiLogicData->aiCalcInProgress = FALSE; } diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 3e0d189a12..56d0f12c5f 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -2,6 +2,142 @@ #include "test/battle.h" #include "battle_ai_util.h" +AI_SINGLE_BATTLE_TEST("AI thinking time doesn't explode (singles, no flags)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + } WHEN { + TURN { } + } THEN { + EXPECT_LT(gBattleStruct->aiDelayFrames, AI_FRAME_CEILING_SINGLES_NO_FLAGS); + } +} + +AI_SINGLE_BATTLE_TEST("AI thinking time doesn't explode (singles, smart)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_SMART_TRAINER); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + } WHEN { + TURN { } + } THEN { + EXPECT_LT(gBattleStruct->aiDelayFrames, AI_FRAME_CEILING_SINGLES_SMART_TRAINER); + } +} + +AI_DOUBLE_BATTLE_TEST("AI thinking time doesn't explode (doubles, no flags)") +{ + GIVEN { + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + } WHEN { + TURN { } + } THEN { + EXPECT_LT(gBattleStruct->aiDelayFrames, AI_FRAME_CEILING_DOUBLES_NO_FLAGS); + } +} + +AI_DOUBLE_BATTLE_TEST("AI thinking time doesn't explode (doubles, smart)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_SMART_TRAINER); + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_CELEBRATE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_FLING); } + } WHEN { + TURN { } + } THEN { + EXPECT_LT(gBattleStruct->aiDelayFrames, AI_FRAME_CEILING_DOUBLES_SMART_TRAINER); + } +} + +AI_MULTI_BATTLE_TEST("AI thinking time doesn't explode (Steven multi)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_BASIC_TRAINER); + BATTLER_AI_FLAGS(B_BATTLER_1, AI_FLAG_BASIC_TRAINER); + BATTLER_AI_FLAGS(B_BATTLER_3, AI_FLAG_CHECK_BAD_MOVE); + MULTI_PLAYER(SPECIES_ZIGZAGOON) { Level(100); Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_CELEBRATE); } + MULTI_PARTNER(SPECIES_METANG) { Level(42); Moves(MOVE_CELEBRATE, MOVE_PSYCHIC, MOVE_REFLECT, MOVE_METAL_CLAW); } + MULTI_PARTNER(SPECIES_SKARMORY) { Level(43); Moves(MOVE_TOXIC, MOVE_AERIAL_ACE, MOVE_PROTECT, MOVE_STEEL_WING); } + MULTI_PARTNER(SPECIES_AGGRON) { Level(44); Moves(MOVE_THUNDER, MOVE_PROTECT, MOVE_SOLARBEAM, MOVE_DRAGON_CLAW); } + MULTI_OPPONENT_A(SPECIES_MIGHTYENA) { Level(42); Moves(MOVE_CELEBRATE, MOVE_SCARY_FACE, MOVE_ASSURANCE, MOVE_SWAGGER); } + MULTI_OPPONENT_A(SPECIES_CROBAT) { Level(43); Moves(MOVE_HAZE, MOVE_BITE, MOVE_AIR_CUTTER, MOVE_QUICK_GUARD); } + MULTI_OPPONENT_A(SPECIES_CAMERUPT) { Level(44); Moves(MOVE_YAWN, MOVE_TAKE_DOWN, MOVE_CURSE, MOVE_EARTH_POWER); } + MULTI_OPPONENT_B(SPECIES_CAMERUPT) { Level(36); Moves(MOVE_TAKE_DOWN, MOVE_CELEBRATE, MOVE_EARTH_POWER, MOVE_LAVA_PLUME); } + MULTI_OPPONENT_B(SPECIES_MIGHTYENA) { Level(38); Moves(MOVE_TAUNT, MOVE_SCARY_FACE, MOVE_ASSURANCE, MOVE_SWAGGER); } + MULTI_OPPONENT_B(SPECIES_GOLBAT) { Level(40); Moves(MOVE_BITE, MOVE_AIR_CUTTER, MOVE_QUICK_GUARD, MOVE_POISON_FANG); } + } WHEN { + TURN { } + } THEN { + EXPECT_LT(gBattleStruct->aiDelayFrames, AI_FRAME_CEILING_STEVEN_MULTI); + } +} + +AI_MULTI_BATTLE_TEST("AI thinking time doesn't explode (Steven multi, smart)") +{ + GIVEN { + AI_FLAGS(AI_FLAG_SMART_TRAINER); + // BATTLER_AI_FLAGS(B_BATTLER_1, AI_FLAG_SMART_TRAINER); + // BATTLER_AI_FLAGS(B_BATTLER_3, AI_FLAG_SMART_TRAINER); + MULTI_PLAYER(SPECIES_ZIGZAGOON) { Level(100); Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_CELEBRATE); } + MULTI_PARTNER(SPECIES_METANG) { Level(42); Moves(MOVE_CELEBRATE, MOVE_PSYCHIC, MOVE_REFLECT, MOVE_METAL_CLAW); } + MULTI_PARTNER(SPECIES_SKARMORY) { Level(43); Moves(MOVE_TOXIC, MOVE_AERIAL_ACE, MOVE_PROTECT, MOVE_STEEL_WING); } + MULTI_PARTNER(SPECIES_AGGRON) { Level(44); Moves(MOVE_THUNDER, MOVE_PROTECT, MOVE_SOLARBEAM, MOVE_DRAGON_CLAW); } + MULTI_OPPONENT_A(SPECIES_MIGHTYENA) { Level(42); Moves(MOVE_CELEBRATE, MOVE_SCARY_FACE, MOVE_ASSURANCE, MOVE_SWAGGER); } + MULTI_OPPONENT_A(SPECIES_CROBAT) { Level(43); Moves(MOVE_HAZE, MOVE_BITE, MOVE_AIR_CUTTER, MOVE_QUICK_GUARD); } + MULTI_OPPONENT_A(SPECIES_CAMERUPT) { Level(44); Moves(MOVE_YAWN, MOVE_TAKE_DOWN, MOVE_CURSE, MOVE_EARTH_POWER); } + MULTI_OPPONENT_B(SPECIES_CAMERUPT) { Level(36); Moves(MOVE_TAKE_DOWN, MOVE_CELEBRATE, MOVE_EARTH_POWER, MOVE_LAVA_PLUME); } + MULTI_OPPONENT_B(SPECIES_MIGHTYENA) { Level(38); Moves(MOVE_TAUNT, MOVE_SCARY_FACE, MOVE_ASSURANCE, MOVE_SWAGGER); } + MULTI_OPPONENT_B(SPECIES_GOLBAT) { Level(40); Moves(MOVE_BITE, MOVE_AIR_CUTTER, MOVE_QUICK_GUARD, MOVE_POISON_FANG); } + } WHEN { + TURN { } + } THEN { + EXPECT_LT(gBattleStruct->aiDelayFrames, AI_FRAME_CEILING_STEVEN_MULTI_SMART_TRAINER); + } +} + AI_SINGLE_BATTLE_TEST("AI prefers Bubble over Water Gun if it's slower") { u32 speedPlayer, speedAi; From 06deb592bb6f8095b5983877da36e059ea5ea36d Mon Sep 17 00:00:00 2001 From: Pawkkie Date: Fri, 20 Mar 2026 20:37:35 -0400 Subject: [PATCH 2/2] remove comment --- test/battle/ai/ai.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 56d0f12c5f..c3a4fea69d 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -119,8 +119,6 @@ AI_MULTI_BATTLE_TEST("AI thinking time doesn't explode (Steven multi, smart)") { GIVEN { AI_FLAGS(AI_FLAG_SMART_TRAINER); - // BATTLER_AI_FLAGS(B_BATTLER_1, AI_FLAG_SMART_TRAINER); - // BATTLER_AI_FLAGS(B_BATTLER_3, AI_FLAG_SMART_TRAINER); MULTI_PLAYER(SPECIES_ZIGZAGOON) { Level(100); Moves(MOVE_DOUBLE_EDGE, MOVE_BELLY_DRUM, MOVE_FLAIL, MOVE_CELEBRATE); } MULTI_PARTNER(SPECIES_METANG) { Level(42); Moves(MOVE_CELEBRATE, MOVE_PSYCHIC, MOVE_REFLECT, MOVE_METAL_CLAW); } MULTI_PARTNER(SPECIES_SKARMORY) { Level(43); Moves(MOVE_TOXIC, MOVE_AERIAL_ACE, MOVE_PROTECT, MOVE_STEEL_WING); }