Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The resource system

Verified against Minecraft 26.3 · Part II · A player presses F3+T, the screen goes to the logo and a bar, and every texture, model, sound and font is rebuilt from a stack of packs without the game stopping.

A player presses F3+T. The screen goes red, the Mojang Studios logo comes up, and a white bar creeps across under it while the old world keeps rendering behind. What is happening is one pipeline that everything the game reads from a pack goes through — textures, models, sounds, language strings, recipes, advancements, loot tables, tags, worldgen JSON: a stack of packs is discovered, merged into a resource manager that is a snapshot of the stack, and a list of reload listeners each rebuild their world from it, preparing at once on the worker pool (anatomy) where they have anything to prepare, and swapping their live state on the owning thread in the order they were registered. The client’s stack is resource packs (PackType.CLIENT_RESOURCES, the assets tree); the server’s is data packs (PackType.SERVER_DATA, the data tree) — same classes, two instances, two directories, and /reload is the same pipeline run by the server. The surprising part is the end. A reload that fails does not find the offending pack. Minecraft.rollbackResourcePacks deselects every resource pack it can, clears the options lists, saves, and reloads again — and if vanilla was the only selected pack it rethrows and crashes instead.

The cast

classwhat it decidesthread
PackRepository · Packwhich packs exist, which are selected, and in what orderwhoever asks: Render on the client, Server on the server
MultiPackResourceManager · FallbackResourceManagerwhich pack’s copy of a file wins, one stack per namespacebuilt on the asking thread, read from workers
ReloadableResourceManagerthe client’s long-lived façade: the current snapshot and the listener listRender
PreparableReloadListener · SimplePreparableReloadListener · SimpleJsonResourceReloadListenerwhat to read off-thread and what to swap on-threadprepare on the worker pool, apply on the owner
SimpleReloadInstancethe schedule: every prepare at once, every apply in order behind a barrierbuilt on the caller’s thread, barriers resolved on the owner
LoadingOverlaywhen the client’s reload is done, and what to do if it failedRender
ReloadableServerResourcesthe server’s one listener, and the registries that replaced the restServer

The pipeline

flowchart TD
    D["discover: PackRepository.reload re-runs every RepositorySource"] --> S["snapshot: a new MultiPackResourceManager over the opened packs"]
    S --> P["prepare: the worker pool reads, all at once"]
    P --> A["apply: each listener swaps its live state, in registration order"]
    A -- "ReloadInstance.checkExceptions finds none" --> F["finish: the level re-extracted"]
    A -- "a listener threw" --> R["roll back: every pack deselected"]
    R -- "the reload run again" --> D

The five stages and the one branch, which comes at the end and not at the start. A snapshot is of the pack list, not of the bytes; the rollback arrow runs back to discover because what follows a failure is another whole reload, not a retry of the stage that failed.

The rest of the page is a section per stage: what comes in, what is decided, what goes out. F3+T is the grounding trace; /reload is the coda, as a table of where the server’s run of the same pipeline differs.

Discover: the repository and its packs

What comes in is a set of RepositorySources (server/packs/repository), each a place packs are found. ClientPackSource and ServerPacksSource are the built-ins, both extending BuiltInPackSource, which also lists the packs bundled inside the vanilla pack — the art packs, the accessibility packs and every feature pack (BuiltInPackSource.TESTS_ID names one more, tests, which only a development build enables); FolderRepositorySource is a directory of user packs; DownloadedPackSource is server-sent packs, client only. PackRepository.reload re-runs every source into the available map and then rebuilds the selected list: prior choices are kept, and a pack whose Pack.isRequired is true is force-inserted at its Pack.getDefaultPosition. Reading the order out of options.txt is a startup step — Options.loadSelectedResourcePacks runs once in the Minecraft constructor, not on every F3+T.

A Pack is a discoverable pack: a PackLocationInfo (id, title, PackSource, optional KnownPack), a Pack.ResourcesSupplier that can open it, its Pack.Metadata (description, PackCompatibility, requested feature flags, overlays) and a PackSelectionConfig — required, default Pack.Position, fixed. Pack.Position owns the insertion algorithm that makes a fixed pack stick, and the list it inserts into runs from the bottom of the stack to the top: Pack.Position.BOTTOM inserts at the front of the list, past any pack already fixed there, and Pack.Position.TOP at the back. The last pack in the list wins (next section), which is why vanilla is BOTTOM and why “higher in the UI” means “later in the list”. The client’s vanilla pack cannot be deselected and the server’s can: ClientPackSource marks vanilla required and bottom; ServerPacksSource marks it bottom but optional.

