Measure, weigh and name the European releases' own way (#84)
The French, German, Italian and Spanish releases store Magikarp's length in millimetres (and print a tenth of them), start the Magikarp house's record at their own length and holder, keep Pokédex heights in decimetres and weights in hectograms (printed in metres and kilograms), read the Heavy Ball's weight byte without converting it, and place the enemy's nickname before EnemyText (French, Italian) or the trainer's name before the class (French, Italian, Spanish). map-revision --symbols writes each release's symbol file, so the reference specs run against a release; specs that pin English data skip there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
627f87d8b5
commit
d3e3faff72
15 files changed
+246
-63
No files matched your search
+41
-10
@@ -12,7 +12,10 @@
|
||||
// Needs the pinned PRET checkout built for 1.1 (its .o, .map and .sym) and
|
||||
// the releases' ROMs at the repository root (or CRYSTAL_<FR|DE|IT|ES>_ROM_PATH).
|
||||
// MAP_REVISION_DUMP=dir also writes every 1.1 label's place in each release
|
||||
// (<dir>/<language>.json), for comparing routines.
|
||||
// (<dir>/<language>.json), for comparing routines. --symbols writes
|
||||
// .reference/pokecrystal/pokecrystal_<language>.sym: 1.1's symbol file with
|
||||
// each release's places (labels not found left out), so the reference specs
|
||||
// run against a release with CRYSTAL_ROM_PATH and CRYSTAL_SYMBOLS_PATH.
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { PROFILE_RAM, PROFILE_SYMBOLS } from "./profile-symbols.mjs";
|
||||
@@ -393,15 +396,24 @@ function resolve(en, fr, symbols, refs) {
|
||||
// near the resolved routine.
|
||||
if (i >= 0 && p - starts[i] <= 64 && Math.floor(starts[i] / BANK) === Math.floor(p / BANK)) {
|
||||
const from = startAt.get(starts[i]);
|
||||
const pattern = [];
|
||||
for (let d = -3; d <= 3; d++) pattern.push(cover.has(p + d) ? -1 : en[p + d]);
|
||||
let hit;
|
||||
for (let q = from; q < from + 128 && q + 3 < fr.length; q++)
|
||||
if (pattern.every((b, k) => b < 0 || fr[q - 3 + k] === b)) {
|
||||
if (hit !== undefined) return undefined;
|
||||
hit = q;
|
||||
}
|
||||
return hit;
|
||||
// Widest first; narrower when a changed byte sits next to it.
|
||||
for (const [before, after] of [[3, 3], [1, 3]]) {
|
||||
const pattern = [];
|
||||
// The pointer itself is what the release changes.
|
||||
for (let d = -before; d <= after; d++) pattern.push(d === 0 || d === 1 || cover.has(p + d) ? -1 : en[p + d]);
|
||||
if (pattern.filter((b) => b >= 0).length < (before === 3 ? 3 : 2)) continue;
|
||||
// The hit nearest where 1.1's distance from the routine puts it,
|
||||
// within 16 bytes and nearer than any other.
|
||||
const expected = from + (p - starts[i]);
|
||||
const hits = [];
|
||||
for (let q = from; q < from + 128 && q + after < fr.length; q++)
|
||||
if (pattern.every((b, k) => b < 0 || fr[q - before + k] === b)) hits.push(q);
|
||||
hits.sort((x, y) => Math.abs(x - expected) - Math.abs(y - expected));
|
||||
if (hits.length === 1) return hits[0];
|
||||
if (hits.length && Math.abs(hits[0] - expected) <= 16 && Math.abs(hits[1] - expected) > Math.abs(hits[0] - expected))
|
||||
return hits[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -650,6 +662,25 @@ function generate() {
|
||||
const { known, how, cover } = resolve(en, fr, symbols, refs);
|
||||
if (process.env.MAP_REVISION_DUMP)
|
||||
writeFileSync(`${process.env.MAP_REVISION_DUMP}/${language}.json`, JSON.stringify(Object.fromEntries(known)));
|
||||
if (process.argv.includes("--symbols")) {
|
||||
const out = [];
|
||||
for (const line of readFileSync(`${root}/pokecrystal11.sym`, "utf8").split("\n")) {
|
||||
const match = /^([0-9a-f]{2}):([0-9a-f]{4}) (\S+)$/.exec(line.trim());
|
||||
if (!match) {
|
||||
out.push(line);
|
||||
continue;
|
||||
}
|
||||
const name = match[3],
|
||||
address = parseInt(match[2], 16);
|
||||
const hex = (value, width) => value.toString(16).padStart(width, "0");
|
||||
if (address >= 0x8000) out.push(`${match[1]}:${hex(known.get(name) ?? address, 4)} ${name}`);
|
||||
else if (known.has(name)) {
|
||||
const f = known.get(name);
|
||||
out.push(`${hex(f < BANK ? 0 : Math.floor(f / BANK), 2)}:${hex(f < BANK ? f : (f % BANK) + 0x4000, 4)} ${name}`);
|
||||
}
|
||||
}
|
||||
writeFileSync(`${root}/pokecrystal_${language}.sym`, out.join("\n"));
|
||||
}
|
||||
const check = checker(en, fr, symbols, refs, known, cover);
|
||||
const counts = {},
|
||||
unresolved = [],
|
||||
|
||||
Reference in new issue
Block a user