<!-- https://minecraftdocs.dev/systems/foundations/resource-system — this page as markdown. The book's index: https://minecraftdocs.dev/llms.txt; the whole book in one file: https://minecraftdocs.dev/llms-full.txt -->

# 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](../anatomy/anatomy#four-threads-worth-memorising)) 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

| class | what it decides | thread |
|---|---|---|
| `PackRepository` · `Pack` | which packs exist, which are selected, and in what order | whoever asks: Render on the client, Server on the server |
| `MultiPackResourceManager` · `FallbackResourceManager` | which pack's copy of a file wins, one stack per namespace | built on the asking thread, read from workers |
| `ReloadableResourceManager` | the client's long-lived façade: the current snapshot and the listener list | Render |
| `PreparableReloadListener` · `SimplePreparableReloadListener` · `SimpleJsonResourceReloadListener` | what to read off-thread and what to swap on-thread | prepare on the worker pool, apply on the owner |
| `SimpleReloadInstance` | the schedule: every prepare at once, every apply in order behind a barrier | built on the caller's thread, barriers resolved on the owner |
| `LoadingOverlay` | when the client's reload is done, and what to do if it failed | Render |
| `ReloadableServerResources` | the server's one listener, and the registries that replaced the rest | Server |

## The pipeline

```mermaid
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 `RepositorySource`s (`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](identifiers-and-registries#feature-flags-the-same-registry-narrowed).

What goes out of the stage is `PackRepository.openAllSelected`: the
selected `Pack`s 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.

```mermaid
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

```mermaid
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 it | `KeyboardHandler.handleDebugKeys` → `Minecraft.reloadResourcePacks`, parked in `Minecraft.pendingReload` if an overlay is already up | `ReloadCommand`, at `Commands.LEVEL_GAMEMASTERS` → `MinecraftServer.reloadResources` |
| discovery | `PackRepository.reload` keeps the current selection; required packs are force-inserted | `ReloadCommand.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 opened | on the Render thread, before the overlay goes up | on the *server thread* first, one `Pack.open` per selected id, before any background work starts |
| the manager | a façade swap: `ReloadableResourceManager.createReload` closes the old `MultiPackResourceManager` and holds the new one | a 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 blocks | the Render thread, between frames; nothing blocks | the 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 listeners | twenty-two, in registration order | one — `ServerFunctionLibrary` (`ReloadableServerResources.listeners`) |
| what is a registry instead | nothing; the client's registries arrive over the wire | tags are read *before* the reload instance by `TagLoader.loadTagsForExistingRegistries` and applied after it ([tags](tags#the-four-moments-tags-are-loaded)); 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](identifiers-and-registries#when-a-world-opens)); item component prototypes rebind through `BuiltInRegistries.DATA_COMPONENT_INITIALIZERS` ([data components](data-components#the-prototype-and-why-it-is-built-at-reload)) |
| when success is reported | at the keypress, in the debug chat line, before the reload has done anything | **before** the reload runs — the success message is sent first, and a failure arrives later |
| what happens on completion | `LevelExtractor.allChanged` · `ResourceLoadStateTracker.finishReload` · `DownloadedPackSource.onReloadSuccess` · `Minecraft.onResourceLoadFinished` | close 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 failure | `Minecraft.rollbackResourcePacks` | the new manager is closed, the old resources stay installed, and the command source is told |
| timing | `ProfiledReloadInstance` only when the logger is at debug | the 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](../commands/brigadier-and-commands#the-tree-on-the-wire)). 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`.*
