Map the French, German, Italian and Spanish releases' addresses (#84)
PRET builds none of these releases, so scripts/map-revision.mjs derives where each keeps the profile's labels from the 1.1 build: a bank-by-bank alignment, then the exact references RGBDS recorded while linking 1.1, read at the matching place in the release, each result checked against its bytes. All but 1-3 labels per release are found; the rest, in code the release changed, are -1 so reading them refuses. check:reference verifies the generated file when the ROMs are present. CrystalAssets now points the profile at the cartridge's addresses, and the releases' characters (from their own fonts) and two European text codes decode. The introduction and Mom's scene play in each language and every map sweeps as in English, but the releases stay refused until the logic they localize (units, word order, layouts) is ported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3c480a28f0
commit
627f87d8b5
17 files changed
+1202
-190
No files matched your search
@@ -0,0 +1,80 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { CrystalAssets } from "../src/game/rom/assets";
|
||||
import { JOHTO, MENU } from "../src/game/rom/profile";
|
||||
import { revisionAddresses, romRevision } from "../src/game/rom/revision";
|
||||
import { RELEASES_IN_PROGRESS, validateRom } from "../src/rom/validate";
|
||||
import { readString } from "../src/game/scripts/text";
|
||||
import { textTiles } from "../src/game/rom/font";
|
||||
import { Playthrough } from "./playthrough-driver";
|
||||
|
||||
/** The European releases at the repository root, each skipped when absent. */
|
||||
const releases = [
|
||||
{ language: "fr", rom: "Pokemon - Version Cristal (France).gbc", release: RELEASES_IN_PROGRESS[0] },
|
||||
{ language: "de", rom: "Pokemon - Kristall-Edition (Germany).gbc", release: RELEASES_IN_PROGRESS[1] },
|
||||
{ language: "it", rom: "Pokemon - Versione Cristallo (Italy).gbc", release: RELEASES_IN_PROGRESS[2] },
|
||||
{ language: "es", rom: "Pokemon - Edicion Cristal (Spain).gbc", release: RELEASES_IN_PROGRESS[3] },
|
||||
] as const;
|
||||
const english = "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc";
|
||||
|
||||
for (const { language, rom, release } of releases)
|
||||
describe.skipIf(!existsSync(rom))(`the ${language} release`, () => {
|
||||
const bytes = existsSync(rom) ? new Uint8Array(readFileSync(rom)) : new Uint8Array();
|
||||
|
||||
it("is recognized from its header, then refused until its logic is ported", async () => {
|
||||
const assets = new CrystalAssets(bytes);
|
||||
expect(romRevision(assets.reader)).toBe(language);
|
||||
await expect(validateRom(new File([bytes], "crystal.gbc"))).rejects.toThrow(
|
||||
`This is the ${release.name.replace("Pokémon Crystal · ", "")} release`,
|
||||
);
|
||||
});
|
||||
|
||||
it("points the profile at its addresses and back to English for another cartridge", () => {
|
||||
const overrides = revisionAddresses(language).JOHTO;
|
||||
const [key, value] = Object.entries(overrides).find(([, address]) => address > 0)!;
|
||||
new CrystalAssets(bytes);
|
||||
expect((JOHTO as Record<string, number>)[key]).toBe(value);
|
||||
if (existsSync(english)) {
|
||||
new CrystalAssets(new Uint8Array(readFileSync(english)));
|
||||
expect((JOHTO as Record<string, number>)[key]).not.toBe(value);
|
||||
new CrystalAssets(bytes);
|
||||
}
|
||||
});
|
||||
|
||||
it("decodes its location names and menu strings, and writes them back as tiles", () => {
|
||||
const assets = new CrystalAssets(bytes);
|
||||
const context = { player: "", rival: "", mom: "", strings: {}, weekday: "" };
|
||||
for (let landmark = 1; landmark < 0x60; landmark++) {
|
||||
const name = assets.landmark(landmark);
|
||||
expect(name.length).toBeGreaterThan(0);
|
||||
// One tile per character, except the release's own word breaks.
|
||||
expect(textTiles(name.replace(/\n/g, " ")).length).toBeGreaterThan(0);
|
||||
}
|
||||
expect(readString(assets.reader, MENU["PlacePartyMonTMHMCompatibility.string_able"], context).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("plays the introduction and Mom's scene in its language, then steps outside", () => {
|
||||
const p = new Playthrough(new CrystalAssets(bytes));
|
||||
p.start();
|
||||
p.world.beginIntro();
|
||||
p.play([0, "", 1, "", 1, 0, "KAWA"]);
|
||||
expect(p.world.intro).toBe(false);
|
||||
expect(p.world.name).toBe("KAWA");
|
||||
// The release's own words: none of the English introduction's.
|
||||
const log = p.log.join(" ");
|
||||
expect(log.length).toBeGreaterThan(200);
|
||||
expect(log).not.toContain("Welcome to the world of POKéMON!");
|
||||
p.reload();
|
||||
p.walk(7, 0);
|
||||
expect(p.world.mapId).toBe("24:6");
|
||||
p.walk(9, 3);
|
||||
expect(p.world.move("down")).toBe(true);
|
||||
p.play([1, 1, 1, 1, 1]);
|
||||
p.reload();
|
||||
p.walk(6, 7);
|
||||
expect(p.world.move("down")).toBe(true);
|
||||
p.play();
|
||||
expect(p.world.mapId).toBe("24:4");
|
||||
expect(p.world.notice).toBe("");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user