What a pack is on disk is PackResources (server/packs): the raw file source with PackResources.getResource, PackResources.listResources and PackResources.getNamespaces. FilePackResources is a zip; PathPackResources a directory; OverlayedPackResources a pack plus its overlays subdirectories, which the Pack.ResourcesSupplier assembles for zip and folder packs. VanillaPackResources is not a PackResources itself: its Pack.ResourcesSupplier hands out FixedPathPackResources layers — the jar’s own assets and data, and on the client the launcher’s asset index above them — and never an overlay. Discovery is guarded: DirectoryValidator, ForbiddenSymlinkInfo and PackDetector decide what a folder is allowed to be, allowed_symlinks.txt is parsed into a DirectoryValidator by LevelStorageSource.parseValidator. One corner of server/packs is worth a sentence here. packs/linkfs is a synthetic read-only file system — LinkFileSystem, LinkFSProvider and a LinkFSPath that is a name in a tree rather than a name on disk — which presents the launcher’s asset files, stored under their hashes, as the one directory tree their index describes, so the game can open a pack that was never assembled.

What pack.mcmeta says

The file is read as ResourceMetadata sections: PackMetadataSection (description and a PackFormat range), FeatureFlagsMetadataSection, OverlayMetadataSection, ResourceFilterSection. Compatibility is a range, not a number. PackMetadataSection carries an inclusive range of PackFormat major/minor pairs and PackCompatibility reports too old, too new, unknown or compatible against the game’s own — resource 97.1 and data 121.0. Above PackFormat.lastPreMinorVersion (64 for assets, 81 for data) the min_format / max_format fields are mandatory and the old integer pack_format is not enough. A pack.mcmeta the strict codec rejects gets one more chance through a description-only fallback so the pack can at least be listed as incompatible. Overlays are versioned sub-packs: OverlayMetadataSection maps a PackFormat range to an overlays subdirectory that OverlayedPackResources layers on top of the pack itself, so one zip can carry variants for several game versions.

Feature flags are packs, but not auto-selected ones. A feature pack is a built-in pack carrying a FeatureFlagsMetadataSection, and its PackSource deliberately reports that it must not be added automatically. MinecraftServer.enableForcedFeaturePacks force-selects the ones matching the world’s forced features, and the world’s flag set is the selected packs’ requested flags joined with those forced ones. Turning on an experiment is enabling a data pack — through a different door than ordinary packs use. What the resulting FeatureFlagSet then gates is a registry lookup, and that is identifiers and registries.

What goes out of the stage is PackRepository.openAllSelected: the selected Packs opened into a list of PackResources, in order.

Snapshot: the manager

MultiPackResourceManager (server/packs/resources) is built from that list. It is a snapshot of the pack list, not of the bytes: it asks each pack for its namespaces — the first half of every id, the name of a directory under the pack’s assets or data root: minecraft for vanilla’s own files and whatever its author chose for the rest — and builds one FallbackResourceManager per namespace, each a stack searched from the last selected pack down. The old world stays up until the last apply, but the old files do not: on the client, ReloadableResourceManager.createReload closes the previous MultiPackResourceManager — with every file handle it held — before building the new one. What is frozen is which packs are in the stack; a Resource still opens its file when it is read, so a folder pack edited on disk mid-reload is observable. A zip is not: FilePackResources holds its zip open for the life of the pack.

A Resource is what a lookup returns: its source pack, an IoSupplier for the bytes — opened lazily, at read time — and a lazily-read ResourceMetadata found beside it. A .mcmeta is looked for in the winning pack or those above it, never in one below, so a pack overriding a texture without its .mcmeta loses the animation. ResourceFilterSection lets a pack hide lower packs’ files by pattern without providing replacements: MultiPackResourceManager reads each pack’s filter section and pushes it onto the namespace stacks as a filter, and a lookup that reaches a filtered entry stops there. Some loaders want every copy, not the winner: ResourceManager.getResourceStack and ResourceManager.listResourceStacks return all packs’ copies bottom-first, which is how languages, tags and atlas sources merge instead of overriding.

