Improve generation and lifecycle safety

- Reduce terrain, noise, hydrology, and decorator sampling overhead.
- Reduce object transaction allocations while preserving rollback behavior.
- Optimize structure placement and cave anchor sampling.
- Parallelize Studio cache warmup and protect world entry and hotload recovery.
- Validate pack fingerprints and publish pack replacements atomically.
- Notify modded operators about outdated packs without synchronizing pack data.
- Harden world creation, loading, evacuation, and teleport failure recovery.
- Coalesce queued region flushes while preserving chunk save ordering.
- Remove obsolete mantle, pack, and cave planning code.
- Add regression tests and reusable gameplay fixtures.
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-06 20:51:28 -04:00
1 parent a2bcd7a302
commit 7f5ce70f49
197 files changed
+7560 -4420

No files matched your search

@@ -18,6 +18,8 @@
package art.arcane.iris.command;
import art.arcane.iris.pack.PackFingerprints;
import art.arcane.iris.Iris;
import art.arcane.iris.pack.validation.PackCompatReport;
import art.arcane.iris.pack.PackDirectoryResolver;
@@ -174,7 +176,6 @@ public class CommandPack implements DirectorExecutor {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NO_CLEANUP_CANDIDATES_FOUND_PACK, MessageArgument.untrusted("pack", String.valueOf(pack))));
return;
}
PackValidationRegistry.remove(packFolder.getName());
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_QUARANTINED_CLEANUP_CANDIDATE_S_UNDER, MessageArgument.untrusted("size", String.valueOf(result.quarantinedPaths().size())), MessageArgument.untrusted("quarantinePath", String.valueOf(result.quarantinePath()))));
reportPaths(s, result.quarantinedPaths(), BukkitRuntimeMessages.COMMAND_PACK_PATH_QUARANTINED);
return;
@@ -230,7 +231,6 @@ public class CommandPack implements DirectorExecutor {
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NOTHING_RESTORE_PACK, MessageArgument.untrusted("pack", String.valueOf(pack))));
return;
}
PackValidationRegistry.remove(packFolder.getName());
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_RESTORED_FILE_S_FROM, MessageArgument.untrusted("size", String.valueOf(result.restoredPaths().size())), MessageArgument.untrusted("dumpPath", String.valueOf(result.dumpPath()))));
reportPaths(s, result.restoredPaths(), BukkitRuntimeMessages.COMMAND_PACK_PATH_RESTORED);
return;
@@ -363,47 +363,79 @@ public class CommandPack implements DirectorExecutor {
return persisted == null || persisted.isBlank() ? "unknown" : persisted;
}
private PackValidationResult runValidate(VolmitSender s, File packFolder) {
PackValidationResult runValidate(VolmitSender s, File packFolder) {
PackValidationRegistry.ValidationTicket ticket =
PackValidationRegistry.tryBeginValidation(packFolder.toPath());
PackValidationResult result;
String contentFingerprint = "";
String contextFingerprint = "";
try {
PackValidationResult result = PackValidator.validate(packFolder);
PackValidationRegistry.publish(result);
reportResult(s, result);
return result;
if (ticket == null) {
throw new IllegalStateException("Pack content is currently being changed.");
}
contentFingerprint = PackFingerprints.computePackTreeFingerprint(packFolder);
contextFingerprint = PackValidationCache.contextFingerprint();
result = PackValidator.validate(packFolder);
if (!contentFingerprint.equals(PackFingerprints.computePackTreeFingerprint(packFolder))
|| !contextFingerprint.equals(PackValidationCache.contextFingerprint())) {
throw new IllegalStateException("Pack content or validation context changed during validation.");
}
} catch (Throwable e) {
Iris.reportError("Pack validation failed for '" + packFolder.getName() + "'", e);
String detail = e.getMessage() == null || e.getMessage().isBlank()
? e.getClass().getSimpleName()
: e.getMessage();
PackValidationResult result = new PackValidationResult(
result = new PackValidationResult(
packFolder.getName(),
List.of("Pack validation failed with " + e.getClass().getSimpleName() + ": " + detail),
List.of(),
System.currentTimeMillis());
PackValidationRegistry.publish(result);
reportResult(s, result);
return result;
contentFingerprint = "";
contextFingerprint = "";
}
if (PackValidationRegistry.publishIfCurrent(ticket, result, contentFingerprint, contextFingerprint)) {
PackValidationRegistry.publish(result);
} else {
result = new PackValidationResult(packFolder.getName(),
List.of("Pack content changed during validation."), List.of(), System.currentTimeMillis());
}
reportResult(s, result);
return result;
}
private void persistValidationCache(File packsRoot) {
List<File> packDirectories = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
List<PackValidationResult> results = new ArrayList<>(packDirectories.size());
for (File packDirectory : packDirectories) {
PackValidationResult result = PackValidationRegistry.get(packDirectory.getName());
if (result == null) {
// The boot cache only loads when it covers every pack, so a partial write is
// pointless - but say so instead of silently skipping the persist forever.
Iris.warn("Pack validation cache not written: \"" + packDirectory.getName()
+ "\" has no registered result. Run /iris pack validate all to repopulate it.");
try {
String contextFingerprint = PackValidationCache.contextFingerprint();
PackFingerprints.PackContentSnapshot snapshot = PackFingerprints.computePackContentSnapshot(packsRoot);
List<File> packDirectories = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
List<PackValidationResult> results = new ArrayList<>(packDirectories.size());
for (File packDirectory : packDirectories) {
PackValidationResult result = PackValidationRegistry.getMatching(
packDirectory.toPath(), snapshot.packContents().get(packDirectory.getName()));
if (result == null) {
Iris.warn("Pack validation cache not written: \"" + packDirectory.getName()
+ "\" has no validation matching its current content. Run /iris pack validate to refresh it.");
return;
}
results.add(result);
}
if (!snapshot.content().equals(PackFingerprints.computePackContentSnapshot(packsRoot).content())
|| !contextFingerprint.equals(PackValidationCache.contextFingerprint())) {
Iris.warn("Pack validation cache not written because pack content or validation context changed.");
return;
}
results.add(result);
}
try {
for (int index = 0; index < packDirectories.size(); index++) {
File packDirectory = packDirectories.get(index);
if (PackValidationRegistry.getMatching(packDirectory.toPath(),
snapshot.packContents().get(packDirectory.getName())) != results.get(index)) {
Iris.warn("Pack validation cache not written because validation changed during persistence.");
return;
}
}
PackValidationCache.save(
Iris.instance.getDataFile("cache", "pack-validation.json").toPath(),
PackValidationCache.contentFingerprint(packsRoot),
PackValidationCache.contextFingerprint(),
snapshot.content(),
contextFingerprint,
results);
} catch (IOException | RuntimeException e) {
Iris.reportError("Could not persist refreshed pack-validation results", e);
@@ -18,8 +18,9 @@
package art.arcane.iris.world;
import art.arcane.iris.pack.PackFingerprints;
import art.arcane.iris.configuration.IrisSettings;
import art.arcane.iris.pack.datapack.ServerConfigurator;
import art.arcane.iris.Iris;
import art.arcane.iris.world.lifecycle.WorldLifecycleStaging;
@@ -184,12 +185,12 @@ public final class IrisWorldGeneratorResolver {
}
List<String> packNames = packDirs.stream().map(File::getName).sorted().toList();
Path cacheFile = IrisPlatforms.get().dataFile("cache", "pack-validation.json").toPath();
ServerConfigurator.PackContentSnapshot contentSnapshot =
new ServerConfigurator.PackContentSnapshot("", Map.of());
PackFingerprints.PackContentSnapshot contentSnapshot =
new PackFingerprints.PackContentSnapshot("", Map.of());
String contextFingerprint = "";
Optional<List<PackValidationResult>> cached = Optional.empty();
try {
contentSnapshot = ServerConfigurator.computePackContentSnapshot(packsRoot);
contentSnapshot = PackFingerprints.computePackContentSnapshot(packsRoot);
contextFingerprint = PackValidationCache.contextFingerprint();
if (!externalContentChanged) {
cached = PackValidationCache.load(
@@ -274,19 +275,19 @@ public final class IrisWorldGeneratorResolver {
private static FreshValidation validateStablePacks(
File packsRoot,
List<File> initialPackDirs,
ServerConfigurator.PackContentSnapshot initialSnapshot
PackFingerprints.PackContentSnapshot initialSnapshot
) {
List<File> packDirs = initialPackDirs;
ServerConfigurator.PackContentSnapshot before = initialSnapshot;
PackFingerprints.PackContentSnapshot before = initialSnapshot;
for (int attempt = 0; attempt < VALIDATION_STABILITY_ATTEMPTS; attempt++) {
List<String> packNames = packDirs.stream().map(File::getName).sorted().toList();
List<PackValidationResult> results = validatePacks(packDirs);
ServerConfigurator.PackContentSnapshot after;
PackFingerprints.PackContentSnapshot after;
try {
after = ServerConfigurator.computePackContentSnapshot(packsRoot);
after = PackFingerprints.computePackContentSnapshot(packsRoot);
} catch (RuntimeException exception) {
Iris.reportError("Could not verify Iris pack bytes after validation", exception);
after = new ServerConfigurator.PackContentSnapshot("", Map.of());
after = new PackFingerprints.PackContentSnapshot("", Map.of());
}
List<File> afterPackDirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
List<String> afterPackNames = afterPackDirs.stream().map(File::getName).sorted().toList();
@@ -923,7 +924,7 @@ public final class IrisWorldGeneratorResolver {
private record FreshValidation(
List<File> packDirs,
ServerConfigurator.PackContentSnapshot contentSnapshot,
PackFingerprints.PackContentSnapshot contentSnapshot,
List<PackValidationResult> results,
boolean stable
) {