mirror of
https://github.com/smogon/sprites.git
synced 2026-10-02 06:09:07 -05:00
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:
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@types/node": "^24.10.7"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig-base"
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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`);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@smogon/sprite-data": "workspace:*",
|
||||
"@smogon/sprite-root": "workspace:*",
|
||||
"@types/node": "^24.10.7"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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`);
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig-base",
|
||||
"references": [
|
||||
{"path": "../../data/lib"},
|
||||
{"path": "../../lib/root"}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user