Delete the PS build and the pokemon-showdown tooling

deploy.json5 has two targets, assets and smogon. ps.build.ts has none: nothing
it produces has shipped from this repo, and the ani/ rsync it was written for
lives in the README as an example. Everything it pulled along came with it.
tools/sheet is its montage tool and had no other caller.
ps-pokemon.dexnums.mjs, 1125 generated lines, existed only to hand its sheet a
dex number after data/species.json went away. tools/spritedata/{dexnums,names,
pokedex,rename}.ts all read a pokemon-showdown checkout, which is a dependency
nothing in either surviving deploy has.

The branch ps-build points at the commit before this, so it is one checkout
away if the PS side ever comes back.

tools/deploy/coverage.ts is the exception and moves rather than goes. It reads
no PS data and generates no table; it reports what a deploy would delete off
the server, which is what makes --delete-after safe to run, and it only lived
in tools/spritedata because that is where I put it.

Both remaining deploys materialize unchanged: xy/ 1651 names, xyicons/ 1556,
assets byte for byte. psid() keeps its caller in the smogdex sheet, which still
needs PS's spelling of a name even with the PS deploy gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christopher Monsanto
2026-08-23 01:50:21 -04:00
parent 7493d948e6
commit a919602cc2
19 changed files with 7 additions and 3242 deletions

View File

