Shared media — art that lives once
A mission that wants art someone else made used to get its own copy of it. LegendaryMissions' 27 MB of console backdrops, card decks and portraits ended up duplicated into every mission that declared it — 314 MB on disk for 27 MB of art, and a re-release meant every one of those copies went stale.
Now the pack is unpacked once, beside the libraries, and every mission reads it there.
data/missions/__lib__/
artemis-sbs.LegendaryMissions.media.v1.4.0.zip the artifact you fetched
media/
artemis-sbs.LegendaryMissions.media.v1.4.0/ unpacked once, per VERSION
casino/terran_deck.png
helm/consoles0001.png
Using a pack
Declare it in your mission's story.json under shared_media:
{
"sbslib": ["artemis-sbs.sbs_utils.v1.4.0.sbslib"],
"shared_media": ["artemis-sbs.LegendaryMissions.media.v1.4.0.zip"]
}
Then ask for a file by its path inside the pack — never by where it was unpacked:
from sbs_utils.procedural.media_paths import media_shared
from sbs_utils.procedural.gui import gui_image_add_atlas
gui_image_add_atlas("card_back", media_shared("casino/terran_back"))
media_shared() searches, in order:
- this mission's own
media/— so a clone can keep editing art in place, - each pack the mission declares, in the order declared.
Mission-local first is what makes overriding one file easy: drop your own
media/casino/terran_back.png in and it wins, with no other change.
Never hardcode the unpacked path
__lib__/media/<pack>-v1.4.0/... contains the version. Write it by hand and your
mission breaks the next time the pack is re-released. That is the whole reason
media_shared() exists.
shared_media vs resources
Both declare a dependency on a media zip. The difference is who copies it.
| who unpacks it | where it lands | |
|---|---|---|
resources |
the engine | copied into <your mission>/media/ — a duplicate per mission |
shared_media |
sbs.pyz (at fetch/unpack time) |
__lib__/media/<pack>/ — one copy, shared |
The engine ignores shared_media, which is exactly why it can mean "don't copy this".
Use resources only when a mission genuinely needs its own writable copy.
Publishing a pack
Add the folder to your __lib__.json under zip:
{
"version": "v1.4.0",
"mastlib": ["consoles", "casino"],
"zip": ["media"]
}
sbs.pyz lib <folder> builds <owner>.<repo>.media.<version>.zip alongside your
.mastlib files, and sbs.pyz release publishes it as a release asset.
If your repo publishes a pack, you probably also want the art out of the source
archive that consumers download — otherwise a fetch still drags 27 MB along that
nothing reads. .gitattributes:
media/ export-ignore
mkdocs/ export-ignore
.github/ export-ignore
export-ignore governs git archive only, so a clone and CI still get everything and
the pack still builds from the real art.
Not only images
A pack is the natural home for anything the engine opens directly, because it unpacks to
a real folder on disk and a .mastlib is a zip. A mod puts its ship data there as well
as its meshes, and names it with ship_data_add_extra — which points both the engine
and sbs_utils at that one file, with nothing written. See
Making a mod.
Things worth knowing
sub_rectis in pixels. Cutting a cell out of a sheet isgui_image_add_atlas(key, file, l, t, r, b)in pixel coordinates, not 0–1 texture coordinates. See Icons by name.- Re-releasing a pack doesn't reach existing copies.
sbs.pyz fetchskips a dependency already in__lib__; pass-o/--overwrite_libsto pull the new one. - Unpacking is per version, so two missions pinned to different pack versions coexist.
- A mission pinned to an older
sbs_utilshas nomedia_shared(), and should keep its ownmedia/folder as before. - Images and skyboxes are proven from a shared pack. Sheets, backdrops and portraits
were already; a skybox was confirmed in the engine on 2026-08-16 by running
LegendaryMissions with
profile=a28_skies, which drops the stock skies and draws the Artemis 2.8 ones straight out of__lib__/media/…/skybox/. - Music cannot come from a pack, and that is the engine's limit, not a gap here.
set_music_foldertakes a bare folder name underdata/audio/music; hand it a path in any spelling and the engine segfaults (an absolute one hangs instead). sbs_utils resolves a pack's bank, declines to hand the path over, warns, and playsdefault. A player who wants a mod's bank copies it intodata/audio/music/<name>. Gated behindMUSIC_ENGINE_ACCEPTS_PATHSfor the day that changes.