The board gains an "A tester" column for the checks that need a person: the project-board hook now reads it, so a commit that mentions one of its cards moves the card instead of adding it to a second column. A test that runs only with MAKE_TEST_SAVES=1 writes the backup those checks import, built from the local ROM and kept out of git. [no-card] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a7aceda089
commit
ee55d344af
3 files changed
+99
-1
No files matched your search
@@ -1017,3 +1017,25 @@ normal play yet (no Storm Badge, caves or further flypoints); Teleport
|
|||||||
works after a Pokémon Center visit for a Pokémon that knows it.
|
works after a Pokémon Center visit for a Pokémon that knows it.
|
||||||
|
|
||||||
Next: Headbutt and Rock Smash encounters (board #26).
|
Next: Headbutt and Rock Smash encounters (board #26).
|
||||||
|
|
||||||
|
The increment was committed as `a7aceda` (`Closes #25`). Not pushed.
|
||||||
|
|
||||||
|
## Manual test cards: 2026-10-02
|
||||||
|
|
||||||
|
Asked for the checks only a person can do. The board gained an "A tester"
|
||||||
|
column (between En cours and Fait) with #46 (Fly map, flight and landing),
|
||||||
|
#47 (Teleport and the Escape Rope outdoors), #48 (touch screens, part of
|
||||||
|
#40), #49 (screen reader, related to #38) and #50 (Dig and the Escape Rope
|
||||||
|
in Dark Cave, blocked by #31). `scripts/project-board.mjs` now also reads
|
||||||
|
that column, so a commit mentioning one of those cards moves it instead of
|
||||||
|
adding it to a second column.
|
||||||
|
|
||||||
|
`tests/human-test-saves.test.ts` writes the backups the cards use, only with
|
||||||
|
`MAKE_TEST_SAVES=1` and the local ROM: `test-saves/fly-teleport.crystal-save.json`
|
||||||
|
(ignored by git, as it holds names read from the ROM). It reloads each save
|
||||||
|
in a fresh world before writing it; importing it through Savegames and using
|
||||||
|
Teleport then Fly was checked once in Chromium and WebKit.
|
||||||
|
|
||||||
|
Not made into cards, because they can be automated: emulator comparisons
|
||||||
|
of the flight paths and of the Teleport/Dig warps through the reference
|
||||||
|
harness.
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
//
|
//
|
||||||
// A commit message mentioning "Closes #N" (also close/closed, fix/fixes/fixed,
|
// A commit message mentioning "Closes #N" (also close/closed, fix/fixes/fixed,
|
||||||
// resolve/resolves/resolved) moves card N to Fait and closes the issue. Any
|
// resolve/resolves/resolved) moves card N to Fait and closes the issue. Any
|
||||||
// other "#N" moves card N to En cours. A commit on the default branch with no
|
// other "#N" moves card N to En cours, including from the optional "A tester"
|
||||||
|
// column of manual test cards. A commit on the default branch with no
|
||||||
// reference creates a closed card in Fait from the commit message. Merges,
|
// reference creates a closed card in Fait from the commit message. Merges,
|
||||||
// amends, rebases and messages containing [no-card] are skipped. On other
|
// amends, rebases and messages containing [no-card] are skipped. On other
|
||||||
// branches only referenced cards move, and only to En cours.
|
// branches only referenced cards move, and only to En cours.
|
||||||
@@ -21,6 +22,8 @@ import { execFileSync } from "node:child_process";
|
|||||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
|
||||||
const COLUMNS = { todo: "A faire", doing: "En cours", done: "Fait" };
|
const COLUMNS = { todo: "A faire", doing: "En cours", done: "Fait" };
|
||||||
|
// Columns whose cards commits may move, but which the board may lack.
|
||||||
|
const OPTIONAL_COLUMNS = { test: "A tester" };
|
||||||
const CLOSING = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s+#(\d+)\b/gi;
|
const CLOSING = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s+#(\d+)\b/gi;
|
||||||
const REFERENCE = /(?<![\w/])#(\d+)\b/g;
|
const REFERENCE = /(?<![\w/])#(\d+)\b/g;
|
||||||
|
|
||||||
@@ -86,6 +89,10 @@ async function board(api) {
|
|||||||
if (!column) throw new Error(`Project ${project} has no "${title}" column`);
|
if (!column) throw new Error(`Project ${project} has no "${title}" column`);
|
||||||
id[key] = column.id;
|
id[key] = column.id;
|
||||||
}
|
}
|
||||||
|
for (const [key, title] of Object.entries(OPTIONAL_COLUMNS)) {
|
||||||
|
const column = columns.find((c) => c.title === title);
|
||||||
|
if (column) id[key] = column.id;
|
||||||
|
}
|
||||||
const membership = new Map();
|
const membership = new Map();
|
||||||
for (const [key, columnId] of Object.entries(id))
|
for (const [key, columnId] of Object.entries(id))
|
||||||
for (let page = 1; ; page++) {
|
for (let page = 1; ; page++) {
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { CrystalAssets } from "../src/game/rom/assets";
|
||||||
|
import { GameWorld } from "../src/game/world/world";
|
||||||
|
import { generatePartyMon } from "../src/game/party/pokemon";
|
||||||
|
import { CRYSTAL_ROM } from "../src/rom/validate";
|
||||||
|
import { encodeSave } from "../src/storage/saves";
|
||||||
|
import { foundationFixture } from "./foundation-fixture";
|
||||||
|
|
||||||
|
// Backups for the manual checks on the project board's "A tester" column,
|
||||||
|
// built from the local ROM and imported through Savegames → Import native
|
||||||
|
// backup. They hold names read from the ROM, so they stay out of the
|
||||||
|
// repository (*.crystal-save.json is ignored).
|
||||||
|
//
|
||||||
|
// MAKE_TEST_SAVES=1 npx vitest run tests/human-test-saves.test.ts
|
||||||
|
const romPath =
|
||||||
|
process.env.CRYSTAL_ROM_PATH ??
|
||||||
|
"Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc";
|
||||||
|
const OUT = "test-saves";
|
||||||
|
/** NativeRuntime.id, which an imported backup must name. */
|
||||||
|
const CORE_ID = "crystal-native-world-v1";
|
||||||
|
|
||||||
|
describe.skipIf(!existsSync(romPath) || !process.env.MAKE_TEST_SAVES)(
|
||||||
|
"saves for manual testing",
|
||||||
|
() => {
|
||||||
|
const assets = () =>
|
||||||
|
new CrystalAssets(new Uint8Array(readFileSync(romPath)));
|
||||||
|
function write(name: string, world: GameWorld) {
|
||||||
|
const state = new TextEncoder().encode(JSON.stringify(world.save()));
|
||||||
|
// The backup must load as the app would load it.
|
||||||
|
new GameWorld(assets()).restore(JSON.parse(new TextDecoder().decode(state)));
|
||||||
|
mkdirSync(OUT, { recursive: true });
|
||||||
|
writeFileSync(
|
||||||
|
`${OUT}/${name}.crystal-save.json`,
|
||||||
|
encodeSave({
|
||||||
|
version: 1,
|
||||||
|
romHash: CRYSTAL_ROM.sha1,
|
||||||
|
coreId: CORE_ID,
|
||||||
|
savedAt: new Date().toISOString(),
|
||||||
|
state,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("writes fly-teleport: New Bark Town with Fly, Teleport and Escape Ropes", () => {
|
||||||
|
const a = assets();
|
||||||
|
// Pidgeot with Fly, the Storm Badge, New Bark and Cherrygrove visited.
|
||||||
|
const world = foundationFixture(a, "fly");
|
||||||
|
const abra = generatePartyMon(a.pokemon, {
|
||||||
|
species: 63, // ABRA
|
||||||
|
level: 20,
|
||||||
|
item: 0,
|
||||||
|
otId: world.playerId,
|
||||||
|
ot: "CHRIS",
|
||||||
|
dvs: 0x9abc,
|
||||||
|
caughtData: 0,
|
||||||
|
});
|
||||||
|
abra.moves = [0x64, 0, 0, 0]; // TELEPORT
|
||||||
|
abra.pp = [20, 0, 0, 0];
|
||||||
|
world.state.party.push(abra);
|
||||||
|
// Violet City too, whose map is not ported yet.
|
||||||
|
world.state.flags.add(a.spawnEngineFlag(16)!);
|
||||||
|
world.state.lastSpawnMap = "26:3"; // Cherrygrove City
|
||||||
|
world.state.bag.items = [{ item: 0x13, quantity: 3 }]; // ESCAPE ROPE
|
||||||
|
expect(world.mapId).toBe("24:4");
|
||||||
|
write("fly-teleport", world);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
Reference in new issue
Block a user