One more manager frames this one. ReloadableResourceManager is the long-lived client façade that holds the current snapshot and the PreparableReloadListener list; the server has no façade — each reload is a fresh MultiPackResourceManager inside MinecraftServer.ReloadableResources.

What goes out is one ResourceManager, wrapped in a PreparableReloadListener.SharedState, and a ReloadInstance that has already started, which is a SimpleReloadInstance, or its subclass ProfiledReloadInstance when the logger is at debug.

Prepare: every listener at once

A PreparableReloadListener.reload takes the shared state (which carries the ResourceManager), a background executor, a PreparableReloadListener.PreparationBarrier and a main-thread executor. SimplePreparableReloadListener splits that into SimplePreparableReloadListener.prepare (background) and SimplePreparableReloadListener.apply (main thread); SimpleJsonResourceReloadListener is the “every JSON file in a directory through one codec” specialisation, using FileToIdConverter to map assets/ns/equipment/foo.json to ns:foo; ResourceManagerReloadListener is the apply-only shape.

SimpleReloadInstance is the schedule, and this is what it does, read from SimpleReloadInstance.prepareTasks. It wraps both executors in counters. It calls PreparableReloadListener.prepareSharedState on every listener first, synchronously. Then it walks the listener list once, calling each listener’s PreparableReloadListener.reload and handing it a barrier chained to the previous listener’s returned future (the first listener’s barrier is chained to the initial task). The barrier’s PreparableReloadListener.PreparationBarrier.wait does two things: it posts a task to the main-thread executor that removes the listener from the set still preparing and completes the all-preparations future when that set empties, and it returns that future combined with the previous listener’s. So listener N’s apply runs only after every listener has reached its barrier and listener N−1 has finished entirely — apply included — and a listener that never reaches its barrier holds every apply behind it. The futures are sequenced fail-fast: the first listener to throw fails the reload as a whole. It does not stop the others — Util.sequenceFailFast completes the outer future exceptionally and leaves every prepare running; Util.sequenceFailFastAndCancel, which would cancel them, is not the one used here. What never happens is the applies.

flowchart TD
    SS["PreparableReloadListener.prepareSharedState:<br/>one synchronous pass, before any prepare starts"]:::client
    subgraph PREP["prepare, all at once"]
        TMp["TextureManager: read every texture"]:::worker
        AMp["AtlasManager: stitch every atlas"]:::worker
        MMp["ModelManager: load models and block states"]:::worker
        ETC["nineteen more listeners"]:::worker
    end
    ALL["all preparations in: every listener has reached its barrier"]:::client
    subgraph APP["apply, in registration order"]
        TMa["TextureManager: swap texture contents"]:::client --> AMa["AtlasManager: upload the atlases"]:::client
        AMa --> MMa["ModelManager: install the baked models"]:::client
    end
    SS --> PREP
    PREP --> ALL
    ALL --> APP
    SS -. "the pending sprite futures" .-> MMp
    AMp -. "completes them" .-> MMp

Three of the client’s twenty-two listeners, preparing on the worker pool and applying on the thread that owns the state, with the two rules that order them drawn once each: nothing in the lower box starts until the gate opens, and inside it each apply waits on the one above. The dotted pair is the single exception — AtlasManager.PENDING_STITCH, published in the first pass and completed during prepare, so the models load while the atlases stitch instead of queueing behind them.

The listeners registered between the three drawn are elided. Every apply waits on the all-preparations node; each apply also waits on the apply before it; and the dotted pair is the only way one listener’s prepare depends on another’s.

The shared-state channel

PreparableReloadListener.prepareSharedState is a separate first pass for a reason: it is the one place a listener can publish something for another listener’s prepare to consume, keyed by a PreparableReloadListener.StateKey. The game declares exactly one — AtlasManager.PENDING_STITCH. AtlasManager publishes a future per atlas there before any prepare starts; ModelManager and ParticleResources pull the pending sprite futures out of it and wait on them inside their own prepare, so model loading overlaps atlas stitching rather than queueing behind it. This is why the model/atlas dependency is not an apply-order dependency, and why reasoning about it from the registration list gets the wrong answer. Which thread runs that first pass depends on who started the reload: the Render thread on the client, but a worker on the server, because the server’s reload instance is created from inside an already-async chain.

