xyicons/ is built from src/minisprites/pokemon/gen6 alone, which holds no
totem, no Mimikyu-Busted and no Gourgeist or Pumpkaboo size forme. Nor does
anywhere else in the tree: home/ has the size formes as 70x112 HOME renders
against gen6's 20x26 XY icons, and no directory anywhere has a totem icon at
all. There is nothing upstream to take either, since PS ships icons as one
dexnum-indexed sheet rather than as files per name.
That is because the games drew those formes no icon of their own. PS's client
sends each to the nearest forme that has art, and ps-pokemon.sheet.mjs already
carries the list, commented out, under "alt forms with duplicate icons". Walk
the same chain rather than leave 22 names 404ing.
The chain cannot be read off a published name, since a base name carries dashes
too (kommo-o, mr-mime, porygon-z), and dropping one word blindly strands
Toxtricity-Low-Key on a Toxtricity-Low that does not exist. So
tools/spritedata/formes.ts writes data/lib/formes.ts, pointing each forme at the
longest run of its leading words that names a forme of its own, and at the base
species otherwise.
xyicons/ gains 22: the 12 totems, Mimikyu-Busted, the six Gourgeist and
Pumpkaboo sizes, Greninja-Bond, Rockruff-Dusk and Pichu-Spiky-Eared. Each is
byte for byte the sprite it fell back to. Togedemaru-Totem is among them and the
server has never had it, so that one is a gain rather than a restoration. Only
this set walks the chain; xy/ has real art for all 22. The species PS keeps but
nobody ever drew, missingno and the 15 pokestar entries, have no ancestor with
an icon and so still get nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PS carries the species and the dex asks for toxtricity-low-key-gmax, so the
build owes a picture for it, but the games drew one Gigantamax Toxtricity and
not two. PS's own icon sheet says as much: ps-pokemon.sheet.mjs records
`toxtricitylowkeygmax: 1320 + 69` among the "alt forms with duplicate icons",
the slot toxtricitygmax already sits in, and play.pokemonshowdown.com serves no
low-key gmax animation under any spelling. Nothing under src/ has a
-olow_key-g gif either, so there is nothing to import and nothing worth
drawing.
Publish the Amped sprite under both names. SPECIES_ALIASES exists for exactly
this shape and nothing else claims the name, so it costs one entry, and xy/,
xyicons/, the forumsprites manifest and the smogdex sheet pick it up together
through smogonNames(): 2118 css classes become 2119, and no existing name
moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The smogon side serves 62 Alcremie animations the build has never made,
alcremie-caramel-swirl-berry and its kin, one per cream and sweet. PS models
the sweet nowhere in pokedex.js, so spriteFiles() will never name one, and
nothing under src/ has ever held one: they live on that server and nowhere
else, which is what --delete-after would have taken.
The sweet cannot ride inside -o. The two readings of a name disagree about
where the cream's words divide, `caramel-swirl` against `caramelswirl`, but
agree about the boundary in front of the sweet, `-berry` for both. Fold the
sweet into the forme and whichever reading you spell it for, the other one
comes out wrong. So -k carries it, published after the forme and before the
variant flags, and formatFilename now leads with every part publishedName
spells rather than with -o alone, for the reason it always led with -o: a
forme's sprites should sort together.
The art comes from play.pokemonshowdown.com/sprites/ani, which serves all 54
combinations the games draw, at the 54x73 the creams here already use. Of the
62 on the smogon server, 8 are <cream>-strawberry: strawberry is the default
sweet, spelled as the bare cream both upstream and in the list chatot keeps, so
nothing asks for those and they go with the other leftovers.
Vanilla cream ships as an explicit -ovanilla_cream, because PS's base Alcremie
publishes as `alcremie` while the sweets are named off the cream.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xy/ and xyicons/ rsync into /smog2/sprites, and the dex composes those urls
itself, sprites/xy/charizard.gif, with no lookup in between. Manifest
content-stamps every copy and writes `name.ext` -> the stamped filename beside
it, which is worth something only to a reader that consults the manifest, and
nothing on the dex side does yet. A deploy of these two sets therefore lands a
tree the dex cannot read.
Copy under the published name with no stamp, and drop the manifests, which
have no second name left to record. The duplicate check survives the loss:
ActionQueue already refuses two copies to one path, so the collision
Manifest.copy used to throw on is an invalid queue instead, and the
published-name layering of the last commit is still what keeps xy/ clear of
it. 1650 files under xy/, 1533 under xyicons/.
assets/ keeps its Manifest and its stamps, since the smogdex reads those urls
out of __meta rather than composing them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
The Smogon Sprite Project's gen 5 sprites arrived under the packed ids that
"Name sprites by name" had just retired, s<num*32 + formeNum>, so
s10209-b-s.png where the tree now wants ssharpedo-omega-b-s.png. Nothing
validates a filename any more, so they built and published as-is: the xy/
manifest took its names off the stem and called Sharpedo-Mega "10209" and
Gigantamax Meowth "1664-gmax".
Apply the mapping tools/spritedata/rename.ts applied, over src/sprites/gen5
alone and only for the stems that are still ids. 2716 files, all png, bytes
unchanged; the 391 pngs and 3344 gifs already named were left alone. Every
name the ids resolve to is one tools/spritedata/names.ts prints, and none of
them collides with a file already in the directory under either extension, so
the gifs still win the xy/ copy wherever a species has both.
The 33 files flagged -g are Gigantamax, which the data treats as a flag and
not a forme, so they ride on the base name rather than gaining an -o.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PS calls the male Meowstic just "Meowstic" -- baseForme M, with Meowstic-F
as the alt forme -- and every published name here falls out of the PS data,
so the tree ships meowstic and meowstic-f. The smogdex splits the pair
evenly and calls that entry Meowstic-M: dex-sync's getName renames it on
import, and toAlias turns that into meowstic-m, which is what the page
background, the sprite- sheet class, and the (minisprite:) embeds all ask
for. Nothing produced that name, so the dex has been living off hand-placed
leftovers under xy/ and xyicons/ that no build has touched in years and that
rsync has no reason to delete.
Give the smogon side an alias table, as the items already have, and publish
the sprite under both names. Only that side gets it: PS asks for meowstic
and the filename already says meowstic. This is the whole of the
disagreement -- the other four renames in getName are a move, an accent, and
two spaces that slug back to what we emit -- so one entry covers it.
The sheet had its own copy of publishedName inline; it wants the aliases
too, so it now calls the shared one. No name changes: the two agreed on
every file in the tree, since none carries both -g and -f.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TEPwEpwMrUFN8Paa9Tb8r
src/ named 48442 sprites by a packed number, s<num*32 + formeNum> for
species and i<num> for items, so s193-b-s.gif is Shiny Charizard-Mega-X's
back sprite and there was no way to know that without looking it up in
data/species.json. The table was the only thing making the tree
navigable, and it existed only because the filenames were opaque.
Put the name in the filename and the table has no job left, so this does
both. A name is encoded rather than spelled: a run of non-alphanumerics
becomes _ when it separates words and vanishes otherwise, decomposing
first so accents are dropped on purpose rather than by accident of how
the source spelled them. Recording where the words divide is what lets
smogon read a name as ho-oh and PS read it as hooh off the same file,
with nothing looking anything up. Formes ride in -o, which keeps the
boundary the two readings differ across and groups a forme's sprites
together under ls.
s/i/x stay, so a directory can hold species and items with rules
matching them apart, and so xatu isn't mistaken for an escape. The x
escape's only other job was telling parseFilename not to resolve a name,
which nothing does now.
Where PS's own formeOrder gives one name to several of the games' forme
slots, nothing tells them apart, so the lowest slot keeps the bare name
and the rest carry -c: 16 files, all in previews/gen9.
The three jobs the table had left, each somewhere narrower now: published
names come off the filename; the 12 items that ship under a second name
are a literal in data/lib; base-forme sheet slots moved to
ps-pokemon.dexnums.mjs last commit. Nothing validates a filename any
more, which get() used to do by throwing; tools/spritedata/names.ts
prints what should exist so a directory can be checked against it.
The smogon and ps deploys materialize byte for byte identically. assets
moves five forumsprites keys, because toSmogonAlias was missing a /g and
so dashed only the first space: deep-seascale, deep-seatooth,
gold-bottlecap, prz-cureberry and psn-cureberry gain their missing
dashes, and the sprite bytes behind them are unchanged. That bug cannot
survive the encoding, which cannot tell which boundary came first. The
smogdex sheet relaid out, since spritesmith packs in glob order; its
2117 css class names are identical.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQicnpaee6kQXGG7uVExLS
ps-pokemon.sheet.mjs reads entry.num out of data/species.json to place a
base species on the sheet, which is the last thing in the build that
needs a number from that table. Move it to ps-pokemon.dexnums.mjs,
generated from pokemon-showdown alongside the hand-kept alt-forme index
table the file already carries.
The sheet is nameSensitive and rebuilt: pokemonicons-sheet.png hashes
the same before and after, and all three deploys materialize byte for
byte identically.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQicnpaee6kQXGG7uVExLS
data/species.json and data/items.json were generated by scripts that
lived outside the tree, so nothing here recorded where the tables came
from or how to refresh them. Port both into tools/spritedata/, taking
the pokemon-showdown checkout as an argument instead of hardcoding an
absolute path.
Both reproduce the committed tables byte-identically against
pokemon-showdown f2fe71c87.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQicnpaee6kQXGG7uVExLS
rules/ exists so several deploys can declare the same rules; a set with
one caller is just indirection. Only gen10Modelslike has two (smogon xy/
and ps ani/), so it stays. The live sets move into assets.build.ts and
smogon.build.ts, and the three deprecated sets move into the commented-out
block that was their only reference, which empties minisprites.ts and
social.ts.
Declarations are copied verbatim, so no rule key moves: a full dry run
reports 7748 up to date, 0 would run, and all three deploy trees come out
byte-identical.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQicnpaee6kQXGG7uVExLS
Nothing chains any more, so Input collapses to string and the machinery
that existed to order rules against each other goes with it: the
executor's demand graph and per-decl memo, the DryDirty sentinel, the
whole blocked outcome, the driver's producer closure, and inspect's
transitive-consumer fixpoint. What is left is a flat parallel sweep whose
only coupling is byte-identical declarations sharing one execution.
Every deploy's tree comes out byte-identical and every rule key is
unchanged: a full dry run reports 7748 up to date, 0 would run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQicnpaee6kQXGG7uVExLS
The sheet's png fed a separate cwebp rule, the one place a rule output
was another rule's input. A third output plus %o1 says the same thing
in one rule; the published css and webp come out byte-identical.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQicnpaee6kQXGG7uVExLS
The prefix a manifest value needs isn't knowable here: it lives in the
receiving home's services.toml, and the upload prepends it. So the two
guessed at each other and every reader carried a copy -- the forum's
SMOGON_XF_SPRITE_URL, the dex's hardcoded /__assets/sprites/.
Write the prefix on this side instead: everything served ships under
sprites/ in the tar, the upload rejects a tree that disagrees, and with
the tar path settled a manifest entry can be the whole url. Keys lose
their extension, since a url has nothing left to disambiguate. __meta/
stays at the tar root -- the upload diverts it out of the served tree.
The css pointer becomes spritesheet-css-url.txt, holding the url rather
than a suffix to paste onto one; the rename is deliberate, so a reader
of the old file fails instead of splicing a url into a prefix.
The minisprites webps go: 9d3e9abe shipped them speculatively in 2023
and nothing has read them or __meta/minisprites-hash.txt since, in any
repo. The dex draws minisprites from the spritesheet, and these are
named by PS id where it uses smogon aliases, so they never could have
lined up. newspritecopy() went with them, its only caller.
xy/ and xyicons/ keep the old shape: they ship by rsync and don't know
their url yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The ctx is #private now; Manifest.copy(f, dest, name) owns the
hash-stamp-record-queue sequence (absorbing set()'s duplicate-key
check), and the sprite-name resolution in spritecopy/itemspritecopy
stays outside as free functions calling it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DeployCtx read/list/hash return promises (copy and write stay sync:
they only queue ops), the stamped copy helpers and buildfile deploy
blocks await them, ActionQueue materialization and packing use
fs/promises, and the CLI, config loaders, spritesheet writer, and
trim tool follow. Declaration-time globbing and the module-init data
reads stay sync: rules exist by the end of the import.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hash and cas move to node:fs/promises: source reconciliation runs a
small worker pool over the stat cache, the executor's clean check
gathers CAS stats concurrently, and inserts/sweeps await their way
through. The sqlite store stays sync; that is all node:sqlite offers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Parsers use charAt or check-and-throw; the executor gets a paired()
helper for its parallel arrays; matchSubsets returns {entry, matched}
pairs so the deploy loop stops indexing two arrays; spawn stdin and
pid get real guards.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Interfaces become type aliases with comma members (extends becomes
intersection), and class privates use #names instead of the private
keyword.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Node builtins come in as node:-prefixed namespace imports;
multi-symbol internal imports use short namespace aliases (artifact,
cas, subst, db, api, executor). Single-symbol pulls, types, and the
buildfile DSL names stay braced, matching the sanctioned exceptions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per the house rule, const survives only where TypeScript hangs
behavior off the spelling: the <const T> type parameter in rule()
and the as const option tables.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Annotation colons are tight (ternaries keep their spaces), plain
double-quoted strings become single-quoted, and .editorconfig comes
over verbatim. Rule cmd values are untouched; the sheet rules reran
once because their tool sources are declared deps, byte-identically.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
deploy(ctx => ...) registers like rule() does, so a buildFile
co-locates each shipping block with the rules it ships instead of
one monolithic finish; the blocks run in registration order after
the build, sharing one output tree per module. The CLI slices the
registry across its sequential imports to know which blocks belong
to which file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The xy and xyicons rsyncs move from ad-hoc scripts (updatexyicons.sh
and the untracked syncdexani.sh) into dir-mode deploy entries;
deploy smogon replaces both.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
deploy <names> -o <dir> materializes each entry's subset under
<dir>/<name>/<entry index>/ instead of running its command (tar
entries included), for eyeballing what would ship.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nothing in tools/build or tools/deploy imports debug, and the
deploy package never imports sprite-data (its consumers live in
the root package, which declares it).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unhandled rejections crash Node by default since v15, and node:sqlite
covers the Store: readBigInts replaces defaultSafeIntegers, a manual
BEGIN/COMMIT helper replaces transaction(), busy detection keys on
errcode, and lookupRule normalizes the null-prototype rows. No native
build step remains (allowBuilds gone from the workspace).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deploy CLI gets a hand-rolled verb dispatch over per-verb
parseArgs option tables and one usage text; trim and
smogdexspritesheet are plain parseArgs calls. tools/build only
listed the dep without importing it.
The smogdex sheet rule depends on its tool's source, so it reran
once; the rebuild was byte-identical (stamps unchanged).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An entry with dir: true materializes its subset into a temp
directory whose path replaces %d in the cmd, instead of piping a
tar on stdin -- rsync-style transports (the PS ani sync) need real
files. The flag and %d must agree, so a forgotten one is caught at
config load.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A dying consumer destroys the tar pack and its pending entry sinks,
each of which emits the EPIPE as an unhandled streamx error and
crashed the process before the child's exit code was reported. Keep
the pack and its sinks quiet; the command's exit code is the report.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying is opt-in per name; with no names the verb prints each
deploy's buildFile and (subset, cmd) entries instead of shipping
everything.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An untracked json5 config maps deploy names to a buildFile plus
(subset globs, cmd) entries: the deploy verb builds the named
buildFiles, finishes each, routes the outputs through the subset
globs, and pipes one tar per entry to its command. Every glob must
match an output and every output must be covered by some entry.
The tar no longer carries __key; the upload command names the asset
set itself (smogonctl assets upload sprites).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
They are the buildFiles the upcoming deploy config points at; "deploy"
now belongs to the config entries that ship their outputs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- casInsert verifies an existing object's bytes before trusting it, fsyncs
before the rename, and the store records object sizes so a crash-truncated
object reads as dirty instead of clean forever.
- The driver hashes sources over the producer closure, so partial builds
(inspect) that pull in producers outside the demanded set work.
- Unknown executor errors are reported and abort the build instead of
vanishing into allSettled.
- %b/%B expand to nominal names at declaration (never CAS basenames), which
also puts name-dependence in the identity key; cmds are key-joined with
NUL so a multi-line command cannot collide with split commands. This
churns every rule key once.
- Rules must declare at least one output; output names reject shell-hostile
characters.
- run --link copies read-only (CAS) sources instead of hardlinking 0444
modes into deploy trees; ctx.list filters dotfiles and directories like
the build-side glob; inspect resolves targets against the invoking cwd.
- The registry dedupes identical declarations (same cmds/inputs/deps/
outputs/display), returning the existing artifacts, so shared rule sets
are plain functions and memo() is gone; ps.deploy.ts no longer declares
the gen5 gif set it never ships.
- rule() returns a tuple typed by its literal output list (single string
output returns the Artifact directly), so destructuring needs no
non-null assertions.
- Manifest is a class carrying the deploy ctx: set() rejects duplicate
keys, write(dst) emits the sorted JSON, and spritecopy/itemspritecopy/
stampcopy take just the manifest. The unstamped smogon sets (xyitems,
fbsprites, twittersprites) are deprecated and commented out.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pnpm build/deploy now run the umbrella CLI (build/deploy/run/inspect); the
fixed-name build/ tree, the old planner (rename detection, adopt, tamper
checks), and the vm deploy sandbox are gone. Also lands tools/deploy/api.ts
and the CLI, which earlier commits referenced but an overbroad local
gitignore pattern had silently excluded.
Migration: the store migrates the v1 db in place; build/ is dead and can be
deleted. A full rebuild repopulated .build/cas, and the old and new deploy
trees were diffed: byte-identical except the two multi-file hash pointers
(algorithm change), fb/twitter PNG re-encodes (pixel-identical; old outputs
predated current tool versions), and a stale 2023 orphan sprite the old
system never cleaned up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
date:create/date:modify tEXt chunks made rebuilds nondeterministic, which
would churn every content-addressed name.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rules/ holds the shared sets (memoized) and the publish helpers that were
duplicated across the vm scripts; assets/smogon/ps .deploy.ts each declare
their rules and finish. The smogdex sheet is split into two chained rules,
retiring the undeclared spritesheet.png temp.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
makeCtx serves finish over the CAS and ActionQueue; cli.ts adds the
build/deploy/run/inspect verbs (unwired until cutover). Copy-mode
materialization chmods CAS-sourced files back to 0644.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keys resolve after producers finish, so there is no upfront plan; identical
keys share one execution, failures block dependents, and GC (union builds
only) drops undeclared keys then sweeps the CAS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
helpers.ts is a pure move out of api.ts; artifact.ts introduces
Artifact-returning rule()/forEachRule() with execution-time content keys.
The old api.ts machinery still powers Buildfile.ts until cutover.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keeps the v1 file_cache on migration; failures are never recorded, so
failed and never-ran are the same state.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
updatepmdsprites.sh is gone entirely; updateminisprites.sh loses its
spritesheet/forumsprites/dex-suffix steps and becomes updatexyicons.sh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deploy tool grows a deploy subcommand that runs the build, packs the
tar, and streams it to the command .env names. Packing now finalizes the
archive; without the end-of-archive marker, strict stream readers
(Python tarfile, hence smogonctl) die on truncation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
spritesheet, minisprites, pmd, and forumsprites now ship as a tar to the
__assets tree: __key leads the tar, and the mutable pointer files
(manifests, css suffix, minisprites hash) ride in __meta/ instead of
being published as immortal assets. xy and xyicons stay in
smogon.deploy.js on the rsync path for now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the esbuild invocation that ran out of the smogon.com checkout:
the deploy script now stamps the webp, rewrites the css urls, and writes
the hashed css plus the suffix file that updateminisprites.sh ships to
/smog2/dex/spritesheet_css_suffix.txt. updateminisprites.sh no longer
depends on ~/smogon/smogon.com/run, esbuild, python, or zopfli/brotli
(css precompression dropped; to be handled separately). Minification is
also dropped (machine-generated css, ~same size compressed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>