Before anything else, run the two diagnostics. They tell you which half is broken in one step.
weaponskin_debug walks the whole skin chain in the order it can break, server side included.
weaponskin_testskin paints the weapon in your hand flat magenta straight from a runtime texture — skipping the studio, the purchase, the allocator and the download.
Result
What it means
The weapon turns magenta
The engine side works. The fault is upstream — the design, the purchase, the allocator or the download
Nothing happens
The fault is in the engine side: the pool, the shells or the DUI path
weaponskin_testskin_off takes the test shell back off.
Cause: the pool was rebuilt while players were online, so it is stale.
Solution:restart the server. A client reads weapon metadata once per session, so the players already in game keep the old pool — letting new ones in would give them a session where no skin works at all.
The console repeats the warning every 20 seconds until somebody restarts. Config.Pool.BlockJoinWhenStale = false turns the guard off, but it does not fix the mismatch.
None of this happens while Config.Pool.AutoGenerate is false, which is the default. The shipped pool is pre-built and needs no rebuild.
Solution: the shipped pool matches the shipped Slots and enabled settings, and changing those does not rebuild it — the studio keeps offering what the pool actually holds, and the console says so at boot.
Either keep the shipped settings, or set Config.Pool.AutoGenerate = true and rebuild on an empty server.
Cause: they shouldn't — the pool patches no weapon metadata at all.
Solution:start order relative to other weapon packs does not matter. The shells are declared as plain object archetypes, so there is nothing to conflict with.
This was not always true. Patching weapons.meta to register shells as real weapon components cost three separate client crashes and could never work for most weapons. A build test now fails if a weapon meta ever comes back.
Cause: the stock weapon body is being drawn under the skin.
Solution: the stock weapon is meant to be hidden, not covered — two coincident surfaces cannot be made to stop fighting over the depth buffer. Force it with:
Cause: the host does not send CORS headers, or it is not on the allowlist.
Solution: this is deliberate and it happens at paste time, not at checkout. Without CORS headers the export canvas would be tainted and the skin could not be built at all — so the link is tested and refused there, with an explanation.
Use a host that sends the headers, or add it to the allowlist in custom/config.lua.
Solution:your own weapon — first person and /inspect — gets the full texture. Everyone else's gets a small one: a quarter of the download and a quarter of the VRAM, on a gun across the street.
Cause: the design cannot be baked by the viewer's client.
Solution: run weaponskin_recipes — it reports how many skins clients bake for themselves and the reason each of the rest falls back. Pass a hash to re-check one from scratch.
A design built on an AI picture can never be baked (those live on the server and are served to their owner alone), and any render that fails or times out falls back automatically. This can only ever save traffic.
Cause: the payload outgrew a reliable event, which carries ~64 KiB for the whole message and the engine simply declines to send anything larger, with no error on either side.
Solution: this is already handled — everything that grows with use (skin lists, the catalog, uploads, animated frames) travels latently. If you have modified that path, put it back.
Cause: it outran the engine's inbound limiter (75 fragments/second, ~153 KB/s).
Solution: uploads are paced under that limiter by design. Note that buying uploads nothing in the normal case — only a design carrying an AI image still uploads its PNGs.
Cause: that client is not the one this resource ships.
Solution: the limits sit well above what the UI does, with tighter limits on anything that spends money or guesses a share code. Treat the line as what it is — a modified client, not a tuning problem.
Cause: they weren't — settings live in the database.
Solution:qs_weaponskin_settings stores one row per value you actually changed, so an update never overwrites your tuning. Reset puts a section back to what the resource ships with.
config/, custom/ and locales/ are in escrow_ignore and stay yours too.
Cause: your locale file is incomplete or outdated after an update.
Solution:missing keys fall back to English, so this is a warning, not a failure. Copy the missing key from the English locale — including the labels in the Settings panel, which live in the same files.
Cause: the pool is ~212 MB of stream files, and every slot is a file every player downloads.
Solution: stream files arrive per weapon, the first time a client sees one — not all at once. To cut it down, set Config.Pool.AutoGenerate = true and rebuild with fewer slots or fewer weapons.