Prepare never touches live state. That is the whole contract, and it is one-directional: a listener that reads from the manager in SimplePreparableReloadListener.apply is reading the new snapshot and that is fine (LanguageManager does exactly this, in the apply-only shape: LanguageManager.onResourceManagerReload reads the language files on the Render thread), while one that mutates live state in SimplePreparableReloadListener.prepare is racing the Render thread. Nothing a listener owns is torn down when a reload starts; the client keeps rendering with the old atlases while the new ones bake.

Apply: registration order

Registration order is apply order. The client registers, in order, LanguageManager, TextureManager, ShaderManager, SoundManager, AtlasManager, FontManager, the three colour listeners (GrassColorReloadListener, FoliageColorReloadListener, DryFoliageColorReloadListener), ModelManager, EquipmentAssetManager, PalettedTextureManager, EntityRenderDispatcher, BlockEntityRenderDispatcher, ParticleResources, GameRenderer, LevelExtractor, the cloud renderer, GpuWarnlistManager, a PeriodicNotificationManager, then SplashManager from Gui and WaypointStyleManager from Hud — twenty-two in all. On the client ReloadableResourceManager.createReload is called with Util.backgroundExecutor (named resourceLoad) and Minecraft itself as the main-thread executor, so apply runs on the Render thread, interleaved with frames. On the server a /reload calls ReloadableServerResources.loadResources with Util.backgroundExecutor and MinecraftServer, so apply runs on the Server thread; the first load, before any server runs, is handed the thread that waits for it — ServerMain, draining Util.blockUntilDone’s queue, on a dedicated server, and the Render thread when singleplayer opens a world.

The counters SimpleReloadInstance wrapped the executors in are where the progress bar’s numbers come from: its ReloadInstance.getActualProgress weighs prepare and apply tasks double and listeners past their barrier single, and the overlay smooths it.

Finish, or roll back

On the client LoadingOverlay is a poll. It draws the logo from the vanilla pack outside the reload (via VanillaPackResources.asResourceManager) and a smoothed bar from ReloadInstance.getActualProgress; a manual reload fades it in over half a second and it will not fade out until a full second has passed. Each tick, once ReloadInstance.isDone, it calls ReloadInstance.checkExceptions and hands the result to its finish callback. Success runs LevelExtractor.allChanged, which is why every chunk section rebuilds after F3+T, then ResourceLoadStateTracker.finishReload, DownloadedPackSource.onReloadSuccess and Minecraft.onResourceLoadFinished. Failure runs Minecraft.rollbackResourcePacks, which does not find the offending pack — it deselects every resource pack it can, clears the options lists, saves, and reloads again, and if vanilla was the only selected pack it rethrows and crashes instead. That recovery reload bypasses the one-at-a-time guard, skips the fade, and if it fails the client abandons recovery: Minecraft.abortResourcePackRecovery drops the overlay, disconnects any level and returns to the title screen with a failure toast. ShaderManager is constructed with Minecraft.triggerResourcePackRecovery for exactly this, so a shader that fails at runtime rather than at load takes the same road. Throughout, ResourceLoadStateTracker records what kind of reload this was and with which packs, so a crash report can say.

On the server there is no overlay; the finish is a continuation on the server thread, and the coda below lists it.

F3+T, end to end

sequenceDiagram
    participant KH as KeyboardHandler
    participant MC as Minecraft
    participant RRM as Reloadable<br/>ResourceManager
    participant SRI as SimpleReloadInstance
    participant Worker as Worker
    participant LO as LoadingOverlay

    KH->>MC: reloadResourcePacks, on the F3+T key mapping
    MC->>MC: PackRepository.reload, then openAllSelected: keep the selection
    MC->>RRM: createReload: close the old manager, build the new snapshot
    RRM->>SRI: create: prepareSharedState on every listener, then reload on each
    SRI->>Worker: every listener's prepare, all at once
    MC->>LO: new, then Gui.setOverlay: the logo and a bar from getActualProgress
    Worker->>SRI: each listener reaches its<br/>Preparable<br/>ReloadListener.<br/>PreparationBarrier
    SRI->>MC: BlockableEventLoop.execute: each barrier posts a task, the last opens the gate
    MC->>MC: apply, one listener per registration slot, between frames
    Note over LO: a later tick
    LO->>SRI: isDone, then checkExceptions
    LO->>MC: rollbackResourcePacks on failure, allChanged on success

