> ## Documentation Index
> Fetch the complete documentation index at: https://docs.marioded.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Extend terrain storage

> Implement a terrain backend or a consistent world snapshot source at the runtime boundary.

# Extend terrain storage

**Advanced integration** · For runtime/backend implementers

Terrain integrations have two distinct responsibilities: **where snapshots are stored** and **how a consistent initial world snapshot is obtained**. Choose the correct contract instead of combining both in an application-specific recording service.

| Responsibility | Contract |
| - | - |
| Store and retrieve chunk payloads | `ChunkStorageProvider` |
| Recognize and snapshot a live world | `WorldSource` |
| Keep the world as it was at recording start | `SnapshotSession` |

> [!NOTE]
>
> **Only recording matches?** Use the [recording service](https://docs.marioded.com/api/recording.md). You do not need a custom terrain provider just to capture an arena or select participants.

## Storage provider

Implement `ChunkStorageProvider` for a terrain backend. It identifies worlds by a string and chunks by `ChunkKey`. Core operations include existence checks, asynchronous load/save, listing chunk keys, flush, finalize-world, and asynchronous close.

The provider must be thread-safe. A missing chunk is represented by a null payload from `getChunk`, not a fabricated all-air chunk that silently changes the scene. Propagate failures through futures so the recording can report degraded terrain instead of claiming a good save.

`ChunkPayload`/`ChunkSectionData` describe protocol-neutral chunk content, with `BlockEntityPayload` for block entities and `TerrainIndex` for chunk-hash mappings and vertical bounds. Do not persist a playback server's version-specific block-state IDs in those shared contracts.

## Ownership routing

`ownsWorld(identifier)` is a synchronous ownership claim. Claim only identifiers your provider can serve completely. Do not infer a backend from UUID syntax, a `template:` prefix, or a filename-shaped string.

The router selects a provider per world. The first provider is the write target for registration, chunk writes, finalization, and flush. Reads go to the first claimant or fall back to the write target. It does not merge missing individual chunks across several providers.

Declare a read-only provider with `isReadOnly()` where appropriate. Complete pending operations during close and define your own resource ownership clearly.

## Factory and source contribution

`ChunkStorageProviderFactory.create()` returns an optional provider, allowing a backend whose external platform is absent to contribute nothing. A provider can expose an optional `getWorldSource()` when its own platform also supplies live snapshots.

Implementing this interface alone does not install a backend into Reverse. Optional chunk factories are explicitly wired into the runtime's provider chain; there is no third-party service scan. The storage backend selected by `storage.backend` uses the distinct `StorageProviderFactory`/`StorageBackend` contracts and the built-in `StorageFactory` registry described in [catalog and storage](https://docs.marioded.com/api/catalog-and-storage.md#adding-another-backend). The public facades expose borrowed stores, not a universal provider-replacement method.

## World source

Implement `WorldSource` when you can take an isolated initial snapshot of a Bukkit world. `supports(world)` should claim only worlds genuinely served by that source. Sources are tried by specificity, with the universal Bukkit source last.

`beginSnapshot(world, bounds)` runs on the main thread and returns a caller-owned `SnapshotSession`. Null bounds retain the source's normal world selection. Nonnull bounds restrict horizontally intersecting chunk columns; payloads keep full height. Filter before expensive conversion, and do not load or generate Bukkit chunks as a side effect of capture.

## Snapshot session

The key promise is t0 consistency: `getChunk` returns the chunk as it existed when the session began, even if the world changes later. Returning the current mutable world chunk after a delay violates this contract.

Possible implementation strategies depend on the platform: eager immutable conversion, an isolated platform clone, or copy-on-write coordinated with pre-mutation events. Whatever strategy you use must obey bounds on eager and lazy paths. The current `SnapshotSession` contract exposes chunk reads and initial payloads only: it has no `close()` method and is not `AutoCloseable`. Do not write integration code that assumes stopping a recording invokes a snapshot-close hook; resource-owning source implementations need explicit lifecycle ownership in the runtime wiring.

`initialChunks()` can expose eagerly captured payloads. Async reads must not query unsafe Bukkit world state from arbitrary pool threads. Copy or snapshot on the required thread, then perform backend I/O asynchronously.

## Template behavior

An explicit nonempty template is selected at recorder start and bypasses live capture. World sources do not decide whether a user meant “template.” Template selection is session state; the replay persists the terrain identifier needed for playback.

Do not add a second manifest lineage format to communicate a template origin. If your integration needs catalog-level business metadata, design it at the catalog/application layer rather than overloading terrain-routing identifiers.

## Testing a provider

Verify missing worlds/chunks, two concurrent sessions on the same live world, mutation after t0, negative coordinates, bounded capture, backend failure during finalization, cleanup after failed startup, and playback on a machine without the original live world. A source that only works because the recorder machine's world folder still exists does not meet the portable-terrain use case.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.