@@ -11,9 +11,6 @@ import {spawnSync} from 'node:child_process';
// when these two sets published through Manifest, and the pre-rename spellings
// below that nothing has asked for in years. Anything else is a hole in the
// build, and this finds it before rsync does.
//
// names.ts answers the same question against pokemon-showdown; this one
// answers it against what is actually being served.
let dir = process.argv[2];
let remote = process.argv[3] ?? 'smogon:/smog2/sprites';
@@ -57,7 +54,7 @@ for (let set of ['xy', 'xyicons']) {
let stamped = 0;
let backlog = 0;
let unexplained = [];
let unexplained: string[] = [];
for (let line of rsync.stdout.split('\n')) {
let m = /^\*deleting +(.*)$/.exec(line);
if (!m) continue;

View File

@@ -1,58 +0,0 @@
import * as cp from 'node:child_process';
import * as path from 'node:path';
let sheetjs = process.argv[2];
let dest = process.argv[3];
if (!sheetjs || !dest) {
throw new Error(`node tools/sheet <file.sheet.js> <dest>`);
}
// Must have file:/// for Windows
let {default: sheet} = await import(path.join('file:///', process.cwd(), sheetjs));
for (let i = 0; i < sheet.entries.length; i++) {
if (sheet.entries[i] === undefined)
throw new Error(`gap: ${i}`);
if (sheet.entries[i] === null) {
// ImageMagick blank entry
sheet.entries[i] = 'xc:transparent';
}
}
// Write list of filenames to stdin, Windows can't handle large cli arg lists.
// Before we wrote a tmp file and deleted it afterwards, but apparently tup
// can't track unlinkSync on Windows?
let proc = cp.spawn('magick', [
'montage',
'@-',
// Keep sheet bytes deterministic for the content-addressed build.
'-define', 'png:exclude-chunks=date,time',
'-background', 'transparent',
'-geometry', `${sheet.width}x${sheet.height}>`,
'-gravity', 'center',
'-tile', `${sheet.tile}x`,
'-depth', '8',
dest
], {
stdio: ['pipe', 'inherit', 'inherit']
});
// Write one at a time to avoid "malloc(): corrupted top size" error
for (let entry of sheet.entries) {
if (!proc.stdin.write(entry + "\n")) {
// imagick doesn't like it when it receives the input too fast. This
// seems to avoid a realloc() invalid next size error.
await new Promise(resolve => {
proc.stdin.once('drain', resolve);
});
}
}
proc.stdin.end();
proc.on('exit', (code, signal) => {
if (code || signal) {
process.exitCode = 1;
}
});

View File

@@ -1,6 +0,0 @@
{
"type": "module",
"dependencies": {
"@types/node": "^24.10.7"
}
}

View File

@@ -1,3 +0,0 @@
{
"extends": "../../tsconfig-base"
}

View File

@@ -1,36 +0,0 @@
import {createRequire} from 'node:module';
import * as path from 'node:path';
// Regenerates ps-pokemon.dexnums.mjs from a pokemon-showdown checkout:
//
// node tools/spritedata/dexnums.ts ~/smogon/pokemon-showdown > ps-pokemon.dexnums.mjs
//
// The PS icon sheet puts a base species at the slot named by its dex number,
// and only alt formes need the hand-kept index table in ps-pokemon.sheet.mjs.
type PokedexEntry = {
num: number,
name: string,
baseSpecies?: string
};
let ps = process.argv[2];
if (!ps) {
throw new Error('usage: node tools/spritedata/dexnums.ts <pokemon-showdown checkout>');
}
let require = createRequire(import.meta.url);
let {Pokedex} = require(path.resolve(ps, 'dist/data/pokedex.js')) as
{Pokedex: Record<string, PokedexEntry>};
let out = '';
out += '// Generated by tools/spritedata/dexnums.ts; do not edit.\n';
out += '// PS id of every base species -> its national dex number.\n\n';
out += 'export const DEX_NUMS = {\n';
for (let {num, name, baseSpecies} of Object.values(Pokedex)) {
if (baseSpecies) continue;
out += `\t${name.toLowerCase().replace(/[^a-z0-9]+/g, '')}: ${num},\n`;
}
out += '};\n';
process.stdout.write(out);

View File

@@ -1,20 +0,0 @@
import {formatFilename} from '@smogon/sprite-data/index.ts';
import {spriteFiles} from './pokedex.ts';
// Every sprite filename pokemon-showdown implies, one per line:
//
// node tools/spritedata/names.ts ~/smogon/pokemon-showdown | sort > /tmp/want
// ls src/models | sed 's/\.[^.]*$//' | cut -d- -f1 | sort -u > /tmp/have
//
// Nothing in the build validates a filename any more, so this is how you check
// a directory against the source of truth.
let ps = process.argv[2];
if (!ps) {
throw new Error('usage: node tools/spritedata/names.ts <pokemon-showdown checkout>');
}
for (let file of spriteFiles(ps)) {
process.stdout.write(`${formatFilename(file)}\n`);
}

View File

@@ -1,8 +0,0 @@
{
"type": "module",
"dependencies": {
"@smogon/sprite-data": "workspace:*",
"@smogon/sprite-root": "workspace:*",
"@types/node": "^24.10.7"
}
}

View File

@@ -1,99 +0,0 @@
import {createRequire} from 'node:module';
import * as path from 'node:path';
import {encode, type InputSpriteFilename} from '@smogon/sprite-data/index.ts';
// Everything the sprite tree wants from a pokemon-showdown checkout: which
// sprites should exist, and what each one is called.
export type Specie = {
num: number,
formeNum: number,
base: string,
forme: string
};
export type Item = {
num: number,
names: string[]
};
type PokedexEntry = {
num: number,
name: string,
baseSpecies?: string,
formeOrder?: string[]
};
type ItemsEntry = {
num: number,
name: string
};
function load<T>(ps: string, file: string, key: string): Record<string, T> {
let require = createRequire(import.meta.url);
let mod = require(path.resolve(ps, 'dist/data', file)) as Record<string, Record<string, T>>;
let table = mod[key];
if (table === undefined) {
throw new Error(`${file} has no ${key} export; is ${ps} built?`);
}
return table;
}
export function species(ps: string): Specie[] {
let out: Specie[] = [];
for (let {num, name, baseSpecies, formeOrder} of Object.values(load<PokedexEntry>(ps, 'pokedex.js', 'Pokedex'))) {
// Formes are enumerated from their base species, not on their own.
if (baseSpecies) continue;
for (let [formeNum, formeName] of (formeOrder ?? [name]).entries()) {
// Gmax is a flag, not a forme.
if (formeName.endsWith('-Gmax')) continue;
out.push({num, formeNum, base: name, forme: formeName.slice(name.length + 1)});
}
}
return out;
}
export function items(ps: string): Item[] {
// One sprite per item number, so items sharing a number share a sprite and
// collect their names: Aspear Berry is also Burnt Berry.
let byNum = new Map<number, Item>();
for (let {num, name} of Object.values(load<ItemsEntry>(ps, 'items.js', 'Items'))) {
let item = byNum.get(num);
if (item) {
item.names.push(name);
} else {
byNum.set(num, {num, names: [name]});
}
}
return [...byNum.values()];
}
// The filename each sprite should have. PS's formeOrder repeats a name where
// the games gave one forme several slots (Zygarde's Power Construct pair,
// Minior's seven meteors), and nothing in the data tells those apart, so the
// lowest slot keeps the bare name and the rest carry the slot in -c.
export function spriteFiles(ps: string): InputSpriteFilename[] {
let out: InputSpriteFilename[] = [];
let seen = new Set<string>();
for (let {formeNum, base, forme} of species(ps)) {
let name = encode(base);
let extra = new Map<string, string>();
if (forme) {
extra.set('o', encode(forme));
}
let key = `${name}-${extra.get('o') ?? ''}`;
if (seen.has(key)) {
extra.set('c', String(formeNum));
}
seen.add(key);
out.push({kind: 's', name, extra});
}
for (let {names} of items(ps)) {
out.push({kind: 'i', name: encode(names[0]!)});
}
return out;
}

View File

@@ -1,100 +0,0 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import {encode, formatFilename, parseFilename, type InputSpriteFilename} from '@smogon/sprite-data/index.ts';
import root from '@smogon/sprite-root/index.ts';
import {items, species, spriteFiles} from './pokedex.ts';
// One-off: renames src/ off the packed numeric ids onto encoded names.
//
// node tools/spritedata/rename.ts ~/smogon/pokemon-showdown [--dry-run]
//
// Kept for the record rather than for reuse. The old id packed the dex number
// and the forme index into one number, `s((|num| << 5) + formeNum)` with bit 29
// marking the negative nums CAP and fakemon use, and `i(|num|)` for items;
// src/previews/gen8 wrote the same number with no prefix at all. Reconstruct
// those here and map each to the name the file should have had.
let ps = process.argv[2];
let dryRun = process.argv.includes('--dry-run');
if (!ps) {
throw new Error('usage: node tools/spritedata/rename.ts <pokemon-showdown checkout> [--dry-run]');
}
function legacy(num: number, formeNum: number): number {
let sid = (Math.abs(num) << 5) + formeNum;
return num < 0 ? sid | (1 << 29) : sid;
}
// spriteFiles() lists species then items, each in the order its own reader
// yields, so the ids line up positionally.
let files = spriteFiles(ps);
let sids = [
...species(ps).map(s => `s${legacy(s.num, s.formeNum)}`),
...items(ps).map(i => `i${i.num < 0 ? Math.abs(i.num) | (1 << 29) : i.num}`),
];
if (sids.length !== files.length) {
throw new Error(`${sids.length} ids for ${files.length} sprites`);
}
let byId = new Map<string, InputSpriteFilename>();
for (let [i, sid] of sids.entries()) {
byId.set(sid, files[i]!);
}
let renames: [string, string][] = [];
let missing = new Set<string>();
function walk(dir: string): void {
for (let ent of fs.readdirSync(dir, {withFileTypes: true})) {
let p = path.join(dir, ent.name);
if (ent.isDirectory()) {
// Its own naming scheme, and nothing parses it.
if (path.basename(p) !== '_uncategorized') walk(p);
continue;
}
if (path.extname(p) === '.md') continue;
let ext = path.extname(ent.name);
let stem = ent.name.slice(0, -ext.length);
let [id, ...flags] = stem.split('-');
let file: InputSpriteFilename;
if (id!.startsWith('x')) {
file = {kind: 'x', name: encode(id!.slice(1))};
} else {
let found = byId.get(/^[0-9]+$/.test(id!) ? `s${id}` : id!);
if (!found) {
missing.add(id!);
continue;
}
file = found;
}
let extra = new Map(file.extra);
for (let flag of flags) {
extra.set(flag.charAt(0), flag.slice(1));
}
let renamed = formatFilename({kind: file.kind, name: file.name, extra}) + ext;
if (renamed !== ent.name) {
renames.push([p, path.join(dir, renamed)]);
}
}
}
walk(path.join(root, 'src'));
if (missing.size) {
throw new Error(`no sprite for ${[...missing].join(', ')}`);
}
let taken = new Set(renames.map(([, to]) => to));
if (taken.size !== renames.length) {
throw new Error('renames collide');
}
for (let [from, to] of renames) {
if (!dryRun) fs.renameSync(from, to);
}
process.stderr.write(`${dryRun ? 'would rename' : 'renamed'} ${renames.length} files\n`);

View File

@@ -1,7 +0,0 @@
{
"extends": "../../tsconfig-base",
"references": [
{"path": "../../data/lib"},
{"path": "../../lib/root"}
]
}