Port the Smart AI effect handlers (#28)

Translate every AI_Smart_EffectHandlers entry, so trainers with the Smart layer are no longer refused. The AI's type checks keep the CheckTypeMatchup bug: they read the move type and Foresight flag through hBattleTurn, and Conversion2's write to the player's move type persists.

The reference emulator runs the unmodified AI_Smart routine over 6,024 states per browser, comparing scores, Random consumption, hBattleTurn and the player's move type. Validated with 579 unit tests, 151 browser tests, native/reference builds and reference/vendor/release checks. Kurt's balls, Park and Safari balls and special battle types remain on #28.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-10-03 15:30:32 +02:00
1 parent 32d736ecae
commit 77704fe848
11 files changed
+682 -71

No files matched your search

+18 -21
View File
@@ -283,26 +283,23 @@ describe.skipIf(!existsSync(romPath))("trainer battles", () => {
expect(result.texts).toContain("was defeated!");
},
);
it("refuses untranslated Smart effects, including reserves, and empty parties before mutation", () => {
for (const kind of ["empty", "ai", "reserve"] as const) {
const { options } = setup();
if (kind === "empty") options.trainer!.party = [];
else {
options.trainer!.aiFlags |= 1 << 4;
const unsupported = structuredClone(options.trainer!.party[0]);
unsupported.moves = [115, 0, 0, 0]; // Reflect has an untranslated Smart handler.
if (kind === "reserve") options.trainer!.party.push(unsupported);
else options.trainer!.party[0] = unsupported;
}
const before = structuredClone(options.party);
const enemyBefore = structuredClone(options.trainer!.party);
let draws = 0;
options.random = () => { draws++; return 0; };
expect(() => new Battle(options).run().next()).toThrow(UnsupportedBattle);
expect(options.party).toEqual(before);
expect(options.trainer!.party).toEqual(enemyBefore);
expect(options.pokedex.seen.size).toBe(0);
expect(draws).toBe(0);
}
it("refuses an empty trainer party before mutation", () => {
const { options } = setup();
options.trainer!.party = [];
const before = structuredClone(options.party);
let draws = 0;
options.random = () => { draws++; return 0; };
expect(() => new Battle(options).run().next()).toThrow(UnsupportedBattle);
expect(options.party).toEqual(before);
expect(options.pokedex.seen.size).toBe(0);
expect(draws).toBe(0);
});
it("completes a battle against Smart AI with a handled status move", () => {
const { options } = setup();
options.trainer!.aiFlags |= 1 << 4;
options.trainer!.party[0].moves = [115, 33, 0, 0]; // Reflect, Tackle
const result = finish(new Battle(options));
expect(result.result).toBe(0);
expect(result.texts).toContain("was defeated!");
});
});