diff --git a/src/main/java/com/tiedup/remake/client/gltf/GltfClientSetup.java b/src/main/java/com/tiedup/remake/client/gltf/GltfClientSetup.java
index 8868887..4fa21f6 100644
--- a/src/main/java/com/tiedup/remake/client/gltf/GltfClientSetup.java
+++ b/src/main/java/com/tiedup/remake/client/gltf/GltfClientSetup.java
@@ -118,6 +118,10 @@ public final class GltfClientSetup {
LOGGER.info(
"[GltfPipeline] Data-driven item reload listener registered"
);
+
+ // GLB structural validation (runs after item definitions are loaded)
+ event.registerReloadListener(new com.tiedup.remake.client.gltf.diagnostic.GlbValidationReloadListener());
+ LOGGER.info("[GltfPipeline] GLB validation reload listener registered");
}
}
diff --git a/src/main/java/com/tiedup/remake/client/gltf/diagnostic/GlbValidationReloadListener.java b/src/main/java/com/tiedup/remake/client/gltf/diagnostic/GlbValidationReloadListener.java
new file mode 100644
index 0000000..bf724b4
--- /dev/null
+++ b/src/main/java/com/tiedup/remake/client/gltf/diagnostic/GlbValidationReloadListener.java
@@ -0,0 +1,138 @@
+package com.tiedup.remake.client.gltf.diagnostic;
+
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
+import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.packs.resources.Resource;
+import net.minecraft.server.packs.resources.ResourceManager;
+import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
+import net.minecraft.util.profiling.ProfilerFiller;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Reload listener that validates all data-driven item GLB models on resource
+ * reload (F3+T or startup).
+ *
+ *
Must be registered AFTER {@link com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener}
+ * so that the item registry is populated before validation runs.
+ *
+ * On each reload:
+ *
+ * - Clears the {@link GlbDiagnosticRegistry}
+ * - Iterates all {@link DataDrivenItemDefinition}s
+ * - For each definition with a model location, attempts to open the GLB
+ * resource and runs {@link GlbValidator#validate} on it
+ * - Records results into the diagnostic registry and logs a summary
+ *
+ */
+@OnlyIn(Dist.CLIENT)
+public class GlbValidationReloadListener
+ extends SimplePreparableReloadListener
+{
+
+ private static final Logger LOGGER = LogManager.getLogger("GltfValidation");
+
+ @Override
+ protected Void prepare(
+ ResourceManager resourceManager,
+ ProfilerFiller profiler
+ ) {
+ return null;
+ }
+
+ @Override
+ protected void apply(
+ Void nothing,
+ ResourceManager resourceManager,
+ ProfilerFiller profiler
+ ) {
+ GlbDiagnosticRegistry.clear();
+
+ Collection definitions =
+ DataDrivenItemRegistry.getAll();
+
+ if (definitions.isEmpty()) {
+ LOGGER.warn(
+ "[GltfValidation] No data-driven item definitions found — skipping GLB validation"
+ );
+ return;
+ }
+
+ int passed = 0;
+ int withWarnings = 0;
+ int withErrors = 0;
+
+ for (DataDrivenItemDefinition def : definitions) {
+ ResourceLocation modelLoc = def.modelLocation();
+ if (modelLoc == null) {
+ continue;
+ }
+
+ Optional resourceOpt =
+ resourceManager.getResource(modelLoc);
+
+ if (resourceOpt.isEmpty()) {
+ // GLB file not found in any resource pack
+ GlbValidationResult missingResult = GlbValidationResult.of(
+ modelLoc,
+ List.of(new GlbDiagnostic(
+ modelLoc,
+ def.id(),
+ GlbDiagnostic.Severity.ERROR,
+ "MISSING_GLB",
+ "GLB file not found: " + modelLoc
+ + " (referenced by item " + def.id() + ")"
+ ))
+ );
+ GlbDiagnosticRegistry.addResult(missingResult);
+ withErrors++;
+ continue;
+ }
+
+ try (InputStream stream = resourceOpt.get().open()) {
+ GlbValidationResult result =
+ GlbValidator.validate(stream, modelLoc);
+ GlbDiagnosticRegistry.addResult(result);
+
+ if (!result.passed()) {
+ withErrors++;
+ } else if (hasWarnings(result)) {
+ withWarnings++;
+ } else {
+ passed++;
+ }
+ } catch (Exception e) {
+ GlbValidationResult errorResult = GlbValidationResult.of(
+ modelLoc,
+ List.of(new GlbDiagnostic(
+ modelLoc,
+ def.id(),
+ GlbDiagnostic.Severity.ERROR,
+ "GLB_READ_ERROR",
+ "Failed to read GLB file: " + e.getMessage()
+ ))
+ );
+ GlbDiagnosticRegistry.addResult(errorResult);
+ withErrors++;
+ }
+ }
+
+ int total = passed + withWarnings + withErrors;
+ LOGGER.info(
+ "[GltfValidation] Validated {} GLBs: {} passed, {} with warnings, {} with errors",
+ total, passed, withWarnings, withErrors
+ );
+ }
+
+ private static boolean hasWarnings(GlbValidationResult result) {
+ return result.diagnostics().stream()
+ .anyMatch(d -> d.severity() == GlbDiagnostic.Severity.WARNING);
+ }
+}