Port weather, copying moves, held items and obedience (#28)
Checks / web (push) Canceled after 0s

Complete the weather, Substitute, Transform, called-move and forced-switch effects. Add held-item behavior, Exp. Share awards and badge-based traded Pokemon obedience, and persist trainer berry consumption.

Validated with 576 passing unit tests and 149 browser tests, native/reference builds, reference/vendor/release checks and 320 Metal Powder emulator comparisons per browser. Smart AI and special balls/battle types remain on #28.
This commit is contained in:
Sébastien QUEROL committed 2026-10-03 15:03:19 +02:00
1 parent 8f5b464efc
commit 32d736ecae
13 files changed
+1509 -75

No files matched your search

+25
View File
@@ -51,6 +51,7 @@ type Case = {
beatUpHit: number;
playerIsSwitching: number;
enemyIsSwitching: number;
weather?: number;
/** A battle script and the index of the command after the one run. */
script: number[];
scriptIndex: number;
@@ -142,6 +143,7 @@ async function compare(page: Page, routines: string[], cases: Case[]) {
enemyGoesFirst: at("wEnemyGoesFirst"),
animParam: at("wBattleAnimParam"),
battleMode: at("wBattleMode"),
weather: at("wBattleWeather"),
partyCount: at("wPartyCount"),
otPartyCount: at("wOTPartyCount"),
beatUpHit: at("wBeatUpHitAtLeastOnce"),
@@ -257,6 +259,7 @@ async function compare(page: Page, routines: string[], cases: Case[]) {
beatUpHit: input.beatUpHit,
playerIsSwitching: input.playerIsSwitching,
enemyIsSwitching: input.enemyIsSwitching,
weather: input.weather ?? 0,
script: [...input.script],
scriptIndex: input.scriptIndex,
});
@@ -269,6 +272,7 @@ async function compare(page: Page, routines: string[], cases: Case[]) {
write(addresses.enemyGoesFirst, input.enemyGoesFirst);
write(addresses.animParam, input.animParam);
write(addresses.battleMode, input.battleMode);
write(addresses.weather, input.weather ?? 0);
write(addresses.partyCount, input.partyCount);
write(addresses.otPartyCount, input.otPartyCount);
write(addresses.beatUpHit, input.beatUpHit);
@@ -621,3 +625,24 @@ test("CheckHit matches Fly, Dig and Lock-On", async ({ page }) => {
}
await compare(page, ["BattleCommand_CheckHit"], cases);
});
test("Stab matches weather, STAB and type effectiveness", async ({ page }) => {
const { make, next } = generator(0x57ab);
// Normal, Fighting, Flying, Ground, Ghost, Fire, Water, Grass, Electric, Psychic, Dark.
const types = [0, 1, 2, 4, 8, 20, 21, 22, 23, 24, 27];
const cases: Case[] = [];
for (let i = 0; i < 1000; i++) {
const input = make({
curDamage: [0, 1, 2, 3, 40, 997, 0x7fff, 0xaaaa, 0xffff][next(9)],
typeModifier: next(4) ? C.EFFECTIVE : 0,
});
const user = input.sides[input.turn],
target = input.sides[input.turn ^ 1];
user.move = { ...user.move, type: types[next(types.length)], effect: next(4) ? 0 : C.EFFECT_SOLARBEAM };
user.types = [types[next(types.length)], types[next(types.length)]];
target.types = [types[next(types.length)], types[next(types.length)]];
if (next(4) === 0) target.sub[0] = 1 << C.SUBSTATUS_IDENTIFIED;
cases.push({ ...input, weather: next(4) } as Case);
}
await compare(page, ["BattleCommand_Stab"], cases);
});