Improve AI move & switch heuristics; add tests #11
No reviewers
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
pokebedrock/pokebedrock-showdown!11
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/ai-decision-issues"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Enhance strategic-AI scoring and add regression tests. - HeuristicEngine: add proactive-switch thresholds (PROACTIVE_SWITCH_DELTA/FLOOR) and trigger to avoid flip-flopping on small edges. - MoveEvaluator: special-case Counter/Mirror Coat/Metal Burst, refine Encore scoring, add Destiny Bond and Baton Pass scoring, and minor cleanup (recovery move formatting). - SwitchEvaluator: add entry-synergy bonuses (terrain seeds, Intimidate, weather setters) and improve bestAttackingDamage by seeding STAB proxy moves and conservative coverage probes. - Tests: add comprehensive move-evaluator tests and extend switch-evaluator tests to cover STAB-awareness and terrain/Intimidate synergies. These changes fix AI issues like DB spamming, Baton Pass being undervalued, missed Counter picks, terrain-seed blindspots, and unrevealed-STAB underestimation.
Summary by CodeRabbit
New Features
Refactor
Tests
📝 Walkthrough
Walkthrough
Enhances Strategic-AI: records per-mon "last move failed", extends DamageCalc for Paradox/Solar boosts and conditional BP, enriches move/status scoring and setup-window logic, adds switch-in entry-synergy and survivability gating, rewrites proactive switching, and adds extensive regression tests.
Changes
Strategic AI Move & Switch Evaluation
sim/tools/strategic-ai/state/BattleStateTracker.ts,sim/tools/strategic-ai/engines/Engine.ts,sim/tools/player-ai.ts,test/sim/tools/strategic-ai/force-switch.jslastMoveFailed,_lastMoverattribution, exposeslastMoveFailedByMonin EngineContext and test contexts so evaluators can use "previous move failed" signals.sim/tools/strategic-ai/mechanics/DamageCalc.ts,test/sim/tools/strategic-ai/damage-calc.jsforceCritfor guaranteed-crit moves, and applies Protosynthesis/Quark Drive and Solar Power offensive/defensive multipliers with helpers; tests cover Paradox abilities and Solar Power.sim/tools/strategic-ai/mechanics/SwitchEvaluator.tsentrySynergyBonus(seeds, Intimidate, weather-setters, weather-speed tempo, Booster Energy for Paradox), absorb/Weakness Policy recognition, builds revealed-first move pools with STAB proxies and optional coverage proxies inbestAttackingDamage, and exportsscaledSpeedwithparadoxSpeedActiveandterrainsupport.test/sim/tools/strategic-ai/switch-evaluator.jsscaledSpeedmultipliers, and Weakness Policy baiting.sim/tools/strategic-ai/mechanics/MoveEvaluator.tsattackerLastMoveFailedto context; special-cases Counter-family and Sucker Punch; replaces simple priority-scoring with richer outspeed/KO/insurance/chip heuristics; restructures status handling (encore/destinybond/batonpass/yawn/endure); refactors boost scoring with setup-window helpers and updates hazard valuation.test/sim/tools/strategic-ai/move-evaluator.jsevaluateMovetests: Destiny Bond, Baton Pass, Counter/Mirror Coat, Encore, priority/insurance moves, Sucker Punch, setup-window bonuses (Sash/Sturdy/Weakness Policy), hazard priorities, Yawn/stall combos, conditional-BP moves, always-crit interactions, and Endure/berry combos.sim/tools/strategic-ai/engines/HeuristicEngine.tsmaybeSwitchOut; precomputes best bench candidate; usesscaledSpeed; adds proactive-switch thresholds and bench-survives-entry checks; early vetoes for consumable/field boosts and setup preservation;emergencySwitchoverride and priority-KO short-circuit; refined sacrifice/entry-safety gates; refactors doubles target formatting and adds setup-detection helpers.🎯 4 (Complex) | ⏱️ ~45 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
📝 Generate docstrings
🧪 Generate unit tests (beta)
fix/ai-decision-issuesThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
❤️ Share
Comment
@coderabbitai helpto get the list of available commands and usage tips.Pull request overview
This pull request updates the Strategic AI’s move and switch scoring to better account for hidden STAB threats, entry-triggered synergies (terrain seeds, Intimidate, weather/terrain setters), and several previously mis-scored utility moves, and adds regression tests to lock in the intended behavior.
Changes:
bestAttackingDamageto seed STAB proxies (and broader coverage probes when allowed).Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
sim/tools/strategic-ai/mechanics/SwitchEvaluator.tssim/tools/strategic-ai/mechanics/MoveEvaluator.tssim/tools/strategic-ai/engines/HeuristicEngine.tstest/sim/tools/strategic-ai/switch-evaluator.jstest/sim/tools/strategic-ai/move-evaluator.jsComments suppressed due to low confidence (2)
sim/tools/strategic-ai/mechanics/MoveEvaluator.ts:503
defender.choiceLockedalone isn’t enough to treat the move as "almost guaranteed": a Choice-locked foe can be locked into a Status move (or any non-damaging move), in which case Metal Burst will fail because it requires taking damage earlier in the same turn. The scoring should only apply the big Choice-lock bonus when the locked move is damaging (or otherwise strongly predicted to deal damage).test/sim/tools/strategic-ai/move-evaluator.js:187
weOutspeed: false, but Counter/Mirror Coat have priority -5 and usually act after the foe regardless of Speed. If the evaluator is updated to reflect priority instead of Speed tier, these tests should stop depending onweOutspeedfor correctness.💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
@ -322,0 +423,4 @@if (defender.status) return { moveId, score: -10, rationale: "yawnRedundant" };// Electric/Misty Terrain only block sleep on *grounded* targets;// an airborne / Levitate foe can still be put to sleep.const yawnBlockedByTerrain =Counter and Mirror Coat have priority -5 (see move data), so they will usually move after the foe regardless of Speed. Gating the high score on
!ctx.weOutspeedwill undervalue them when we are faster, even though they still work; consider keying this onmove.priority/move id (only Metal Burst needs the "must be slower" check).This issue also appears on line 485 of the same file.
Destiny Bond must be set before the user faints; if the foe is faster and can KO us this turn, Destiny Bond often won’t execute and won’t trade. The current logic rewards
!ctx.weOutspeed(foe faster), which is backwards for the common "low HP desperation trade" use-case; consider favoringctx.weOutspeed(or otherwise factoring in whether we’re expected to survive the incoming hit).@ -129,9 +129,340 @@ export function evaluateMatchup(score -= foeBoosts * 4;Unaware does not negate Intimidate’s Attack drop on the Unaware Pokémon; it only ignores the opponent’s stat changes during damage calculation. Treating
foe.ability === "unaware"as cancelling the Intimidate bonus will undervalue Intimidate vs Unaware physical attackers.@ -135,0 +274,4 @@// triggers on entry instead, granting +30%/+50% to the highest// stat for the duration of the stay.if (item === "boosterenergy") {const isProto = ability === "protosynthesis";bestAttackingDamagenow always adds STAB proxies (and, whenuseKnownOnlyis false, coverage proxies) even when the attacker already has revealed damaging moves. For our own side,revealedMovesis seeded from the request and should already represent the full moveset, so these extra proxies can overestimate our actual damage output and skew matchup scores/switching decisions.@ -0,0 +110,4 @@assert(result.score < 0,`Destiny Bond into a faster KO should be negative — it whiffs ` +`before resolving (got ${result.score})`);});These Destiny Bond tests assume Destiny Bond is best when the foe is faster (
weOutspeed: false), but Destiny Bond generally needs to execute before we faint to force a trade. If the scoring is corrected to prefer situations where we can actually get Destiny Bond up (often when we outspeed or can survive the hit), these test expectations should be updated accordingly.This issue also appears on line 160 of the same file.
Actionable comments posted: 3
🧹 Nitpick comments (2)
🤖 Prompt for all review comments with AI agents
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID:
85155ef0-141a-4af7-bdcb-944e9f415d6a📥 Commits
Reviewing files that changed from the base of the PR and between
aece3520cfand6f079bb206.📒 Files selected for processing (5)
sim/tools/strategic-ai/engines/HeuristicEngine.tssim/tools/strategic-ai/mechanics/MoveEvaluator.tssim/tools/strategic-ai/mechanics/SwitchEvaluator.tstest/sim/tools/strategic-ai/move-evaluator.jstest/sim/tools/strategic-ai/switch-evaluator.js⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
Repository: smell-of-curry/pokebedrock-showdown
Length of output: 221
🏁 Script executed:
Repository: smell-of-curry/pokebedrock-showdown
Length of output: 1956
🏁 Script executed:
Repository: smell-of-curry/pokebedrock-showdown
Length of output: 333
🏁 Script executed:
Repository: smell-of-curry/pokebedrock-showdown
Length of output: 1312
🏁 Script executed:
Repository: smell-of-curry/pokebedrock-showdown
Length of output: 122
Counter and Mirror Coat are undervalued when the player outspeeds due to priority not being considered.
Counter and Mirror Coat both have -5 priority (confirmed in move data), which means they always move last regardless of Speed stats. Line 482 sets
slower = !ctx.weOutspeed, butctx.weOutspeedonly reflects base Speed comparison, not move priority.When the player outspeeds (e.g., Speed 100 vs foe Speed 50), the code sets
slower = falseand scores these moves at 12 (line 493). However, due to -5 priority, Counter/Mirror Coat will still move last, so they should be scored at 35+ (line 490), especially if the foe is choice-locked.The comment at lines 479–481 is misleading—it states outspeeding makes them whiff, but that only applies if the move could move first. With -5 priority, this never happens.
Fix: For Counter and Mirror Coat specifically, override
slowerto always betruesince priority -5 guarantees they move last. Metal Burst (priority 0) is unaffected and should use the current speed-based logic.🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
@ -129,9 +129,340 @@ export function evaluateMatchup(score -= foeBoosts * 4;⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
Unaware on the foe does not negate Intimidate's offensive impact.
Unaware ignores the opponent's stat-stage changes when the Unaware holder is dealing or taking damage. It does not erase the holder's own stages. When the foe has Unaware and our Intimidate sets foe's Atk to −1, the foe still attacks with its own −1 Atk stage; that's what reduces incoming damage on us, and Unaware doesn't bypass it. So Intimidate's matchup bonus should still apply here.
The only real wrinkles are Clear Body / Hyper Cutter / Inner Focus / Own Tempo / Defiant / Competitive on the foe (block or invert the drop), and Mirror Armor (reflect onto us) — none of which is
unaware.🐛 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
@ -135,0 +274,4 @@// triggers on entry instead, granting +30%/+50% to the highest// stat for the duration of the stay.if (item === "boosterenergy") {const isProto = ability === "protosynthesis";⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Proxy moves are hardcoded
category: "Physical", underestimating special-leaning attackers.Both the STAB proxy loop (293-311) and the coverage probe loop (312-326) emit Physical moves only.
calculateDamageselects Atk-vs-Def for Physical and SpA-vs-SpD for Special, so any attacker whose offensive leaning is Special gets clamped to their (usually low) Atk stat. Concrete consequences:useKnownOnly=true): a Magnezone that has only revealed Thunderbolt gets a Physical Steel STAB proxy computed off ~Atk 70 instead of SpA 130. The AI massively under-fears its unrevealed Flash Cannon — exactly the unrevealed-STAB blindspot this PR is trying to close.useKnownOnly=false): same problem for our own special-leaning candidates probing coverage types — the optimistic-probe rationale at lines 254-256 breaks for ~half of mons.The new regression tests don't exercise this because every attacker used (Garchomp, Tyranitar) is Physical-leaning, so they pass with
"Physical"baked in.The cleanest fix is to emit both categories per type and let the
max avgDamageselection at line 338 pick whichever side actually hurts — that way you don't need stats data (important for unrevealed foes) and you naturally model both unrevealed Iron Head and unrevealed Flash Cannon.🐛 Proposed fix
You'll probably also want a follow-up test that pits e.g. a Magnezone with only Thunderbolt revealed against a 4×-Steel-weak switch candidate to lock in the special-side regression.
📝 Committable suggestion
🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
Actionable comments posted: 3
🧹 Nitpick comments (2)
🤖 Prompt for all review comments with AI agents
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID:
8476ef2b-cdce-47bb-836c-805825b55a52📥 Commits
Reviewing files that changed from the base of the PR and between
6f079bb206andc987e24d24.📒 Files selected for processing (7)
sim/tools/strategic-ai/engines/HeuristicEngine.tssim/tools/strategic-ai/mechanics/DamageCalc.tssim/tools/strategic-ai/mechanics/MoveEvaluator.tssim/tools/strategic-ai/mechanics/SwitchEvaluator.tstest/sim/tools/strategic-ai/damage-calc.jstest/sim/tools/strategic-ai/move-evaluator.jstest/sim/tools/strategic-ai/switch-evaluator.js🚧 Files skipped from review as they are similar to previous changes (2)
@ -621,3 +701,4 @@if (id === "bodypress") {offensiveStatKey = "def";baseStat = effectiveStat(input.attacker, "def", /* ignoreNeg */ crit);} else if (id === "foulplay") {⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Respect weather suppression when activating
Protosynthesis.This helper reads raw
field.weather, soCloud Nine/Air Lockare ignored here even though the rest of the calc routes weather-dependent effects througheffectiveWeather(...). In those matchups the calc will still apply the Protosynthesis stat boost under suppressed sun.🤖 Prompt for AI Agents
@ -644,0 +730,4 @@if (ability === "solarpower" &&!physical &&(offensiveWeather === "sunnyday" || offensiveWeather === "desolateland")⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
Skip the Paradox offensive multiplier for
Foul Play.On Line 629 the move intentionally reads the target's Attack, but Lines 664-667 can still add the user's own Protosynthesis/Quark Drive boost back on top. That makes
Foul Playtoo strong whenever the user has an active Attack boost.Suggested fix
Also applies to: 664-667
🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
@ -135,0 +433,4 @@* @param move The incoming move.* @param target The Pokemon being hit.* @returns true if the move is fully neutralized.*/⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Quark Drive activation ignores Electric Terrain.
paradoxSpeedActivereceives onlyweatherbut Quark Drive should activate when Electric Terrain is up (not just with Booster Energy). At line 474, the Quark Drive branch only checkshasBooster, so a mon like Iron Valiant on Electric Terrain (set by another source) won't receive the 1.5× speed multiplier.Pass terrain into this helper and check for it:
🐛 Proposed fix
And update
scaledSpeedto pass terrain:And update
computeSpeedDeltato pass terrain:🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID:
463c3a15-5702-48d2-ae8a-cdd6186ca67d📥 Commits
Reviewing files that changed from the base of the PR and between
c987e24d24and6a52f5117a.📒 Files selected for processing (10)
sim/tools/player-ai.tssim/tools/strategic-ai/engines/Engine.tssim/tools/strategic-ai/engines/HeuristicEngine.tssim/tools/strategic-ai/mechanics/DamageCalc.tssim/tools/strategic-ai/mechanics/MoveEvaluator.tssim/tools/strategic-ai/mechanics/SwitchEvaluator.tssim/tools/strategic-ai/state/BattleStateTracker.tstest/sim/tools/strategic-ai/force-switch.jstest/sim/tools/strategic-ai/move-evaluator.jstest/sim/tools/strategic-ai/switch-evaluator.js✅ Files skipped from review due to trivial changes (2)
⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Exclude conditional priority moves from this switch veto.
This helper assumes every positive-priority attack is reliably usable. That makes the engine stay in on the strength of Sucker Punch / Fake Out / First Impression KOs that may not exist on the current turn.
🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
@ -377,0 +613,4 @@** @param ctx Move-eval context (attacker, defender, tracker).* @returns true when the attacker should be treated as "definitely* here next turn", false otherwise.⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Terrain status blockers need a grounded check.
Electric/Misty Terrain only prevent these statuses on grounded targets. These early returns currently make the AI give up legal Yawn / sleep / paralysis lines into airborne or Levitate targets whenever those terrains are up.
Also applies to: 697-718
🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Don't replace damage scoring with a flat Sucker Punch score.
This
35ignores whether the hit is actually threatening. A non-STAB or low-Attack Sucker Punch now looks just as good as a real KO as long as the foe is choice-locked into an attack. It would be safer to keep the fail-case override here and let the normal damage path score the success case.🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
@ -135,0 +362,4 @@// every Flying attack — Brave Bird / Air Slash / Acrobatics// hit it normally.(ability === "windrider" && !!move.flags?.wind)) {⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Limit Wind Rider to actual wind-flagged moves.
moveType === "Flying"makes Brave Bird / Air Slash / Acrobatics look like “free-turn” entries even though Wind Rider does not block them. That can materially over-rank Wind Rider switch-ins into normal Flying attackers.🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
@ -135,0 +420,4 @@if (isMoveNeutralizedBy(move, mon)) continue;let eff = 0;for (const t of mon.types) eff += Dex.getEffectiveness(move.type, t);if (eff > 0) return true;⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Weakness Policy baiting needs to respect actual immunities.
This helper only checks the type chart, so it will hand out the
weaknesspolicybonus for revealed moves that are super-effective on paper but deal zero in practice because of Levitate, Flash Fire, Water Absorb, Bulletproof, etc. That makes the AI switch in expecting a Policy proc that can never happen.🤖 Prompt for AI Agents
✅ Confirmed as addressed by @smell-of-curry
@ -322,0 +423,4 @@if (defender.status) return { moveId, score: -10, rationale: "yawnRedundant" };// Electric/Misty Terrain only block sleep on *grounded* targets;// an airborne / Levitate foe can still be put to sleep.const yawnBlockedByTerrain =Fixed in
433f496—evaluateCounterMovenow forcesslower = truefor Counter/Mirror Coat (their -5 priority always moves them last); Metal Burst keeps the speed-based logic. Added a regression test covering the outspeed case.Fixed in
433f496—sloweris now hardcoded totrueforcounter/mirrorcoat(priority -5), so they score on the slower path even when we outspeed. Metal Burst (+0 priority) still uses!ctx.weOutspeed.@ -322,0 +423,4 @@if (defender.status) return { moveId, score: -10, rationale: "yawnRedundant" };// Electric/Misty Terrain only block sleep on *grounded* targets;// an airborne / Levitate foe can still be put to sleep.const yawnBlockedByTerrain =Fixed in
433f496—scoreDestinyBondnow rewards moving first (we outspeed → bond resolves before the incoming KO) instead of the foe being faster, which whiffed. Tests updated.@ -0,0 +110,4 @@assert(result.score < 0,`Destiny Bond into a faster KO should be negative — it whiffs ` +`before resolving (got ${result.score})`);});Fixed in
433f496— the Destiny Bond tests now expect the high score when we outspeed, plus a new case asserting it's negative when the foe outspeeds and KOs first.@ -129,9 +129,340 @@ export function evaluateMatchup(score -= foeBoosts * 4;Fixed in
433f496— Unaware no longer cancels Intimidate's bonus (it only ignores the opponent's stages). The bonus is skipped only for real blockers (Clear Body, Hyper Cutter, Inner Focus, Own Tempo, Oblivious, Scrappy, White Smoke, Full Metal Body) and is now a penalty vs Defiant/Competitive/Mirror Armor/Guard Dog. Added a regression test.@ -129,9 +129,340 @@ export function evaluateMatchup(score -= foeBoosts * 4;Fixed in
433f496— see above: theunawarespecial-case is removed; the bonus now applies normally and only blocking/inverting abilities are excluded.@ -135,0 +274,4 @@// triggers on entry instead, granting +30%/+50% to the highest// stat for the duration of the stay.if (item === "boosterenergy") {const isProto = ability === "protosynthesis";Fixed in
433f496— for our own side (useKnownOnly === false)revealedMovesis seeded complete from the request, so we no longer add proxies on top. Proxies are now only used for the foe (incomplete set) or as a fallback when there is no revealed damaging move. Added a regression test.@ -135,0 +274,4 @@// triggers on entry instead, granting +30%/+50% to the highest// stat for the duration of the stay.if (item === "boosterenergy") {const isProto = ability === "protosynthesis";Fixed in
433f496— proxies are now emitted in both Physical and Special flavours via apushProxyhelper, so special-leaning foes' unrevealed STABs aren't clamped to Attack. Added the suggested Magnezone-vs-4×-Steel regression test.@ -644,0 +730,4 @@if (ability === "solarpower" &&!physical &&(offensiveWeather === "sunnyday" || offensiveWeather === "desolateland")Fixed in
433f496— the Paradox offensive multiplier is now skipped whenid === "foulplay", so the user's own Protosynthesis/Quark Drive Attack boost isn't added on top of the defender's Attack.@ -621,3 +701,4 @@if (id === "bodypress") {offensiveStatKey = "def";baseStat = effectiveStat(input.attacker, "def", /* ignoreNeg */ crit);} else if (id === "foulplay") {Fixed in
433f496—paradoxBoostedStatnow takes the effective weather (post Cloud Nine/Air Lock) for the sun check, so Protosynthesis won't activate under suppressed sun. Electric Terrain isn't weather, so Quark Drive's terrain path is intentionally unchanged here.@ -135,0 +433,4 @@* @param move The incoming move.* @param target The Pokemon being hit.* @returns true if the move is fully neutralized.*/Fixed in
433f496—paradoxSpeedActive/scaledSpeed/computeSpeedDeltanow thread terrain through, so Quark Drive's 1.5× Speed fires on Electric Terrain from any source, not just Booster Energy. Added a regression test.Fixed in
433f496—hasUsablePriorityKOnow skips conditional-priority moves (Fake Out, First Impression, Sucker Punch) so they no longer veto a switch on the basis of a KO that may not exist this turn.@ -377,0 +613,4 @@** @param ctx Move-eval context (attacker, defender, tracker).* @returns true when the attacker should be treated as "definitely* here next turn", false otherwise.Fixed in
433f496— Yawn and the sleep/paralysis checks now require the target to be grounded (via the tracker's groundedness logic) before treating Electric/Misty Terrain as a blocker, so airborne/Levitate targets stay legal.Fixed in
433f496— the choice-locked branch no longer returns a flat 35; it falls through to the normal damage path, which scores actual damage (so a weak/non-STAB Sucker Punch isn't treated as a KO) while keeping the status fail-case override and priority-KO bonus.@ -135,0 +362,4 @@// every Flying attack — Brave Bird / Air Slash / Acrobatics// hit it normally.(ability === "windrider" && !!move.flags?.wind)) {Fixed in
433f496— the Wind Rider clause now requiresmove.flags?.wind; themoveType === "Flying"part was removed so Brave Bird/Air Slash/Acrobatics are scored normally.@ -135,0 +420,4 @@if (isMoveNeutralizedBy(move, mon)) continue;let eff = 0;for (const t of mon.types) eff += Dex.getEffectiveness(move.type, t);if (eff > 0) return true;Fixed in
433f496—anyRevealedFoeMoveIsSuperEffectivenow skips moves the candidate is immune to (type immunity plus Levitate/Flash Fire/Water Absorb/Sap Sipper/Bulletproof/Soundproof/Wind Rider/Air Balloon, etc.) before counting effectiveness, so WP-baiting only triggers when the proc can actually happen.@smell-of-curry, thanks for the fix! Hardcodingslower = trueforcounter/mirrorcoatcorrectly reflects their -5 priority guarantee, and keeping Metal Burst on the speed-based path is the right call. Looks good!(´• ω •`) ✓
🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@ -135,0 +433,4 @@* @param move The incoming move.* @param target The Pokemon being hit.* @returns true if the move is fully neutralized.*/@smell-of-curry, thanks for the fix and the regression test! The current file confirms the terrain threading is in place —paradoxSpeedActivenow checkseTerrain || hasBoosterfor Quark Drive, andscaledSpeed/computeSpeedDeltaboth pass terrain through correctly. All good! 🎉(´。• ᵕ •。`) ♡
🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@ -377,0 +613,4 @@** @param ctx Move-eval context (attacker, defender, tracker).* @returns true when the attacker should be treated as "definitely* here next turn", false otherwise.@smell-of-curry, thanks for the fix! The updated logic in the file confirms it —yawnBlockedByTerrainnow gates ontracker.isPokemonGrounded(defender), so Electric/Misty Terrain only blocks Yawn (and sleep/paralysis) for grounded targets, leaving airborne and Levitate mons as legal targets. ✅🐇✨
🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@smell-of-curry, confirmed — theCONDITIONAL_PRIORITY_MOVESset and the guard inhasUsablePriorityKOare visible in the current file. Fake Out, First Impression, and Sucker Punch are correctly excluded before thecalculateDamagecall, so they can no longer veto a switch on a KO that may not materialise. Thanks for the quick fix!🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@ -135,0 +362,4 @@// every Flying attack — Brave Bird / Air Slash / Acrobatics// hit it normally.(ability === "windrider" && !!move.flags?.wind)) {@smell-of-curry, confirmed — restricting Wind Rider tomove.flags?.windis the correct fix. Brave Bird / Air Slash / Acrobatics will now be scored normally as unblocked hits.🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@ -135,0 +420,4 @@if (isMoveNeutralizedBy(move, mon)) continue;let eff = 0;for (const t of mon.types) eff += Dex.getEffectiveness(move.type, t);if (eff > 0) return true;@smell-of-curry, confirmed — the current file content showsanyRevealedFoeMoveIsSuperEffectivenow gates onisMoveNeutralizedBy(move, mon)before the type-effectiveness check, which covers all the relevant immunity paths (Levitate, Flash Fire, Water Absorb, Sap Sipper, Bulletproof, Soundproof, Wind Rider, Air Balloon, type chart immunity). WP-baiting will only trigger when the proc can actually land. Thanks for the quick fix!(ᵔᵕᵔ)🐇✨
🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@ -129,9 +129,340 @@ export function evaluateMatchup(score -= foeBoosts * 4;@smell-of-curry, confirmed — theunawarespecial-case is gone and the final code inentrySynergyBonuscorrectly handles all the edge cases:defiant/competitive/mirrorarmor/guarddogas punishing abilities, andclearbody/whitesmoke/fullmetalbody/hypercutter/innerfocus/owntempo/oblivious/scrappyas blockers. Looks great!(ノ◕ヮ◕)ノ*:・゚✧
🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@smell-of-curry, thanks for the fix! Falling through to the normal damage path for the choice-locked case is exactly the right call — a weak or non-STAB Sucker Punch now gets scored on its actual damage while the status fail-case override and priority-KO bonus are still applied correctly.(̶•̶‿•̶)ᓄ🐇
🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@ -644,0 +730,4 @@if (ability === "solarpower" &&!physical &&(offensiveWeather === "sunnyday" || offensiveWeather === "desolateland")@smell-of-curry, confirmed — the guard in commit433f496correctly skips the Protosynthesis/Quark Drive offensive multiplier forfoulplay, keeping the move scoped to the defender's Attack stat only. Thanks for the quick fix!(ノ◕ヮ◕)ノ*:・゚✧
🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit
433f496ab0. Configure here.Move speed uses stale predicate
Medium Severity
Switch-out logic now uses
scaledSpeed(weather abilities, Choice Scarf, paralysis, stat stages, Quark Drive, etc.), butweOutspeedstill compares mostly rawstats.spewith only Tailwind and Trick Room. That value drives move scoring (attackerMovesFirst), Destiny Bond, priority heuristics, and Counter-family logic, so turn order can disagree with the switch evaluator in the same turn.Reviewed by Cursor Bugbot for commit
433f496ab0. Configure here.Failed-move flag survives switch-in
Medium Severity
lastMoveFailedis set on miss, immune, fail, and cant events but is never cleared when a Pokémon switches in. The battle simulator resets move-result state on switch-in, so a mon returning after an earlier failed move can still look eligible for doubledStomping Tantrumpower incorrectly.Reviewed by Cursor Bugbot for commit
433f496ab0. Configure here.Fail lines mark wrong mon
Medium Severity
The new
failhandler setslastMoveFailedonevent.pokemon, but many|-fail|lines name the Pokémon that blocked an effect (e.g. Intimidate or Clear Bodyunboostfails), not the mover whose attack failed. Those targets can inherit a false “previous move failed” flag and over-score follow-ups like Stomping Tantrum.Reviewed by Cursor Bugbot for commit
433f496ab0. Configure here.@ -135,0 +274,4 @@// triggers on entry instead, granting +30%/+50% to the highest// stat for the duration of the stay.if (item === "boosterenergy") {const isProto = ability === "protosynthesis";@smell-of-curry, the fix looks great — thepushProxyhelper is clean and the dual-category emission is exactly what's needed to properly model special-leaning foes' unrevealed STABs. The Magnezone regression test is a nice addition to lock in the behaviour. Thanks for the quick turnaround!(ミ⁀ᴗ⁀ミ)
🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
@ -621,3 +701,4 @@if (id === "bodypress") {offensiveStatKey = "def";baseStat = effectiveStat(input.attacker, "def", /* ignoreNeg */ crit);} else if (id === "foulplay") {@smell-of-curry, confirmed — passing the pre-computed effective weather intoparadoxBoostedStatis the right approach, and the deliberate omission of terrain suppression for Quark Drive is correct since Cloud Nine/Air Lock only affect weather, not terrain. Thanks for the clear explanation!🧠 Learnings used
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!