Layer the xy set on published names, not filenames

xy/ takes the first source that offers a sprite, keyed until now on the
filename. That held while gen 5 was only there for the CAPs the models never
got: a CAP has one file and one name. The Smogon Sprite Project's batch broke
it. gen 5 now carries a file per forme slot, and the slots the games gave a
single sprite share a published name, so the six Minior meteors and the two
Zygarde Power Construct slots each arrived as their own filename asking for a
name the models had already published. The manifest rejected the first of
them, `duplicate sprite name minior-meteor.gif`, and the deploy stopped there.

Key the layering on the published name, which is what has to be unique, and
lift the name off rules/publish.ts as publishedNames() so both the layering
and spritecopy read it from one place. Sprites that publish nothing (back,
shiny, asymmetrical, and the x literals) come back as no names and copy
nothing, as before.

xy/ gains 13 sprites, all of them from the new gen 5 files, and is now 1650
names against the 1637 it held before the batch. Seven are Gigantamax:
Blastoise, Cinderace, Inteleon, Rillaboom, Urshifu, Urshifu-Rapid-Strike and
Venusaur, which is exactly what models/ lacks of the 33 Gigantamax formes gen 5
draws. The other six are Greninja-Bond, Pikachu-World, Ribombee-Totem,
Rockruff-Dusk, Sneasel-Hisui-F and Xerneas-Neutral. Everything else in the
batch is a species the models, gen9species or champions already cover.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christopher Monsanto
2026-08-23 00:45:25 -04:00
parent c16546739d
commit 61df5387d2
2 changed files with 30 additions and 12 deletions

View File

@@ -64,25 +64,34 @@ function extOf(f: Sprite, ext?: string): string {
return result;
}
export async function spritecopy(manifest: Manifest, f: Sprite, dest: Dest,
allowUnknown = false): Promise<void> {
// The names a sprite publishes under on the smogon side, or none where it
// isn't published at all. A set that backfills another needs this before it
// copies, since the mapping isn't one name per filename in either direction:
// Meowstic answers to two, and the forme slots the games gave one sprite (the
// six Minior meteors, Zygarde's Power Construct pair) answer to the same one.
export function publishedNames(f: Sprite, allowUnknown = false): string[] {
let sn = spritedata.parseFilename(f.name);
// Skip asymmetrical for now
if (sn.extra.has('a') || sn.extra.has('b') || sn.extra.has('s')) {
return;
return [];
}
if (sn.kind === 'x') {
// Skip these, we don't use Unknown/Substitute
if (!allowUnknown || sn.name !== 'unknown') {
return;
return [];
}
} else if (sn.kind !== 's') {
throw new Error(`Not a specie sprite: ${f.name}`);
}
for (let name of spritedata.smogonNames(sn)) {
return spritedata.smogonNames(sn);
}
export async function spritecopy(manifest: Manifest, f: Sprite, dest: Dest,
allowUnknown = false): Promise<void> {
for (let name of publishedNames(f, allowUnknown)) {
await manifest.copy(f, dest, name);
}
}

View File

@@ -1,6 +1,6 @@
import {gen10Modelslike} from './rules/modelslike.ts';
import {Manifest, type Sprite, spritecopy} from './rules/publish.ts';
import {Manifest, type Sprite, publishedNames, spritecopy} from './rules/publish.ts';
import {forEachRule} from './tools/build/artifact.ts';
import {compresspng, trimimg} from './tools/build/helpers.ts';
import {deploy} from './tools/deploy/api.ts';
@@ -19,7 +19,9 @@ let xyModels = forEachRule('src/gen9species/*.png', {
let xyChampions = gen10Modelslike();
// Non-model gen 5 CAPs.
// Whatever the models don't cover, in gen 5 style: the CAPs that never got a
// model, and, since the Smogon Sprite Project's batch landed, the Gigantamax
// formes and a few others.
let xyGen5 = forEachRule('src/sprites/gen5/*.png', [
// TODO, add customizable compression for gif
@@ -29,14 +31,22 @@ let xyGen5 = forEachRule('src/sprites/gen5/*.png', [
], '%B.gif');
deploy(async ctx => {
let seenModels = new Set<string>();
let seen = new Set<string>();
let manifest = new Manifest(ctx);
// First source wins per published name rather than per filename, because
// the later sources are backfills and a name can be spelled more than one
// way. gen 5 carries a sprite per forme slot, so its six Minior meteors
// and two Zygarde Power Construct slots all want the name the models
// already publish, which the manifest would reject as a duplicate.
let xycopy = async (f: Sprite) => {
if (seenModels.has(f.name)) {
let names = publishedNames(f);
if (names.some(n => seen.has(n))) {
return;
}
seenModels.add(f.name);
await spritecopy(manifest, f, {dir: 'xy'});
for (let name of names) {
seen.add(name);
await manifest.copy(f, {dir: 'xy'}, name);
}
};
for (let f of await ctx.list('src/models')) {
@@ -48,7 +58,6 @@ deploy(async ctx => {
for (let f of xyChampions) {
await xycopy(f);
}
// Non-model CAPs
for (let f of await ctx.list('src/sprites/gen5')) {
if (f.ext === 'gif') {
await xycopy(f);