The keypress, end to end, with the worker pool drawn as one lane for a whole pool. The two arrows into Minecraft from below are the reload handing work back to the Render thread: the barriers’ posted tasks, which open the gate for the applies, and the overlay’s callback once it finds the instance done.

The key does nothing but ask. KeyboardHandler.handleDebugKeys matches Options.keyDebugReloadResourcePacks and calls Minecraft.reloadResourcePacks. If a reload is already showing an overlay, the request is parked in Minecraft.pendingReload and drained from Minecraft.runTick, and a second request while one is parked simply returns the same future. The one path that bypasses the guard is a recovery reload after a failure. The rest is the five stages above: PackRepository.reload and PackRepository.openAllSelected on the Render thread, ReloadableResourceManager.createReload closing the old snapshot and building the new one, SimpleReloadInstance fanning prepares out to the resourceLoad pool and marshalling applies back through Minecraft, and LoadingOverlay.tick polling for the end.

The first reload of the game’s life runs the same way from the Minecraft constructor, tagged ResourceLoadStateTracker.ReloadReason.INITIAL rather than manual; a world being opened builds its own first snapshot through WorldLoader.load, whose WorldLoader.PackConfig.createResourceManager runs MinecraftServer.configurePackRepository and opens the packs; and a server-sent pack is just one more RepositorySource, so DownloadedPackSource triggers an ordinary Minecraft.reloadResourcePacks.

/reload, the same pipeline on the server

F3+T (client)/reload (server)
who starts itKeyboardHandler.handleDebugKeys → Minecraft.reloadResourcePacks, parked in Minecraft.pendingReload if an overlay is already upReloadCommand, at Commands.LEVEL_GAMEMASTERS → MinecraftServer.reloadResources
discoveryPackRepository.reload keeps the current selection; required packs are force-insertedReloadCommand.discoverNewPacks runs PackRepository.reload and then selects every available pack not in the world’s disabled list — which is how a datapack dropped into the folder is picked up
where the packs are openedon the Render thread, before the overlay goes upon the server thread first, one Pack.open per selected id, before any background work starts
the managera façade swap: ReloadableResourceManager.createReload closes the old MultiPackResourceManager and holds the new onea fresh MultiPackResourceManager inside a new MinecraftServer.ReloadableResources; the old one is closed only when the new one is installed, and the new one is closed if the reload fails
which thread applies, and whether it blocksthe Render thread, between frames; nothing blocksthe Server thread; if /reload is issued from the server thread the method blocks it with BlockableEventLoop.managedBlock until done — /reload stalls the tick
how many listenerstwenty-two, in registration orderone — ServerFunctionLibrary (ReloadableServerResources.listeners)
what is a registry insteadnothing; the client’s registries arrive over the wiretags are read before the reload instance by TagLoader.loadTagsForExistingRegistries and applied after it (tags); loot tables, predicates, item modifiers, recipes, advancements and the rest of RegistryDataLoader.RELOADABLE_REGISTRIES load as the RegistryLayer.RELOADABLE layer in ReloadableServerRegistries.reload (identifiers and registries); item component prototypes rebind through BuiltInRegistries.DATA_COMPONENT_INITIALIZERS (data components)
when success is reportedat the keypress, in the debug chat line, before the reload has done anythingbefore the reload runs — the success message is sent first, and a failure arrives later
what happens on completionLevelExtractor.allChanged · ResourceLoadStateTracker.finishReload · DownloadedPackSource.onReloadSuccess · Minecraft.onResourceLoadFinishedclose the old MinecraftServer.ReloadableResources · install the new · PackRepository.setSelected · write the new WorldDataConfiguration into level data · ReloadableServerResources.updateComponentsAndStaticRegistryTags · RecipeManager.finalizeRecipeLoading · PlayerList.saveAll · PlayerList.reloadResources — which re-reads every player’s advancements, broadcasts ClientboundUpdateTagsPacket and ClientboundUpdateRecipesPacket, and re-sends every player’s whole recipe book · ServerFunctionManager.replaceLibrary · StructureTemplateManager.onResourceManagerReload
what happens on failureMinecraft.rollbackResourcePacksthe new manager is closed, the old resources stay installed, and the command source is told
timingProfiledReloadInstance only when the logger is at debugthe same

