Files
sprites/rules/avatars.ts
Christopher Monsanto 0d214ecc77 Build the forum's auto avatars, and publish them as a slot table
The forum has carried its default avatars as 702 committed gifs since its
initial commit in 2019: bytes in the wrong repo, and the last sprite
collection it hadn't moved onto a manifest. The sources are all here, so
build them.

They are gen 5 animations on a uniform 96x96 box. The box is not decorative:
XenForo scales an <img> to fill its avatar container, so a sprite published
at its own aspect would arrive stretched. It is also the games' own sprite
size, so nothing is scaled up, and the 45 animations that overflow it are
cropped to it, which is what the 2019 set did too.

The pool is an index, not a set. The forum picks a slot with
crc32(username) % count, so a name added, removed or respelled reassigns
every user's avatar -- which is why rules/avatars.ts spells the pool out
instead of globbing src/sprites/gen5, and why these publish under the PS ids
the 2019 set used (`hooh`, not `ho-oh`) rather than the smogon names every
other set here publishes. publishedName(sn, psid) spells all but two of
them; Arceus-Normal and Swanna-2 were second copies of a picture the pool
already carries, and now share its file.

What ships is that index rather than a name -> url manifest, because nothing
looks an avatar up: __meta/avatars/manifest.json is the directory listing
scandir() used to return, as an array of urls. Two things about that listing
are the index and so are reproduced rather than tidied away. It sorted
filenames with the extension attached, which orders arceus-bug.gif ahead of
arceus.gif where the bare names sort the other way, and disagrees with the
manifest order at 68 of the 702 slots. And it led with `.` and `..`, which
counted toward the divisor and named no avatar: those two rendered a broken
image for two of every 704 usernames, and now get Substitute, which the pool
already carries, so they cost nothing but the slot.

The animations themselves change, unavoidably. The 2019 files came from an
earlier generation of these sources: 649 of the 693 that still resolve have
a different frame count, and no coalesced frame survives. Same Pokemon, same
slot, same framing, re-animated.

Manifest grows url(), which write() already composed inline, so a set can
publish another projection of the same mapping.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 19:03:06 -04:00

738 lines
12 KiB
TypeScript

