Site Runtime Implementation
The critical question for site runtime is not whether we have tick hooks. It is where each class of data belongs. The recommended model is three persistence layers plus one live-state layer.
Verified Persistence And Lifecycle API
| Topic | Verified API or event | Implementation conclusion |
|---|---|---|
| world ledger entry | ServerLevel.getDataStorage() | ledger lives on ServerLevel |
| ledger create/load | DimensionDataStorage.computeIfAbsent(Function<CompoundTag, T>, Supplier<T>, String) | suitable for SiteLedgerSavedData |
| dirty mark | SavedData.setDirty() | required after every ledger mutation |
| save checkpoint | LevelEvent.Save | valid point for ledger consistency checks |
| chunk NBT | ChunkDataEvent.Load / Save | use for chunk-side auxiliary data only |
| chunk load | ChunkEvent.Load | do not run heavy world interaction here |
| chunk unload | ChunkEvent.Unload | release local cache only |
| chunk capability | AttachCapabilitiesEvent<T>, IForgeLevelChunk extends ICapabilityProvider | alternate route for chunk-local state |
| player watching | ChunkWatchEvent.Watch / UnWatch | send auxiliary chunk data to specific players |
Four Implementation Layers
| Layer | Recommended implementation | Stores |
|---|---|---|
| world truth | SiteLedgerSavedData | ruin instances, anchors, lifecycle, covered chunks |
| live state | SiteRuntimeRegistry + ActiveSiteRuntime | current pressure, phase, temporary events, owner |
| chunk-side auxiliary layer | ChunkDataEvent or chunk capability | local presentation, cache, light indexes |
| sync layer | ChunkWatchEvent.Watch / UnWatch | minimal chunk payloads for the client |
SiteLedgerSavedData Skeleton
public final class SiteLedgerSavedData extends SavedData {
private final Map<String, DiscoveredSiteRecord> records = new HashMap<>();
public static SiteLedgerSavedData load(CompoundTag tag) {
SiteLedgerSavedData data = new SiteLedgerSavedData();
// parse records
return data;
}
@Override
public CompoundTag save(CompoundTag tag) {
// write records back
return tag;
}
public DiscoveredSiteRecord put(DiscoveredSiteRecord record) {
records.put(record.ref().toString(), record);
setDirty();
return record;
}
}The important part is not the exact field set:
- the world ledger uses
SavedData, - mutations call
setDirty()immediately, - real disk writes happen during world save.
LevelEvent.Save Responsibilities
LevelEvent.Save fires on the server side only. Good uses:
- ledger consistency assertions,
- runtime-to-ledger convergence checks,
- refreshing values that must be valid before the save boundary.
It should not become the per-tick runtime loop.
Two Paths For Chunk-Side Data
Path A: ChunkDataEvent.Load / ChunkDataEvent.Save
Suitable for:
- local ruin markers currently exposed by a chunk,
- precomputed visibility or presentation cache,
- lightweight derived values keyed by
coveredChunkKeys.
Notes:
ChunkDataEvent.Loadfires duringChunkSerializer.read(...)and has been verified as asynchronous.- Do not access heavy world logic there. Do not start runtime there. Do not query across chunks there.
Path B: AttachCapabilitiesEvent<LevelChunk>
Suitable for:
- local state objects tied to
LevelChunk, - cache objects that should release uniformly when the chunk invalidates.
This path is useful for richer local state, but it is still not world truth.
ChunkEvent.Load And ChunkEvent.Unload
| Event | Good use | Must not do |
|---|---|---|
ChunkEvent.Load | refill lightweight cache, prepare local sync | create runtime, perform heavy interaction, query across chunks |
ChunkEvent.Unload | release local cache, drop weak references | delete world-ledger records |
ChunkEvent.Load may fire before LevelChunk reaches ChunkStatus.FULL. It is not a site-runtime entry point.
Coverage Chunk Algorithm Recommendation
For each DiscoveredSiteRecord, store coveredChunkKeys in the ledger. Recommended initialization:
- use the ruin anchor as center,
- convert event radius into chunk radius,
- precompute the covered chunk set,
- let chunk events and watch events work only on that key set.
That gives us stable handling for:
- sync when a player enters visible range,
- local cache release when a chunk unloads,
- tracking one large ruin across multiple chunks.
ChunkWatchEvent.Watch / UnWatch
These events are best for "a player starts seeing this chunk, now send the extra ruin data."
| Event | Recommended use |
|---|---|
ChunkWatchEvent.Watch | send local ruin state or coverage markers for that chunk to the player |
ChunkWatchEvent.UnWatch | tear down client cache or subscription state for that player |
They solve sync, not persistence.
Recommended Object Boundaries
| Object | Responsibility |
|---|---|
SiteLedgerSavedData | world truth |
SiteRuntimeRegistry | currently active site events |
ActiveSiteRuntime | one ruin's dynamic runtime state |
ChunkSiteAuxData | one chunk's auxiliary cache |
SiteSyncPayload | minimum extra data sent to the client |
Implementation Errors To Avoid
- treating
ChunkDataEvent.Loadas a safe main-thread entry, - treating
ChunkEvent.Unloadas authority to delete ruin instances, - mutating
SavedDatawithout callingsetDirty().