From 61df5387d214ae51010d0fa348f36a556aa1852a Mon Sep 17 00:00:00 2001 From: Christopher Monsanto Date: Sun, 23 Aug 2026 00:45:25 -0400 Subject: [PATCH] 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) --- rules/publish.ts | 19 ++++++++++++++----- smogon.build.ts | 23 ++++++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/rules/publish.ts b/rules/publish.ts index f1383bad..984b92dd 100644 --- a/rules/publish.ts +++ b/rules/publish.ts @@ -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 { +// 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 { + for (let name of publishedNames(f, allowUnknown)) { await manifest.copy(f, dest, name); } } diff --git a/smogon.build.ts b/smogon.build.ts index aeb06b35..013329f1 100644 --- a/smogon.build.ts +++ b/smogon.build.ts @@ -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(); + let seen = new Set(); 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);