// The forum's auto avatars: a frozen pool of pictures it assigns to users who
// have uploaded none. It picks one with crc32(username) % count, so the pool is
// an index and not a set -- a name added, removed or respelled reassigns every
// user's avatar. That is why this is a list and not a glob over the gen 5
// sources: a sprite landing in src/ must not join the pool on its own.
//
// The names are the ones the 2019 set was built under, which are PS ids rather
// than the smogon names every other set here publishes: `hooh`, not `ho-oh`.
// publishedName(sn, psid) spells all but two of them.
export let AVATARS: string[] = [
'abomasnow',
'abra',
'absol',
'accelgor',
'aerodactyl',
'aggron',
'aipom',
'alakazam',
'alomomola',
'altaria',
'ambipom',
'amoonguss',
'ampharos',
'anorith',
'arbok',
'arcanine',
'arceus',
'arceus-bug',
'arceus-dark',
'arceus-dragon',
'arceus-electric',
'arceus-ghost',
'arceus-grass',
'arceus-ground',
'arceus-ice',
'arceus-normal',
'arceus-poison',
'arceus-psychic',
'arceus-rock',
'arceus-steel',
'arceus-water',
'archen',
'archeops',
'ariados',
'armaldo',
'aron',
'articuno',
'audino',
'aurumoth',
'axew',
'azelf',
'azumarill',
'azurill',
'bagon',
'baltoy',
'banette',
'barboach',
'basculin',
'bastiodon',
'bayleef',
'beartic',
'beautifly',
'beedrill',
'beheeyem',
'beldum',
'bellossom',
'bellsprout',
'bibarel',
'bidoof',
'bisharp',
'blastoise',
'blaziken',
'blissey',
'blitzle',
'boldore',
'bonsly',
'bouffalant',
'braviary',
'breloom',
'bronzong',
'bronzor',
'budew',
'buizel',
'bulbasaur',
'buneary',
'burmy',
'burmy-sandy',
'burmy-trash',
'butterfree',
'cacnea',
'cacturne',
'camerupt',
'carnivine',
'carracosta',
'carvanha',
'cascoon',
'castform',
'castform-rainy',
'castform-snowy',
'castform-sunny',
'caterpie',
'celebi',
'chandelure',
'chansey',
'charizard',
'charmander',
'charmeleon',
'chatot',
'cherrim',
'cherrim-sunshine',
'cherubi',
'chikorita',
'chimchar',
'chimecho',
'chinchou',
'chingling',
'cinccino',
'clamperl',
'claydol',
'clefable',
'clefairy',
'cleffa',
'cloyster',
'cobalion',
'cofagrigus',
'combee',
'combusken',
'conkeldurr',
'corphish',
'corsola',
'cottonee',
'cradily',
'cranidos',
'crawdaunt',
'cresselia',
'croagunk',
'crobat',
'croconaw',
'crustle',
'cryogonal',
'cubchoo',
'cubone',
'cyndaquil',
'darkrai',
'darmanitan',
'darmanitan-zen',
'darumaka',
'deerling',
'deerling-autumn',
'deerling-summer',
'deerling-winter',
'deino',
'delcatty',
'delibird',
'deoxys',
'deoxys-attack',
'deoxys-defense',
'deoxys-speed',
'dewgong',
'dewott',
'dialga',
'diglett',
'ditto',
'dodrio',
'doduo',
'donphan',
'dragonair',
'dragonite',
'drapion',
'dratini',
'drifblim',
'drifloon',
'drilbur',
'drowzee',
'druddigon',
'ducklett',
'dugtrio',
'dunsparce',
'duosion',
'durant',
'dusclops',
'dusknoir',
'duskull',
'dustox',
'dwebble',
'eelektrik',
'eelektross',
'eevee',
'ekans',
'electabuzz',
'electivire',
'electrike',
'electrode',
'elekid',
'elgyem',
'emboar',
'emolga',
'empoleon',
'entei',
'escavalier',
'espeon',
'excadrill',
'exeggcute',
'exeggutor',
'exploud',
'farfetchd',
'fearow',
'feebas',
'feraligatr',
'ferroseed',
'ferrothorn',
'finneon',
'flaaffy',
'flareon',
'floatzel',
'flygon',
'foongus',
'forretress',
'fraxure',
'frillish',
'froslass',
'furret',
'gabite',
'gallade',
'galvantula',
'garbodor',
'garchomp',
'gardevoir',
'gastly',
'gastrodon',
'gastrodon-east',
'genesect',
'gengar',
'geodude',
'gible',
'gigalith',
'girafarig',
'giratina',
'giratina-origin',
'glaceon',
'glalie',
'glameow',
'gligar',
'gliscor',
'gloom',
'golbat',
'goldeen',
'golduck',
'golem',
'golett',
'golurk',
'gorebyss',
'gothita',
'gothitelle',
'gothorita',
'granbull',
'graveler',
'grimer',
'grotle',
'groudon',
'grovyle',
'growlithe',
'grumpig',
'gulpin',
'gurdurr',
'gyarados',
'happiny',
'hariyama',
'haunter',
'haxorus',
'heatmor',
'heatran',
'heracross',
'herdier',
'hippopotas',
'hippowdon',
'hitmonchan',
'hitmonlee',
'hitmontop',
'honchkrow',
'hooh',
'hoothoot',
'hoppip',
'horsea',
'houndoom',
'houndour',
'huntail',
'hydreigon',
'hypno',
'igglybuff',
'illumise',
'infernape',
'ivysaur',
'jellicent',
'jigglypuff',
'jirachi',
'jolteon',
'joltik',
'jumpluff',
'jynx',
'kabuto',
'kabutops',
'kadabra',
'kakuna',
'kangaskhan',
'karrablast',
'kecleon',
'keldeo',
'keldeo-resolute',
'kingdra',
'kingler',
'kirlia',
'klang',
'klink',
'klinklang',
'koffing',
'krabby',
'kricketot',
'kricketune',
'krokorok',
'krookodile',
'kyogre',
'kyurem',
'kyurem-black',
'kyurem-white',
'lairon',
'lampent',
'landorus',
'landorus-therian',
'lanturn',
'lapras',
'larvesta',
'larvitar',
'latias',
'latios',
'leafeon',
'leavanny',
'ledian',
'ledyba',
'lickilicky',
'lickitung',
'liepard',
'lileep',
'lilligant',
'lillipup',
'linoone',
'litwick',
'lombre',
'lopunny',
'lotad',
'loudred',
'lucario',
'ludicolo',
'lugia',
'lumineon',
'lunatone',
'luvdisc',
'luxio',
'luxray',
'machamp',
'machoke',
'machop',
'magby',
'magcargo',
'magikarp',
'magmar',
'magmortar',
'magnemite',
'magneton',
'magnezone',
'makuhita',
'malaconda',
'mamoswine',
'manaphy',
'mandibuzz',
'manectric',
'mankey',
'mantine',
'mantyke',
'maractus',
'mareep',
'marill',
'marowak',
'marshtomp',
'masquerain',
'mawile',
'medicham',
'meditite',
'meganium',
'meloetta',
'meloetta-pirouette',
'meowth',
'mesprit',
'metagross',
'metang',
'metapod',
'mew',
'mewtwo',
'mienfoo',
'mienshao',
'mightyena',
'milotic',
'miltank',
'mimejr',
'minccino',
'minun',
'misdreavus',
'mismagius',
'mollux',
'moltres',
'monferno',
'mothim',
'mrmime',
'mudkip',
'muk',
'munchlax',
'munna',
'murkrow',
'musharna',
'natu',
'necturna',
'nidoking',
'nidoqueen',
'nidoranf',
'nidoranm',
'nidorina',
'nidorino',
'nincada',
'ninetales',
'ninjask',
'noctowl',
'nosepass',
'numel',
'nuzleaf',
'octillery',
'oddish',
'omanyte',
'omastar',
'onix',
'oshawott',
'pachirisu',
'palkia',
'palpitoad',
'panpour',
'pansage',
'pansear',
'paras',
'parasect',
'patrat',
'pawniard',
'pelipper',
'persian',
'petilil',
'phanpy',
'phione',
'pichu',
'pidgeot',
'pidgeotto',
'pidgey',
'pidove',
'pignite',
'pikachu',
'piloswine',
'pineco',
'pinsir',
'piplup',
'plusle',
'politoed',
'poliwag',
'poliwhirl',
'poliwrath',
'ponyta',
'poochyena',
'porygon',
'porygon2',
'porygonz',
'primeape',
'prinplup',
'probopass',
'psyduck',
'pupitar',
'purrloin',
'purugly',
'quagsire',
'quilava',
'qwilfish',
'raichu',
'raikou',
'ralts',
'rampardos',
'rapidash',
'raticate',
'rattata',
'rayquaza',
'regice',
'regigigas',
'regirock',
'registeel',
'relicanth',
'remoraid',
'reshiram',
'reuniclus',
'rhydon',
'rhyhorn',
'rhyperior',
'riolu',
'roggenrola',
'roselia',
'roserade',
'rotom',
'rotom-heat',
'rotom-mow',
'rotom-wash',
'rufflet',
'sableye',
'salamence',
'samurott',
'sandile',
'sandshrew',
'sandslash',
'sawk',
'sawsbuck',
'sawsbuck-autumn',
'sawsbuck-summer',
'sawsbuck-winter',
'sceptile',
'scizor',
'scolipede',
'scrafty',
'scraggy',
'scyther',
'seadra',
'seaking',
'sealeo',
'seedot',
'seel',
'seismitoad',
'sentret',
'serperior',
'servine',
'seviper',
'sewaddle',
'sharpedo',
'shaymin',
'shaymin-sky',
'shedinja',
'shelgon',
'shellder',
'shellos',
'shellos-east',
'shelmet',
'shieldon',
'shiftry',
'shinx',
'shroomish',
'shuckle',
'shuppet',
'sigilyph',
'silcoon',
'simipour',
'simisage',
'simisear',
'skarmory',
'skiploom',
'skitty',
'skorupi',
'skuntank',
'slaking',
'slakoth',
'slowbro',
'slowking',
'slowpoke',
'slugma',
'smeargle',
'smoochum',
'sneasel',
'snivy',
'snorlax',
'snorunt',
'snover',
'snubbull',
'solosis',
'solrock',
'spearow',
'spheal',
'spinarak',
'spinda',
'spiritomb',
'spoink',
'squirtle',
'stantler',
'staraptor',
'staravia',
'starly',
'starmie',
'staryu',
'steelix',
'stoutland',
'stunfisk',
'stunky',
'substitute',
'sudowoodo',
'suicune',
'sunflora',
'sunkern',
'surskit',
'swablu',
'swadloon',
'swalot',
'swampert',
'swanna',
'swanna-2',
'swellow',
'swinub',
'swoobat',
'taillow',
'tangela',
'tangrowth',
'tauros',
'teddiursa',
'tentacool',
'tentacruel',
'tepig',
'terrakion',
'throh',
'thundurus',
'thundurus-therian',
'timburr',
'tirtouga',
'togekiss',
'togepi',
'togetic',
'tomohawk',
'torchic',
'torkoal',
'tornadus',
'tornadus-therian',
'torterra',
'totodile',
'toxicroak',
'tranquill',
'trapinch',
'treecko',
'tropius',
'trubbish',
'turtwig',
'tympole',
'tynamo',
'typhlosion',
'tyranitar',
'tyrogue',
'umbreon',
'unfezant',
'unown',
'ursaring',
'uxie',
'vanillish',
'vanillite',
'vanilluxe',
'vaporeon',
'venipede',
'venomoth',
'venonat',
'venusaur',
'vespiquen',
'vibrava',
'victini',
'victreebel',
'vigoroth',
'vileplume',
'virizion',
'volbeat',
'volcarona',
'voltorb',
'vullaby',
'vulpix',
'wailmer',
'wailord',
'walrein',
'wartortle',
'watchog',
'weavile',
'weedle',
'weepinbell',
'weezing',
'whimsicott',
'whirlipede',
'whiscash',
'whismur',
'wigglytuff',
'wingull',
'wobbuffet',
'woobat',
'wooper',
'wormadam',
'wormadam-sandy',
'wormadam-trash',
'wurmple',
'wynaut',
'xatu',
'yamask',
'yanma',
'yanmega',
'zangoose',
'zapdos',
'zebstrika',
'zekrom',
'zigzagoon',
'zoroark',
'zorua',
'zubat',
'zweilous',
];
// Two names the 2019 tree carried a second copy of. Swanna's two files were
// byte for byte the same picture; Arceus and Arceus-Normal were two palettes of
// one pose, and only the one is drawn today. Both spend their slot on the
// picture already in the pool.
export let AVATAR_ALIASES: Record<string, string> = {
'arceus-normal': 'arceus',
'swanna-2': 'swanna',
};
// The pool in the order the forum indexes it, which is the directory listing
// scandir() used to return. Two things about that listing are the index and so
// are reproduced rather than tidied away: it sorted filenames with the
// extension attached, which orders `arceus-bug.gif` ahead of `arceus.gif` where
// the bare names sort the other way round, and it led with `.` and `..`, which
// named no avatar but counted toward the divisor. Those two slots rendered a
// broken image; they get Substitute, which the pool already carries, so they
// cost nothing but the slot.
export function avatarSlots(): string[] {
let files = [...AVATARS].sort((a, b) => `${a}.gif` < `${b}.gif` ? -1 : 1);
return ['substitute', 'substitute', ...files];
}