Port the remaining battle items, the stats screen and hBattleTurn at the menu

- Run every non-ball item effect usable in battle from the ROM's
  ItemEffects: Full Restore, revives, bitter medicine, Bitter Berry, Ethers
  and Elixers, X items, X Accuracy, Guard Spec, Dire Hit and Poké Doll.
- Read happiness changes from HappinessChanges, including the larger
  level-up gain where a Pokémon was caught.
- Model hBattleTurn at the battle menu, including the trainer AI's writes,
  and drop the status-heal refusal.
- Port the stats screen for battles and Bill's PC.
- Compare the send-out and withdraw texts, RestoreHealth, RestorePP,
  ChangeHappiness and CalcPlayerStats with the original routines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sébastien QUEROLandClaude Opus 5.5 committed 2026-09-30 11:24:55 +02:00
1 parent cfd083c16d
commit b13e09ec20
22 files changed
+1547 -171

No files matched your search

+30
View File
@@ -94,6 +94,7 @@ describe("trainer AI native scoring", () => {
move: 2,
index: 1,
scores: [20, 20, 80, 80],
enemyTurn: false,
});
},
);
@@ -124,8 +125,34 @@ describe("trainer AI native scoring", () => {
move: 2,
index: 1,
scores: [20, 20, 80, 80],
enemyTurn: false,
});
});
it("reports when AI_Types, AI_Status or AIDamageCalc set hBattleTurn", () => {
const run = (
weights: number,
moves = [move(1), move(2)],
playerTypes = [0, 0],
) => {
const input = fixture(moves, [0, 1, 2, 3, 0, 1, 2, 3]);
input.weights = weights;
input.player.mon.types = playerTypes;
input.damage = () => 0;
return chooseTrainerMove(input).enemyTurn;
};
expect(run(0)).toBe(false);
expect(run(4)).toBe(true); // Types
expect(run(64)).toBe(true); // Aggressive's damage estimates
expect(run(256)).toBe(true); // Status, for damaging moves
// A poison move against a Poison type skips the matchup and the write;
// a status move with no power checks nothing.
const poison = [
move(1, C.EFFECT_POISON, 0),
move(2, C.EFFECT_ATTACK_UP, 0),
];
expect(run(256, poison, [C.POISON, C.POISON])).toBe(false);
expect(run(256, poison)).toBe(true);
});
it("refuses a random source that never produces a candidate", () => {
const input = fixture();
input.random = () => 3;
@@ -140,6 +167,7 @@ describe("trainer AI native scoring", () => {
move: 3,
index: 2,
scores: [80, 80, 20, 80],
enemyTurn: false,
});
});
it("still scores exhausted moves and consumes randomness before Struggle", () => {
@@ -153,6 +181,7 @@ describe("trainer AI native scoring", () => {
move: 1,
index: 0,
scores: [78, 80, 80, 80],
enemyTurn: false,
});
});
it("still selects when the only move is disabled", () => {
@@ -163,6 +192,7 @@ describe("trainer AI native scoring", () => {
move: 1,
index: 0,
scores: [80, 20, 80, 80],
enemyTurn: false,
});
});
it("refuses empty move lists instead of the cartridge's infinite loop", () => {