Until now the only way a creature got a picture on this site was for an
operator to open UOFiddler on a desktop, export sprites by hand, copy them to
the web host and write a spawnAtlas.art.json naming each one. Almost nobody
did, so shard_spawn_creatures.art was NULL on every install.
The shard has had those files the whole time. Admin -> Shard -> Import now
walks its asset manifest, fetches only the sprites whose hash changed, writes
them under uploads/atlas/, asks the shard for a body id per atlas creature
(§8: it CONSTRUCTS the creature and reads Body.BodyID, which is the only thing
that is right for a shard's own custom creatures) and points each creature at
its picture. On a stock client that is 787 portraits, about a megabyte.
**The one thing v8.md §12 got wrong, and it is not cosmetic.** It says
`shard_spawn_creatures.art` "starts being filled by the import". That table is
emptied and refilled by replaceAtlas on EVERY atlas refresh, and a refresh runs
on every boot -- so a filename stored there would be destroyed by an ordinary
re-parse of the ServUO tree, with the next Update finding the client files
unchanged, reporting "nothing to do", and never restoring it. Nothing would
report a fault; the pictures would just be gone.
So the assets and the body map live in their own tables outside that blast
radius, and applyAtlas re-derives `art` on the way past as
`{ ...derived, ...operatorMap }` -- which is also the one place "the operator's
own artwork wins" is enforced, on every rebuild rather than only at import.
Smaller decisions worth not rediscovering:
- The derivation joins on the catalogue KEY, not on the body id. The simpler
join is correct today and stops being correct the moment phase 6 adds
body/400/a2/f0, at which point one slug matches dozens of rows.
- Filenames are content-addressed. A stable name overwritten in place leaves
every browser and CDN serving the previous client's sprite, with the database
row perfectly correct.
- An unchanged key whose FILE is missing is fetched again. The row and the disk
can disagree (a wiped uploads volume, a restore from a dump), and a broken
image on a creature page is worse than one re-fetched sprite.
- A key the shard cannot render is not a failure. Two thirds of the playable
ghost and gargoyle bodies have no art on a stock client, and an import that
reported eight failures every time would teach an operator to ignore the panel.
- A key that VANISHED from the manifest needs review before anything changes:
an unmounted client volume and a deliberate downgrade look identical here.
23 new tests; 674 server and 42 client tests pass. The SQL was also run against
a real MariaDB, which is what proved the CONCAT join and the singleton CHECK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
381 lines
8.3 KiB
JSON
381 lines
8.3 KiB
JSON
{
|
|
"$comment": "Generated inventory of the URLs module-uo serves - the module half of the freeze core keeps in server/routes.manifest.json. DERIVED as the difference between a core without this module and the same core with it, both at the pinned ref in ci/core-ref.json. Regenerate with the frozen-manifest workflow; see server/scripts/frozenManifest.js.",
|
|
"routes": [
|
|
{
|
|
"method": "DELETE",
|
|
"path": "/api/v1/admin/uo-link/towncrier/:id",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "DELETE",
|
|
"path": "/api/v1/admin/users/:id/shard/link/:account",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/accounts",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/assets",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/atlas",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/audit",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/char/:serial",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/clilocs",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/houses",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/pages",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/roster/:account",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/sales",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/vendors/:account",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/shard/visibility",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/uo-link/config",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/uo-link/signup-mode",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/uo-link/stream",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/users/:id/shard/accounts",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/users/:id/shard/houses",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/users/:id/shard/online",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/users/:id/shard/sales",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/admin/users/:id/shard/standing",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/player/shard/accounts",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/player/shard/char/:serial",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/player/shard/houses",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/player/shard/roster/:account",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/player/shard/sales",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/player/shard/vendors/:account",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/atlas/champions",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/atlas/creatures",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/atlas/creatures/:slug",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/atlas/landmarks",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/atlas/meta",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/atlas/regions",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/champs",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/economy",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/features",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/feed",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/governors",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/governors/:city/history",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/guilds",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/guilds/:id",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/houses",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/idoc",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/market",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/market/meta",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/market/vendors/:serial",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/online",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/points",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/points/:system",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/presence",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/ruleset",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/status",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "GET",
|
|
"path": "/api/v1/public/shard/stream",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/account",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/assets/import",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/atlas/approve",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/atlas/import",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/atlas/reject",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/ban",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/broadcast",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/clilocs/import",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/kick",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/link",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/pages/:id/close",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/pages/:id/respond",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/shard/unban",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/admin/uo-link/towncrier",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/player/shard/account",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/api/v1/player/shard/link",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "PUT",
|
|
"path": "/api/v1/admin/shard/atlas/path",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "PUT",
|
|
"path": "/api/v1/admin/shard/clilocs/path",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "PUT",
|
|
"path": "/api/v1/admin/shard/visibility",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "PUT",
|
|
"path": "/api/v1/admin/uo-link/config",
|
|
"tier": "public"
|
|
},
|
|
{
|
|
"method": "PUT",
|
|
"path": "/api/v1/admin/uo-link/signup-mode",
|
|
"tier": "public"
|
|
}
|
|
]
|
|
}
|