Compat report, validation and commands

- Force-load every registrant and placement during pack validation and persist the findings with the Minecraft version in the validation cache
- Turn an excluded dimension into a blocking validation error and suppress content-key warnings the gate already reported
- Print the capped per-key listing after the validation lines on Bukkit and modded, ordered excluded, dropped, substituted
- Log one summary line per world engine from the published validation result
- Add /iris pack compat on Bukkit and modded with help and locale keys for every shipped language
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-03 04:54:38 -04:00
1 parent 0d464aabf3
commit 0b543bf7ee
33 files changed
+1018 -28

No files matched your search

@@ -25,6 +25,7 @@ import art.arcane.iris.core.pack.PackValidationRegistry;
import art.arcane.iris.core.pack.PackValidationResult;
import art.arcane.iris.core.pack.PackValidator;
import art.arcane.iris.modded.command.ModdedPackCommands;
import art.arcane.iris.spi.IrisPlatforms;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
@@ -41,6 +42,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
public final class ModdedStartup {
private static final int COMPAT_BOOT_KEY_CAP = 3;
private static final AtomicBoolean PREPARED = new AtomicBoolean(false);
private static final AtomicBoolean STARTED = new AtomicBoolean(false);
@@ -138,17 +140,23 @@ public final class ModdedStartup {
try {
PackValidationResult result = PackValidator.validate(packDir);
PackValidationRegistry.publish(result);
String minecraftVersion = IrisPlatforms.isBound() ? IrisPlatforms.get().minecraftVersion() : null;
String compatSummary = PackValidator.compatSummary(result, minecraftVersion);
String compatSuffix = compatSummary.isEmpty() ? "" : " " + compatSummary;
if (!result.isLoadable()) {
ModdedIrisLog.error("Iris pack '{}' FAILED validation with {} blocking error(s); world/studio creation will be refused. First error: {}",
result.getPackName(), result.getBlockingErrors().size(),
result.getBlockingErrors().getFirst());
} else if (!result.getWarnings().isEmpty()) {
ModdedIrisLog.info("Iris pack '{}' validated ({} warning(s)).", result.getPackName(), result.getWarnings().size());
ModdedIrisLog.info("Iris pack '{}' validated ({} warning(s)).{}", result.getPackName(), result.getWarnings().size(), compatSuffix);
for (String warning : result.getWarnings()) {
ModdedIrisLog.warn(" [{}] {}", result.getPackName(), warning);
}
} else {
ModdedIrisLog.info("Iris pack '{}' validated.", result.getPackName());
ModdedIrisLog.info("Iris pack '{}' validated.{}", result.getPackName(), compatSuffix);
}
for (String line : PackValidator.compatBootLines(result, minecraftVersion, COMPAT_BOOT_KEY_CAP)) {
ModdedIrisLog.warn("{}", line);
}
} catch (Throwable e) {
ModdedIrisLog.error("Iris pack validation failed for '{}'", packDir.getName(), e);
@@ -164,7 +164,8 @@ final class ModdedCommandHelp {
Entry.command("validate", "[pack]", ModdedHelpMessages.COMMAND_VALIDATE_VALIDATE_A_PACK_OR_EVERY_PACK, "v"),
Entry.command("cleanup", "<pack> [apply]", ModdedHelpMessages.COMMAND_CLEANUP_PREVIEW_OR_QUARANTINE_UNUSED_RESOURCE_CANDIDATES, "c"),
Entry.command("restore", "<pack> [apply]", ModdedHelpMessages.COMMAND_RESTORE_PREVIEW_OR_RESTORE_THE_LATEST_QUARANTINE, "r"),
Entry.command("status", "[pack]", ModdedHelpMessages.COMMAND_STATUS_SHOW_CACHED_VALIDATION_STATUS, "s")
Entry.command("status", "[pack]", ModdedHelpMessages.COMMAND_STATUS_SHOW_CACHED_VALIDATION_STATUS, "s"),
Entry.command("compat", "[pack]", ModdedHelpMessages.COMMAND_COMPAT_SHOW_CONTENT_THAT_CANNOT_GENERATE_ON_THIS_MINECRAFT_VERSION, "cp")
));
SECTIONS.put("pk", SECTIONS.get("pack"));
SECTIONS.put("world", List.of(
@@ -19,6 +19,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.compat.PackCompatReport;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.core.pack.PackResourceCleanup;
@@ -93,6 +94,15 @@ public final class ModdedPackCommands {
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> status(context.getSource(), StringArgumentType.getString(context, "pack")))));
root.then(Commands.literal("compat")
.executes((CommandContext<CommandSourceStack> context) -> compat(context.getSource(), null))
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> compat(context.getSource(), StringArgumentType.getString(context, "pack")))));
root.then(Commands.literal("cp")
.executes((CommandContext<CommandSourceStack> context) -> compat(context.getSource(), null))
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> compat(context.getSource(), StringArgumentType.getString(context, "pack")))));
return root;
}
@@ -215,6 +225,77 @@ public final class ModdedPackCommands {
return 1;
}
private static int compat(CommandSourceStack source, String pack) {
if (pack == null || pack.isBlank()) {
Map<String, PackValidationResult> snapshot = PackValidationRegistry.snapshot();
if (snapshot.isEmpty()) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_NO_VALIDATION_RESULTS_RECORDED_RUN_IRIS_PACK_VALIDATE_FIRST));
return 0;
}
for (Map.Entry<String, PackValidationResult> entry : snapshot.entrySet()) {
for (String line : compatOutput(entry.getKey(), entry.getValue())) {
IrisModdedCommands.ok(source, line);
}
}
return 1;
}
PackValidationResult result = PackValidationRegistry.get(pack);
for (String line : compatOutput(pack, result)) {
if (result == null) {
IrisModdedCommands.fail(source, line);
} else {
IrisModdedCommands.ok(source, line);
}
}
return result == null ? 0 : 1;
}
/**
* The compat listing for one pack, straight from the persisted validation so no pack is reloaded. Every key is
* printed with every subject; the boot listing is the capped form of the same data.
*/
static List<String> compatOutput(String pack, PackValidationResult result) {
List<String> lines = new ArrayList<>();
if (result == null) {
lines.add(IrisLanguage.plain(
ModdedCommandMessages.MODDED_PACK_COMMANDS_NO_VALIDATION_RESULT_RUN_IRIS_PACK_VALIDATE,
MessageArgument.untrusted("pack", pack),
MessageArgument.untrusted("pack2", pack)));
return lines;
}
String version = compatVersion(result);
if (result.getCompatFindings().isEmpty()) {
lines.add(IrisLanguage.plain(
ModdedCommandMessages.MODDED_PACK_COMMANDS_COMPAT_NONE,
MessageArgument.untrusted("pack", pack),
MessageArgument.untrusted("version", version)));
return lines;
}
lines.add(IrisLanguage.plain(
ModdedCommandMessages.MODDED_PACK_COMMANDS_COMPAT_HEADER,
MessageArgument.untrusted("pack", pack),
MessageArgument.untrusted("version", version)));
for (String line : PackCompatReport.of(result.getCompatFindings()).keyLines(0)) {
lines.add(IrisLanguage.plain(
ModdedCommandMessages.MODDED_PACK_COMMANDS_MESSAGE_2,
MessageArgument.untrusted("value", line)));
}
lines.add(IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_COMPAT_REMEDY));
return lines;
}
/** The live Minecraft version, falling back to the one the validation ran against. */
static String compatVersion(PackValidationResult result) {
if (IrisPlatforms.isBound()) {
String live = IrisPlatforms.get().minecraftVersion();
if (live != null && !live.isBlank()) {
return live;
}
}
String persisted = result.getMinecraftVersion();
return persisted == null || persisted.isBlank() ? "unknown" : persisted;
}
private static void report(CommandSourceStack source, PackValidationResult result) {
if (result.isLoadable()) {
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_PACK_IS_LOADABLE_WARNINGS, MessageArgument.untrusted("value", result.getPackName()), MessageArgument.untrusted("value2", result.getWarnings().size())));