Two of those rows are worth a second look. The command tree is rebuilt by every /reload — a new Commands inside the new ReloadableServerResources — and nothing re-sends it, so what a connected client holds is the old tree’s shape. It is less than it sounds, because both server-side parses read the new dispatcher immediately (Brigadier and commands). And the reload is debug-timed only: the “Resource reload finished after N ms” line, the per-listener timings and the total-blocking-time figure all come from ProfiledReloadInstance, selected only when the logger is at debug.

Across the wire

A server pushes a pack with ClientboundResourcePackPushPacket (id, URL, hash, required, prompt) and withdraws one with ClientboundResourcePackPopPacket, sent by ServerResourcePackConfigurationTask in the configuration phase and, in a development build, by ServerPackCommand (/serverpack push|pop) at any time in play; the client answers with a ServerboundResourcePackPacket and its ServerboundResourcePackPacket.Action. Packs are keyed by UUID and stack, and a server-sent pack pins itself to the top of the selection. ServerCommonPacketListenerImpl.handleResourcePackResponse disconnects a client that declines — but the “required” it consults is MinecraftServer.isResourcePackRequired, a server-wide server.properties setting, not the flag on the individual pack, so a declined /serverpack push can disconnect you on a server whose properties pack is required.

What the client keeps of them is a cache with a hard cap. DownloadQueue gives every pack a directory of its own under a cache root, runs the downloads one at a time on a ConsecutiveExecutor over Util.nonCriticalIoPool, appends every attempt to a JsonEventLog beside them, and calls DownloadCacheCleaner.vacuumCacheDir at construction to trim the root to DownloadQueue.MAX_KEPT_PACKS. The cap counts files, not packs — twenty, newest first, and one file per directory before any directory’s second — so with one file per pack it is the twenty newest packs that survive on disk, and the twenty-first is downloaded again next time.

The system is data-driven by pack.mcmeta (PackMetadataSection, with min_format / max_format replacing the integer pack_format), options.txt (Options.resourcePacks), level.dat’s WorldDataConfiguration (DataPackConfig enabled/disabled plus the FeatureFlagSet), server.properties for the server-sent pack, and allowed_symlinks.txt via DirectoryValidator.

Questions players ask

Why does my pack’s animated texture stop animating when another pack overrides the image? Because the .mcmeta is looked for in the winning pack or those above it, never below. The override won the image and brought no metadata.

Why does a datapack I dropped into the folder appear after /reload but a resource pack I dropped in does not after F3+T? ReloadCommand.discoverNewPacks selects every newly available pack; PackRepository.reload on the client only re-discovers and keeps the selection you had.

Why did F3+T turn all my packs off? A listener threw. The rollback does not know which pack did it, so it clears them all and reloads with only the packs it cannot remove: vanilla, and any pack a server sent that was already in use.

Why does /reload freeze the server? When it is issued from the server thread, MinecraftServer.reloadResources blocks that thread with BlockableEventLoop.managedBlock until the reload is done.

Why can I disable the vanilla data pack but not the vanilla resource pack? ClientPackSource marks it required; ServerPacksSource does not.

For a 1.21-era reader. CompositePackResources is OverlayedPackResources. The vanilla pack is not a PackResources any more: VanillaPackResources hands out FixedPathPackResources layers, and its asProvider is VanillaPackResources.asResourceManager. RecipeManager and ServerAdvancementManager are not reload listeners: recipes and advancements are reloadable registries, and ServerFunctionLibrary is the server’s one listener.

Where to look

PackType · PackResources · Pack · PackRepository · PackCompatibility · PackFormat · ServerPacksSource · ClientPackSource · BuiltInPackSource · FolderRepositorySource · MultiPackResourceManager · FallbackResourceManager · ReloadableResourceManager · PreparableReloadListener · SimplePreparableReloadListener · SimpleJsonResourceReloadListener · SimpleReloadInstance · ResourceLoadStateTracker · Minecraft.reloadResourcePacks · LoadingOverlay · ReloadCommand · MinecraftServer.reloadResources · ReloadableServerResources · WorldLoader · DownloadedPackSource · ServerPackManager


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.