From 236902d814e0402f06c49f44b828fb595f32a963 Mon Sep 17 00:00:00 2001
From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com>
Date: Wed, 21 Aug 2024 08:05:05 -0700
Subject: [PATCH] Finish porting Model Loading, FRAPI, and Indigo (#4043)

---
 .../model/loading/v1/BlockStateResolver.java  |  15 --
 .../loading/v1/DelegatingUnbakedModel.java    |  17 +-
 .../model/loading/v1/ModelLoadingPlugin.java  |   2 +-
 .../model/loading/v1/ModelModifier.java       |  24 ---
 .../model/loading/v1/ModelResolver.java       |  14 --
 .../v1/PreparableModelLoadingPlugin.java      |   2 +-
 .../model/loading/ModelLoaderHooks.java       |  10 --
 .../loading/ModelLoadingEventDispatcher.java  | 168 ++++++------------
 .../ModelLoadingPluginContextImpl.java        |   8 +-
 .../loading/ModelLoadingPluginManager.java    |   9 +-
 .../model/loading/BakedModelManagerMixin.java |  79 ++++++--
 .../model/loading/BlockStatesLoaderMixin.java |  56 ------
 .../model/loading/ModelLoaderMixin.java       | 147 ++-------------
 .../model/loading/class_10097Mixin.java       |  95 ++++++++++
 .../fabric-model-loading-api-v1.mixins.json   |   6 +-
 .../loading/BakedModelFeatureRenderer.java    |  18 +-
 .../model/loading/ModelTestModClient.java     |  15 +-
 .../model/loading/NestedModelLoadingTest.java | 110 ------------
 .../testmodClient/resources/fabric.mod.json   |   1 -
 .../api/renderer/v1/mesh/MutableQuadView.java |  28 ++-
 .../api/renderer/v1/mesh/QuadEmitter.java     |  10 +-
 .../fabric/api/renderer/v1/mesh/QuadView.java |  19 +-
 .../impl/renderer/VanillaModelEncoder.java    |   2 +-
 .../fabric/test/renderer/FrameBlock.java      |  29 +--
 .../renderer/client/FrameUnbakedModel.java    |  13 --
 .../client/OctagonalColumnUnbakedModel.java   |  13 --
 .../renderer/client/PillarUnbakedModel.java   |  14 --
 .../client/RiverstoneUnbakedModel.java        |  17 +-
 .../indigo/renderer/IndigoRenderer.java       |   1 -
 .../indigo/renderer/aocalc/AoCalculator.java  |  39 +++-
 .../renderer/aocalc/VanillaAoHelper.java      |  13 +-
 .../renderer/mesh/MutableQuadViewImpl.java    |  11 +-
 .../renderer/render/BlockRenderInfo.java      |   4 +-
 .../renderer/render/ItemRenderContext.java    |   2 +-
 .../renderer/BlockModelRendererMixin.java     |   6 -
 .../indigo/renderer/ItemRendererMixin.java    |   8 +-
 settings.gradle                               |   6 +-
 37 files changed, 379 insertions(+), 652 deletions(-)
 delete mode 100644 fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BlockStatesLoaderMixin.java
 create mode 100644 fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/class_10097Mixin.java
 delete mode 100644 fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java

diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java
index 4f350e43d..4712101bf 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/BlockStateResolver.java
@@ -20,9 +20,7 @@ import org.jetbrains.annotations.ApiStatus;
 
 import net.minecraft.block.Block;
 import net.minecraft.block.BlockState;
-import net.minecraft.client.render.model.ModelLoader;
 import net.minecraft.client.render.model.UnbakedModel;
-import net.minecraft.util.Identifier;
 
 /**
  * Block state resolvers are responsible for mapping each {@link BlockState} of a block to an {@link UnbakedModel}.
@@ -69,18 +67,5 @@ public interface BlockStateResolver {
 		 * @param model the unbaked model for this block state
 		 */
 		void setModel(BlockState state, UnbakedModel model);
-
-		/**
-		 * Loads a model using an {@link Identifier}, or gets it if it was already loaded.
-		 *
-		 * @param id the model identifier
-		 * @return the unbaked model, or a missing model if it is not present
-		 */
-		UnbakedModel getOrLoadModel(Identifier id);
-
-		/**
-		 * The current model loader instance, which changes between resource reloads.
-		 */
-		ModelLoader loader();
 	}
 }
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java
index 900a28066..4e2c80937 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/DelegatingUnbakedModel.java
@@ -16,8 +16,6 @@
 
 package net.fabricmc.fabric.api.client.model.loading.v1;
 
-import java.util.Collection;
-import java.util.List;
 import java.util.function.Function;
 
 import org.jetbrains.annotations.Nullable;
@@ -37,7 +35,6 @@ import net.minecraft.util.Identifier;
  */
 public final class DelegatingUnbakedModel implements UnbakedModel {
 	private final Identifier delegate;
-	private final List<Identifier> dependencies;
 
 	/**
 	 * Constructs a new delegating model.
@@ -46,23 +43,11 @@ public final class DelegatingUnbakedModel implements UnbakedModel {
 	 */
 	public DelegatingUnbakedModel(Identifier delegate) {
 		this.delegate = delegate;
-		this.dependencies = List.of(delegate);
 	}
 
-	/*@Override
-	public Collection<Identifier> getModelDependencies() {
-		return dependencies;
-	}
-
-	@Override
-	public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
-		modelLoader.apply(delegate).setParents(modelLoader);
-	}*/
-
-	// TODO 24w44a
 	@Override
 	public void method_62326(class_10103 arg, class_10102 arg2) {
-
+		arg.method_62642(delegate);
 	}
 
 	@Override
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java
index 5d15c8428..ace79da59 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelLoadingPlugin.java
@@ -47,7 +47,7 @@ public interface ModelLoadingPlugin {
 	 * Called towards the beginning of the model loading process, every time resource are (re)loaded.
 	 * Use the context object to extend model loading as desired.
 	 */
-	void onInitializeModelLoader(Context pluginContext);
+	void initialize(Context pluginContext);
 
 	@ApiStatus.NonExtendable
 	interface Context {
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java
index 0d8596b6d..d3340dc7b 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelModifier.java
@@ -25,7 +25,6 @@ import org.jetbrains.annotations.UnknownNullability;
 import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.client.render.model.Baker;
 import net.minecraft.client.render.model.ModelBakeSettings;
-import net.minecraft.client.render.model.ModelLoader;
 import net.minecraft.client.render.model.UnbakedModel;
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.client.util.ModelIdentifier;
@@ -109,19 +108,6 @@ public final class ModelModifier {
 			 */
 			@UnknownNullability("#resourceId() != null")
 			ModelIdentifier topLevelId();
-
-			/**
-			 * Loads a model using an {@link Identifier}, or gets it if it was already loaded.
-			 *
-			 * @param id the model identifier
-			 * @return the unbaked model, or a missing model if it is not present
-			 */
-			UnbakedModel getOrLoadModel(Identifier id);
-
-			/**
-			 * The current model loader instance, which changes between resource reloads.
-			 */
-			ModelLoader loader();
 		}
 	}
 
@@ -177,11 +163,6 @@ public final class ModelModifier {
 			 * {@linkplain Baker#bake load baked models}.
 			 */
 			Baker baker();
-
-			/**
-			 * The current model loader instance, which changes between resource reloads.
-			 */
-			ModelLoader loader();
 		}
 	}
 
@@ -250,11 +231,6 @@ public final class ModelModifier {
 			 * {@linkplain Baker#bake load baked models}.
 			 */
 			Baker baker();
-
-			/**
-			 * The current model loader instance, which changes between resource reloads.
-			 */
-			ModelLoader loader();
 		}
 	}
 
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java
index 7f0843e2c..9ee85033f 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/ModelResolver.java
@@ -19,7 +19,6 @@ package net.fabricmc.fabric.api.client.model.loading.v1;
 import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.Nullable;
 
-import net.minecraft.client.render.model.ModelLoader;
 import net.minecraft.client.render.model.UnbakedModel;
 import net.minecraft.util.Identifier;
 
@@ -61,18 +60,5 @@ public interface ModelResolver {
 		 * The identifier of the model to be loaded.
 		 */
 		Identifier id();
-
-		/**
-		 * Loads a model using an {@link Identifier}, or gets it if it was already loaded.
-		 *
-		 * @param id the model identifier
-		 * @return the unbaked model, or a missing model if it is not present
-		 */
-		UnbakedModel getOrLoadModel(Identifier id);
-
-		/**
-		 * The current model loader instance, which changes between resource reloads.
-		 */
-		ModelLoader loader();
 	}
 }
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java
index b900cffc5..b0a3eb526 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/PreparableModelLoadingPlugin.java
@@ -48,7 +48,7 @@ public interface PreparableModelLoadingPlugin<T> {
 	 * @param data The data loaded by the {@link DataLoader}.
 	 * @param pluginContext The context that can be used to extend model loading.
 	 */
-	void onInitializeModelLoader(T data, ModelLoadingPlugin.Context pluginContext);
+	void initialize(T data, ModelLoadingPlugin.Context pluginContext);
 
 	@FunctionalInterface
 	interface DataLoader<T> {
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java
index 434988699..4a4a0b747 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoaderHooks.java
@@ -16,16 +16,6 @@
 
 package net.fabricmc.fabric.impl.client.model.loading;
 
-import net.minecraft.client.render.model.UnbakedModel;
-import net.minecraft.client.util.ModelIdentifier;
-import net.minecraft.util.Identifier;
-
 public interface ModelLoaderHooks {
 	ModelLoadingEventDispatcher fabric_getDispatcher();
-
-	UnbakedModel fabric_getMissingModel();
-
-	UnbakedModel fabric_getOrLoadModel(Identifier id);
-
-	void fabric_add(ModelIdentifier id, UnbakedModel model);
 }
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java
index 7d6a3dcc5..34daf56bf 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingEventDispatcher.java
@@ -16,8 +16,12 @@
 
 package net.fabricmc.fabric.impl.client.model.loading;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -35,13 +39,14 @@ import net.minecraft.block.BlockState;
 import net.minecraft.client.render.block.BlockModels;
 import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.client.render.model.Baker;
+import net.minecraft.client.render.model.BlockStatesLoader;
 import net.minecraft.client.render.model.ModelBakeSettings;
-import net.minecraft.client.render.model.ModelLoader;
 import net.minecraft.client.render.model.UnbakedModel;
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.client.util.ModelIdentifier;
 import net.minecraft.client.util.SpriteIdentifier;
-import net.minecraft.state.StateManager;
+import net.minecraft.registry.Registries;
+import net.minecraft.registry.RegistryKey;
 import net.minecraft.util.Identifier;
 
 import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver;
@@ -51,24 +56,23 @@ import net.fabricmc.fabric.api.client.model.loading.v1.ModelResolver;
 
 public class ModelLoadingEventDispatcher {
 	private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoadingEventDispatcher.class);
+	public static final ThreadLocal<ModelLoadingEventDispatcher> CURRENT = new ThreadLocal<>();
 
-	private final ModelLoader loader;
 	private final ModelLoadingPluginContextImpl pluginContext;
 
-	private final ObjectArrayList<ModelResolverContext> modelResolverContextStack = new ObjectArrayList<>();
+	private final ModelResolverContext modelResolverContext = new ModelResolverContext();
 	private final BlockStateResolverContext blockStateResolverContext = new BlockStateResolverContext();
 
-	private final ObjectArrayList<OnLoadModifierContext> onLoadModifierContextStack = new ObjectArrayList<>();
+	private final OnLoadModifierContext onLoadModifierContext = new OnLoadModifierContext();
 	private final ObjectArrayList<BeforeBakeModifierContext> beforeBakeModifierContextStack = new ObjectArrayList<>();
 	private final ObjectArrayList<AfterBakeModifierContext> afterBakeModifierContextStack = new ObjectArrayList<>();
 
-	public ModelLoadingEventDispatcher(ModelLoader loader, List<ModelLoadingPlugin> plugins) {
-		this.loader = loader;
+	public ModelLoadingEventDispatcher(List<ModelLoadingPlugin> plugins) {
 		this.pluginContext = new ModelLoadingPluginContextImpl();
 
 		for (ModelLoadingPlugin plugin : plugins) {
 			try {
-				plugin.onInitializeModelLoader(pluginContext);
+				plugin.initialize(pluginContext);
 			} catch (Exception exception) {
 				LOGGER.error("Failed to initialize model loading plugin", exception);
 			}
@@ -81,18 +85,30 @@ public class ModelLoadingEventDispatcher {
 		}
 	}
 
-	public boolean loadBlockStateModels(Identifier id, StateManager<Block, BlockState> stateManager) {
-		BlockStateResolver resolver = pluginContext.blockStateResolvers.get(id);
+	public BlockStatesLoader.class_10095 loadBlockStateModels() {
+		Map<ModelIdentifier, BlockStatesLoader.BlockModel> map = new HashMap<>();
 
-		if (resolver != null) {
-			resolveBlockStates(resolver, stateManager.getOwner(), id);
-			return true;
-		} else {
-			return false;
-		}
+		pluginContext.blockStateResolvers.forEach((block, resolver) -> {
+			Optional<RegistryKey<Block>> optionalKey = Registries.BLOCK.getKey(block);
+
+			if (optionalKey.isEmpty()) {
+				return;
+			}
+
+			Identifier blockId = optionalKey.get().getValue();
+
+			BiConsumer<BlockState, UnbakedModel> output = (state, model) -> {
+				ModelIdentifier modelId = BlockModels.getModelId(blockId, state);
+				map.put(modelId, new BlockStatesLoader.BlockModel(state, model));
+			};
+
+			resolveBlockStates(resolver, block, output);
+		});
+
+		return new BlockStatesLoader.class_10095(map);
 	}
 
-	private void resolveBlockStates(BlockStateResolver resolver, Block block, Identifier blockId) {
+	private void resolveBlockStates(BlockStateResolver resolver, Block block, BiConsumer<BlockState, UnbakedModel> output) {
 		BlockStateResolverContext context = blockStateResolverContext;
 		context.prepare(block);
 
@@ -100,7 +116,6 @@ public class ModelLoadingEventDispatcher {
 		ImmutableList<BlockState> allStates = block.getStateManager().getStates();
 		boolean thrown = false;
 
-		// Call resolver
 		try {
 			resolver.resolveBlockStates(context);
 		} catch (Exception e) {
@@ -108,34 +123,21 @@ public class ModelLoadingEventDispatcher {
 			thrown = true;
 		}
 
-		// Copy models over to the loader
-		if (thrown) {
-			UnbakedModel missingModel = ((ModelLoaderHooks) loader).fabric_getMissingModel();
+		if (!thrown) {
+			if (resolvedModels.size() == allStates.size()) {
+				// If there are as many resolved models as total states, all states have
+				// been resolved and models do not need to be null-checked.
+				resolvedModels.forEach(output);
+			} else {
+				for (BlockState state : allStates) {
+					@Nullable
+					UnbakedModel model = resolvedModels.get(state);
 
-			for (BlockState state : allStates) {
-				ModelIdentifier modelId = BlockModels.getModelId(blockId, state);
-				((ModelLoaderHooks) loader).fabric_add(modelId, missingModel);
-			}
-		} else if (resolvedModels.size() == allStates.size()) {
-			// If there are as many resolved models as total states, all states have
-			// been resolved and models do not need to be null-checked.
-			resolvedModels.forEach((state, model) -> {
-				ModelIdentifier modelId = BlockModels.getModelId(blockId, state);
-				((ModelLoaderHooks) loader).fabric_add(modelId, model);
-			});
-		} else {
-			UnbakedModel missingModel = ((ModelLoaderHooks) loader).fabric_getMissingModel();
-
-			for (BlockState state : allStates) {
-				ModelIdentifier modelId = BlockModels.getModelId(blockId, state);
-				@Nullable
-				UnbakedModel model = resolvedModels.get(state);
-
-				if (model == null) {
-					LOGGER.error("Block state resolver did not provide a model for state {} in block {}. Using missing model.", state, block);
-					((ModelLoaderHooks) loader).fabric_add(modelId, missingModel);
-				} else {
-					((ModelLoaderHooks) loader).fabric_add(modelId, model);
+					if (model == null) {
+						LOGGER.error("Block state resolver did not provide a model for state {} in block {}. Using missing model.", state, block);
+					} else {
+						output.accept(state, model);
+					}
 				}
 			}
 		}
@@ -145,31 +147,13 @@ public class ModelLoadingEventDispatcher {
 
 	@Nullable
 	public UnbakedModel resolveModel(Identifier id) {
-		if (modelResolverContextStack.isEmpty()) {
-			modelResolverContextStack.add(new ModelResolverContext());
-		}
-
-		ModelResolverContext context = modelResolverContextStack.pop();
-		context.prepare(id);
-
-		UnbakedModel model = pluginContext.resolveModel().invoker().resolveModel(context);
-
-		modelResolverContextStack.push(context);
-		return model;
+		modelResolverContext.prepare(id);
+		return pluginContext.resolveModel().invoker().resolveModel(modelResolverContext);
 	}
 
 	public UnbakedModel modifyModelOnLoad(UnbakedModel model, @UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId) {
-		if (onLoadModifierContextStack.isEmpty()) {
-			onLoadModifierContextStack.add(new OnLoadModifierContext());
-		}
-
-		OnLoadModifierContext context = onLoadModifierContextStack.pop();
-		context.prepare(resourceId, topLevelId);
-
-		model = pluginContext.modifyModelOnLoad().invoker().modifyModelOnLoad(model, context);
-
-		onLoadModifierContextStack.push(context);
-		return model;
+		onLoadModifierContext.prepare(resourceId, topLevelId);
+		return pluginContext.modifyModelOnLoad().invoker().modifyModelOnLoad(model, onLoadModifierContext);
 	}
 
 	public UnbakedModel modifyModelBeforeBake(UnbakedModel model, @UnknownNullability Identifier resourceId, @UnknownNullability ModelIdentifier topLevelId, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings settings, Baker baker) {
@@ -201,7 +185,7 @@ public class ModelLoadingEventDispatcher {
 		return model;
 	}
 
-	private class ModelResolverContext implements ModelResolver.Context {
+	private static class ModelResolverContext implements ModelResolver.Context {
 		private Identifier id;
 
 		private void prepare(Identifier id) {
@@ -212,19 +196,9 @@ public class ModelLoadingEventDispatcher {
 		public Identifier id() {
 			return id;
 		}
-
-		@Override
-		public UnbakedModel getOrLoadModel(Identifier id) {
-			return ((ModelLoaderHooks) loader).fabric_getOrLoadModel(id);
-		}
-
-		@Override
-		public ModelLoader loader() {
-			return loader;
-		}
 	}
 
-	private class BlockStateResolverContext implements BlockStateResolver.Context {
+	private static class BlockStateResolverContext implements BlockStateResolver.Context {
 		private Block block;
 		private final Reference2ReferenceMap<BlockState, UnbakedModel> models = new Reference2ReferenceOpenHashMap<>();
 
@@ -251,19 +225,9 @@ public class ModelLoadingEventDispatcher {
 				throw new IllegalStateException("Duplicate model for state " + state + " on block " + block);
 			}
 		}
-
-		@Override
-		public UnbakedModel getOrLoadModel(Identifier id) {
-			return ((ModelLoaderHooks) loader).fabric_getOrLoadModel(id);
-		}
-
-		@Override
-		public ModelLoader loader() {
-			return loader;
-		}
 	}
 
-	private class OnLoadModifierContext implements ModelModifier.OnLoad.Context {
+	private static class OnLoadModifierContext implements ModelModifier.OnLoad.Context {
 		@UnknownNullability
 		private Identifier resourceId;
 		@UnknownNullability
@@ -285,19 +249,9 @@ public class ModelLoadingEventDispatcher {
 		public ModelIdentifier topLevelId() {
 			return topLevelId;
 		}
-
-		@Override
-		public UnbakedModel getOrLoadModel(Identifier id) {
-			return ((ModelLoaderHooks) loader).fabric_getOrLoadModel(id);
-		}
-
-		@Override
-		public ModelLoader loader() {
-			return loader;
-		}
 	}
 
-	private class BeforeBakeModifierContext implements ModelModifier.BeforeBake.Context {
+	private static class BeforeBakeModifierContext implements ModelModifier.BeforeBake.Context {
 		@UnknownNullability
 		private Identifier resourceId;
 		@UnknownNullability
@@ -340,14 +294,9 @@ public class ModelLoadingEventDispatcher {
 		public Baker baker() {
 			return baker;
 		}
-
-		@Override
-		public ModelLoader loader() {
-			return loader;
-		}
 	}
 
-	private class AfterBakeModifierContext implements ModelModifier.AfterBake.Context {
+	private static class AfterBakeModifierContext implements ModelModifier.AfterBake.Context {
 		@UnknownNullability
 		private Identifier resourceId;
 		@UnknownNullability
@@ -397,10 +346,5 @@ public class ModelLoadingEventDispatcher {
 		public Baker baker() {
 			return baker;
 		}
-
-		@Override
-		public ModelLoader loader() {
-			return loader;
-		}
 	}
 }
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginContextImpl.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginContextImpl.java
index 3231be50b..bd7c7211b 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginContextImpl.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginContextImpl.java
@@ -17,7 +17,7 @@
 package net.fabricmc.fabric.impl.client.model.loading;
 
 import java.util.Collection;
-import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Objects;
@@ -44,7 +44,7 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
 	private static final Logger LOGGER = LoggerFactory.getLogger(ModelLoadingPluginContextImpl.class);
 
 	final Set<Identifier> extraModels = new LinkedHashSet<>();
-	final Map<Identifier, BlockStateResolver> blockStateResolvers = new HashMap<>();
+	final Map<Block, BlockStateResolver> blockStateResolvers = new IdentityHashMap<>();
 
 	private final Event<ModelResolver> modelResolvers = EventFactory.createArrayBacked(ModelResolver.class, resolvers -> context -> {
 		for (ModelResolver resolver : resolvers) {
@@ -124,9 +124,7 @@ public class ModelLoadingPluginContextImpl implements ModelLoadingPlugin.Context
 			throw new IllegalArgumentException("Received unregistered block");
 		}
 
-		Identifier blockId = optionalKey.get().getValue();
-
-		if (blockStateResolvers.put(blockId, resolver) != null) {
+		if (blockStateResolvers.put(block, resolver) != null) {
 			throw new IllegalArgumentException("Duplicate block state resolver for " + block);
 		}
 	}
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java
index 8ccbee026..21f43bd2a 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/ModelLoadingPluginManager.java
@@ -47,11 +47,6 @@ public final class ModelLoadingPluginManager {
 		PREPARABLE_PLUGINS.add(new PreparablePluginHolder<>(loader, plugin));
 	}
 
-	/**
-	 * The current exception behavior as of 1.20 is as follows.
-	 * If getting a {@link CompletableFuture}s throws then the whole client will crash.
-	 * If a {@link CompletableFuture} completes exceptionally then the resource reload will fail.
-	 */
 	public static CompletableFuture<List<ModelLoadingPlugin>> preparePlugins(ResourceManager resourceManager, Executor executor) {
 		List<CompletableFuture<ModelLoadingPlugin>> futures = new ArrayList<>();
 
@@ -63,12 +58,12 @@ public final class ModelLoadingPluginManager {
 			futures.add(preparePlugin(holder, resourceManager, executor));
 		}
 
-		return Util.combine(futures);
+		return Util.combineSafe(futures);
 	}
 
 	private static <T> CompletableFuture<ModelLoadingPlugin> preparePlugin(PreparablePluginHolder<T> holder, ResourceManager resourceManager, Executor executor) {
 		CompletableFuture<T> dataFuture = holder.loader.load(resourceManager, executor);
-		return dataFuture.thenApply(data -> pluginContext -> holder.plugin.onInitializeModelLoader(data, pluginContext));
+		return dataFuture.thenApply(data -> pluginContext -> holder.plugin.initialize(data, pluginContext));
 	}
 
 	private ModelLoadingPluginManager() { }
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java
index 24e1b33c8..2d91f0782 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java
@@ -16,17 +16,24 @@
 
 package net.fabricmc.fabric.mixin.client.model.loading;
 
-import java.util.List;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.Executor;
 import java.util.function.BiFunction;
+import java.util.function.Function;
 
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.ModifyArg;
 import org.spongepowered.asm.mixin.injection.Redirect;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 import net.minecraft.class_10097;
 import net.minecraft.client.render.model.BakedModel;
@@ -41,12 +48,15 @@ import net.minecraft.util.Pair;
 import net.minecraft.util.profiler.Profiler;
 
 import net.fabricmc.fabric.api.client.model.loading.v1.FabricBakedModelManager;
-import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
 import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingConstants;
+import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
 import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager;
 
 @Mixin(BakedModelManager.class)
 abstract class BakedModelManagerMixin implements FabricBakedModelManager {
+	@Unique
+	private volatile CompletableFuture<ModelLoadingEventDispatcher> eventDispatcherFuture;
+
 	@Shadow
 	private Map<ModelIdentifier, BakedModel> models;
 
@@ -55,33 +65,64 @@ abstract class BakedModelManagerMixin implements FabricBakedModelManager {
 		return models.get(ModelLoadingConstants.toResourceModelId(id));
 	}
 
+	@Inject(method = "reload", at = @At("HEAD"))
+	private void onHeadReload(ResourceReloader.Synchronizer synchronizer, ResourceManager manager, Profiler prepareProfiler, Profiler applyProfiler, Executor prepareExecutor, Executor applyExecutor, CallbackInfoReturnable<CompletableFuture<Void>> cir) {
+		eventDispatcherFuture = ModelLoadingPluginManager.preparePlugins(manager, prepareExecutor).thenApplyAsync(ModelLoadingEventDispatcher::new);
+	}
+
+	@ModifyReturnValue(method = "reload", at = @At("RETURN"))
+	private CompletableFuture<Void> resetEventDispatcherFuture(CompletableFuture<Void> future) {
+		return future.thenApplyAsync(v -> {
+			eventDispatcherFuture = null;
+			return v;
+		});
+	}
+
+	@ModifyExpressionValue(method = "reload", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/BakedModelManager.reloadBlockStates(Lnet/minecraft/client/render/model/BlockStatesLoader;Lnet/minecraft/resource/ResourceManager;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
+	private CompletableFuture<BlockStatesLoader.class_10095> hookBlockStateModelLoading(CompletableFuture<BlockStatesLoader.class_10095> modelsFuture) {
+		CompletableFuture<BlockStatesLoader.class_10095> resolvedModelsFuture = eventDispatcherFuture.thenApplyAsync(ModelLoadingEventDispatcher::loadBlockStateModels);
+		return modelsFuture.thenCombine(resolvedModelsFuture, (models, resolvedModels) -> {
+			Map<ModelIdentifier, BlockStatesLoader.BlockModel> map = models.models();
+
+			if (!(map instanceof HashMap)) {
+				map = new HashMap<>(map);
+				models = new BlockStatesLoader.class_10095(map);
+			}
+
+			map.putAll(resolvedModels.models());
+			return models;
+		});
+	}
+
 	@Redirect(
 			method = "reload",
 			at = @At(
 					value = "INVOKE",
 					target = "java/util/concurrent/CompletableFuture.thenCombineAsync(Ljava/util/concurrent/CompletionStage;Ljava/util/function/BiFunction;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;",
+					ordinal = 0,
 					remap = false
-			),
-			allow = 1)
-	private CompletableFuture<class_10097> loadModelPluginData(
+			))
+	private CompletableFuture<class_10097> hookModelDiscovery(
 			CompletableFuture<BlockStatesLoader.class_10095> self,
 			CompletionStage<Map<Identifier, UnbakedModel>> otherFuture,
 			BiFunction<BlockStatesLoader.class_10095, Map<Identifier, UnbakedModel>, class_10097> function,
-			Executor executor,
-			// reload args
-			ResourceReloader.Synchronizer synchronizer,
-			ResourceManager manager,
-			Profiler prepareProfiler,
-			Profiler applyProfiler,
-			Executor prepareExecutor,
-			Executor applyExecutor) {
-		CompletableFuture<List<ModelLoadingPlugin>> pluginsFuture = ModelLoadingPluginManager.preparePlugins(manager, prepareExecutor);
+			Executor executor) {
 		CompletableFuture<Pair<BlockStatesLoader.class_10095, Map<Identifier, UnbakedModel>>> pairFuture = self.thenCombine(otherFuture, Pair::new);
-		return pairFuture.thenCombineAsync(pluginsFuture, (pair, plugins) -> {
-			ModelLoadingPluginManager.CURRENT_PLUGINS.set(plugins);
-			class_10097 modelLoader = function.apply(pair.getLeft(), pair.getRight());
-			ModelLoadingPluginManager.CURRENT_PLUGINS.remove();
-			return modelLoader;
+		return pairFuture.thenCombineAsync(eventDispatcherFuture, (pair, eventDispatcher) -> {
+			ModelLoadingEventDispatcher.CURRENT.set(eventDispatcher);
+			class_10097 class_10097 = function.apply(pair.getLeft(), pair.getRight());
+			ModelLoadingEventDispatcher.CURRENT.remove();
+			return class_10097;
 		}, executor);
 	}
+
+	@ModifyArg(method = "reload", at = @At(value = "INVOKE", target = "java/util/concurrent/CompletableFuture.thenApplyAsync (Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 1), index = 0)
+	private Function<Void, Object> hookModelBaking(Function<Void, Object> function) {
+		return v -> {
+			ModelLoadingEventDispatcher.CURRENT.set(eventDispatcherFuture.join());
+			Object bakingResult = function.apply(v);
+			ModelLoadingEventDispatcher.CURRENT.remove();
+			return bakingResult;
+		};
+	}
 }
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BlockStatesLoaderMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BlockStatesLoaderMixin.java
deleted file mode 100644
index fe9290f29..000000000
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BlockStatesLoaderMixin.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.fabricmc.fabric.mixin.client.model.loading;
-
-import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-import net.minecraft.block.Block;
-import net.minecraft.block.BlockState;
-import net.minecraft.client.render.model.BlockStatesLoader;
-import net.minecraft.state.StateManager;
-import net.minecraft.util.Identifier;
-
-import net.fabricmc.fabric.impl.client.model.loading.BlockStatesLoaderHooks;
-
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-
-import java.util.List;
-
-@Mixin(BlockStatesLoader.class)
-abstract class BlockStatesLoaderMixin implements BlockStatesLoaderHooks {
-	@Unique
-	@Nullable
-	private LoadingOverride loadingOverride;
-
-	// TODO 24w33a - Mappings
-	@Inject(method = "method_62627", at = @At("HEAD"), cancellable = true)
-	private void onHeadLoadBlockStates(Identifier id, StateManager<Block, BlockState> stateManager, List<BlockStatesLoader.class_10094> list, CallbackInfoReturnable<BlockStatesLoader.class_10095> cir) {
-		if (loadingOverride != null && loadingOverride.loadBlockStates(id, stateManager)) {
-			cir.cancel();
-		}
-	}
-
-	@Override
-	public void fabric_setLoadingOverride(LoadingOverride override) {
-		loadingOverride = override;
-	}
-}
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java
index aee4dbff5..22d7f84f9 100644
--- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/ModelLoaderMixin.java
@@ -16,162 +16,54 @@
 
 package net.fabricmc.fabric.mixin.client.model.loading;
 
-import java.util.Map;
-import java.util.Set;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 
 import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
 import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
-import com.llamalad7.mixinextras.sugar.Local;
-import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
-import org.spongepowered.asm.mixin.Final;
+import org.jetbrains.annotations.Nullable;
 import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Coerce;
 import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.ModifyVariable;
-import org.spongepowered.asm.mixin.injection.Redirect;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
-import net.minecraft.client.color.block.BlockColors;
+import net.minecraft.class_10096;
 import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.client.render.model.Baker;
-import net.minecraft.client.render.model.BlockStatesLoader;
 import net.minecraft.client.render.model.ModelBakeSettings;
 import net.minecraft.client.render.model.ModelLoader;
 import net.minecraft.client.render.model.UnbakedModel;
-import net.minecraft.client.render.model.json.JsonUnbakedModel;
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.client.util.ModelIdentifier;
 import net.minecraft.client.util.SpriteIdentifier;
-import net.minecraft.util.Identifier;
-import net.minecraft.util.profiler.Profiler;
 
 import net.fabricmc.fabric.impl.client.model.loading.BakerImplHooks;
-import net.fabricmc.fabric.impl.client.model.loading.BlockStatesLoaderHooks;
 import net.fabricmc.fabric.impl.client.model.loading.ModelLoaderHooks;
 import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingConstants;
 import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
-import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager;
 
 @Mixin(ModelLoader.class)
 abstract class ModelLoaderMixin implements ModelLoaderHooks {
-	@Final
-	@Shadow
-	private Set<Identifier> modelsToLoad;
-	@Final
-	@Shadow
-	private Map<Identifier, UnbakedModel> unbakedModels;
-	@Shadow
-	@Final
-	private Map<ModelIdentifier, UnbakedModel> modelsToBake;
-	@Shadow
-	@Final
-	private UnbakedModel missingModel;
-
 	@Unique
+	@Nullable
 	private ModelLoadingEventDispatcher fabric_eventDispatcher;
-	@Unique
-	private final ObjectLinkedOpenHashSet<Identifier> modelLoadingStack = new ObjectLinkedOpenHashSet<>();
 
-	@Shadow
-	abstract UnbakedModel getOrLoadModel(Identifier id);
-
-	@Shadow
-	abstract void add(ModelIdentifier id, UnbakedModel unbakedModel);
-
-	@Shadow
-	abstract JsonUnbakedModel loadModelFromJson(Identifier id);
-
-	@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/BlockStatesLoader;load()V"))
-	private void afterMissingModelInit(BlockColors blockColors, Profiler profiler, Map<Identifier, JsonUnbakedModel> jsonUnbakedModels, Map<Identifier, List<BlockStatesLoader.SourceTrackedData>> blockStates, CallbackInfo info, @Local BlockStatesLoader blockStatesLoader) {
-		// Sanity check
-		if (missingModel == null || !modelsToBake.containsKey(ModelLoader.MISSING_MODEL_ID)) {
-			throw new AssertionError("Missing model not initialized. This is likely a Fabric API porting bug.");
-		}
-
-		// Add the missing model to the cache since vanilla doesn't. Mods may load/bake the missing model directly.
-		unbakedModels.put(ModelLoader.MISSING_ID, missingModel);
-		profiler.swap("fabric_plugins_init");
-
-		fabric_eventDispatcher = new ModelLoadingEventDispatcher((ModelLoader) (Object) this, ModelLoadingPluginManager.CURRENT_PLUGINS.get());
-		fabric_eventDispatcher.addExtraModels(this::addExtraModel);
-		((BlockStatesLoaderHooks) blockStatesLoader).fabric_setLoadingOverride(fabric_eventDispatcher::loadBlockStateModels);
-	}
-
-	@Unique
-	private void addExtraModel(Identifier id) {
-		ModelIdentifier modelId = ModelLoadingConstants.toResourceModelId(id);
-		UnbakedModel unbakedModel = getOrLoadModel(id);
-		add(modelId, unbakedModel);
-	}
-
-	@Inject(method = "getOrLoadModel", at = @At("HEAD"), cancellable = true)
-	private void allowRecursiveLoading(Identifier id, CallbackInfoReturnable<UnbakedModel> cir) {
-		// If the stack is empty, this is the top-level call, so proceed as normal.
-		if (!modelLoadingStack.isEmpty()) {
-			if (unbakedModels.containsKey(id)) {
-				cir.setReturnValue(unbakedModels.get(id));
-			} else if (modelLoadingStack.contains(id)) {
-				throw new IllegalStateException("Circular reference while loading model '" + id + "' (" + modelLoadingStack.stream().map(i -> i + "->").collect(Collectors.joining()) + id + ")");
-			} else {
-				UnbakedModel model = loadModel(id);
-				unbakedModels.put(id, model);
-				// These will be loaded at the top-level call.
-				modelsToLoad.addAll(model.getModelDependencies());
-				cir.setReturnValue(model);
-			}
-		}
-	}
-
-	// This is the call that needs to be redirected to support ModelResolvers, but it returns a JsonUnbakedModel.
-	// Redirect it to always return null and handle the logic in a ModifyVariable right after the call.
-	@Redirect(method = "getOrLoadModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/ModelLoader;loadModelFromJson(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/json/JsonUnbakedModel;"))
-	private JsonUnbakedModel cancelLoadModelFromJson(ModelLoader self, Identifier id) {
-		return null;
-	}
-
-	@ModifyVariable(method = "getOrLoadModel", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/client/render/model/ModelLoader;loadModelFromJson(Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/model/json/JsonUnbakedModel;"))
-	private UnbakedModel doLoadModel(UnbakedModel model, @Local(ordinal = 1) Identifier id) {
-		return loadModel(id);
-	}
-
-	@Unique
-	private UnbakedModel loadModel(Identifier id) {
-		modelLoadingStack.add(id);
-
-		try {
-			UnbakedModel model = fabric_eventDispatcher.resolveModel(id);
-
-			if (model == null) {
-				model = loadModelFromJson(id);
-			}
-
-			return fabric_eventDispatcher.modifyModelOnLoad(model, id, null);
-		} finally {
-			modelLoadingStack.removeLast();
-		}
-	}
-
-	@ModifyVariable(method = "add", at = @At("HEAD"), argsOnly = true)
-	private UnbakedModel onAdd(UnbakedModel model, ModelIdentifier id) {
-		if (ModelLoadingConstants.isResourceModelId(id)) {
-			return model;
-		}
-
-		return fabric_eventDispatcher.modifyModelOnLoad(model, null, id);
+	@Inject(method = "<init>", at = @At("RETURN"))
+	private void onReturnInit(CallbackInfo ci) {
+		fabric_eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
 	}
 
 	@WrapOperation(method = "method_61072(Lnet/minecraft/client/render/model/ModelLoader$SpriteGetter;Lnet/minecraft/client/util/ModelIdentifier;Lnet/minecraft/client/render/model/UnbakedModel;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/ModelLoader$BakerImpl;bake(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;"))
 	private BakedModel wrapSingleOuterBake(@Coerce Baker baker, UnbakedModel unbakedModel, ModelBakeSettings settings, Operation<BakedModel> operation, ModelLoader.SpriteGetter spriteGetter, ModelIdentifier id) {
-		if (ModelLoadingConstants.isResourceModelId(id) || id.equals(ModelLoader.MISSING_MODEL_ID)) {
+		if (fabric_eventDispatcher == null) {
+			return operation.call(baker, unbakedModel, settings);
+		}
+
+		if (ModelLoadingConstants.isResourceModelId(id) || id.equals(class_10096.field_53661)) {
 			// Call the baker instead of the operation to ensure the baked model is cached and doesn't end up going
 			// through events twice.
-			// This ignores the UnbakedModel in modelsToBake but it should be the same as the one in unbakedModels.
+			// This ignores the UnbakedModel in field_53662 (top-level model map) but it should be the same as the one in field_53663 (resource model map).
 			return baker.bake(id.id(), settings);
 		}
 
@@ -185,19 +77,4 @@ abstract class ModelLoaderMixin implements ModelLoaderHooks {
 	public ModelLoadingEventDispatcher fabric_getDispatcher() {
 		return fabric_eventDispatcher;
 	}
-
-	@Override
-	public UnbakedModel fabric_getMissingModel() {
-		return missingModel;
-	}
-
-	@Override
-	public UnbakedModel fabric_getOrLoadModel(Identifier id) {
-		return getOrLoadModel(id);
-	}
-
-	@Override
-	public void fabric_add(ModelIdentifier id, UnbakedModel model) {
-		add(id, model);
-	}
 }
diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/class_10097Mixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/class_10097Mixin.java
new file mode 100644
index 000000000..6bbf6e5d3
--- /dev/null
+++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/class_10097Mixin.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.client.model.loading;
+
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import net.minecraft.class_10097;
+import net.minecraft.client.render.model.BlockStatesLoader;
+import net.minecraft.client.render.model.UnbakedModel;
+import net.minecraft.client.util.ModelIdentifier;
+import net.minecraft.util.Identifier;
+
+import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingConstants;
+import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher;
+
+@Mixin(class_10097.class)
+abstract class class_10097Mixin {
+	@Unique
+	@Nullable
+	private ModelLoadingEventDispatcher fabric_eventDispatcher;
+
+	@Shadow
+	abstract UnbakedModel method_62638(Identifier identifier);
+
+	@Shadow
+	abstract void method_62635(ModelIdentifier modelIdentifier, UnbakedModel unbakedModel);
+
+	@Inject(method = "<init>", at = @At("RETURN"))
+	private void onReturnInit(CallbackInfo ci) {
+		fabric_eventDispatcher = ModelLoadingEventDispatcher.CURRENT.get();
+	}
+
+	@Inject(method = "method_62632", at = @At("RETURN"))
+	private void onAddStandardModels(BlockStatesLoader.class_10095 blockStateModels, CallbackInfo ci) {
+		if (fabric_eventDispatcher == null) {
+			return;
+		}
+
+		fabric_eventDispatcher.addExtraModels(id -> {
+			ModelIdentifier modelId = ModelLoadingConstants.toResourceModelId(id);
+			UnbakedModel unbakedModel = method_62638(id);
+			method_62635(modelId, unbakedModel);
+		});
+	}
+
+	@ModifyVariable(method = "method_62640", at = @At(value = "STORE", ordinal = 0), ordinal = 0)
+	@Nullable
+	private UnbakedModel onLoadResourceModel(@Nullable UnbakedModel model, Identifier id) {
+		if (fabric_eventDispatcher == null) {
+			return model;
+		}
+
+		UnbakedModel resolvedModel = fabric_eventDispatcher.resolveModel(id);
+
+		if (resolvedModel != null) {
+			model = resolvedModel;
+		}
+
+		return fabric_eventDispatcher.modifyModelOnLoad(model, id, null);
+	}
+
+	@ModifyVariable(method = "method_62635", at = @At("HEAD"), argsOnly = true)
+	private UnbakedModel onAddTopLevelModel(UnbakedModel model, ModelIdentifier modelId) {
+		if (fabric_eventDispatcher == null) {
+			return model;
+		}
+
+		if (ModelLoadingConstants.isResourceModelId(modelId)) {
+			return model;
+		}
+
+		return fabric_eventDispatcher.modifyModelOnLoad(model, null, modelId);
+	}
+}
diff --git a/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json b/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json
index 8da1ac215..047df397b 100644
--- a/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json
+++ b/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json
@@ -4,9 +4,9 @@
   "compatibilityLevel": "JAVA_21",
   "client": [
     "BakedModelManagerMixin",
-    "BlockStatesLoaderMixin",
-    "ModelLoaderMixin",
-    "ModelLoaderBakerImplMixin"
+    "class_10097Mixin",
+    "ModelLoaderBakerImplMixin",
+    "ModelLoaderMixin"
   ],
   "injectors": {
     "defaultRequire": 1
diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java
index 5da7b30d2..aaa3e5d7b 100644
--- a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java
+++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/BakedModelFeatureRenderer.java
@@ -18,8 +18,6 @@ package net.fabricmc.fabric.test.model.loading;
 
 import java.util.function.Supplier;
 
-import net.minecraft.client.render.entity.state.EntityRenderState;
-
 import org.joml.AxisAngle4f;
 import org.joml.Quaternionf;
 
@@ -31,29 +29,27 @@ import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.entity.feature.FeatureRenderer;
 import net.minecraft.client.render.entity.feature.FeatureRendererContext;
 import net.minecraft.client.render.entity.model.EntityModel;
+import net.minecraft.client.render.entity.state.LivingEntityRenderState;
 import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.entity.LivingEntity;
+import net.minecraft.util.math.MathHelper;
 
-public class BakedModelFeatureRenderer<T extends EntityRenderState, M extends EntityModel<T>> extends FeatureRenderer<T, M> {
+public class BakedModelFeatureRenderer<S extends LivingEntityRenderState, M extends EntityModel<S>> extends FeatureRenderer<S, M> {
 	private final Supplier<BakedModel> modelSupplier;
 
-	public BakedModelFeatureRenderer(FeatureRendererContext<T, M> context, Supplier<BakedModel> modelSupplier) {
+	public BakedModelFeatureRenderer(FeatureRendererContext<S, M> context, Supplier<BakedModel> modelSupplier) {
 		super(context);
 		this.modelSupplier = modelSupplier;
 	}
 
 	@Override
-	public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T state, float limbAngle, float limbDistance) {
+	public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, S state, float limbAngle, float limbDistance) {
 		BakedModel model = modelSupplier.get();
 		VertexConsumer vertices = vertexConsumers.getBuffer(TexturedRenderLayers.getEntityCutout());
 		matrices.push();
-		//matrices.multiply(Vector3f.POSITIVE_Y.getDegreesQuaternion(headYaw));
-		//matrices.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(headPitch));
-		// TODO 24w33a Is limbAngle the same as animationProgress? Probably not
-		matrices.multiply(new Quaternionf(new AxisAngle4f(limbAngle * 0.07F, 0, 1, 0)));
+		matrices.multiply(new Quaternionf(new AxisAngle4f(state.age * 0.07F - state.bodyYaw * MathHelper.RADIANS_PER_DEGREE, 0, 1, 0)));
 		matrices.scale(-0.75F, -0.75F, 0.75F);
-		float aboveHead = (float) (Math.sin(limbAngle * 0.08F)) * 0.5F + 0.5F;
+		float aboveHead = (float) (Math.sin(state.age * 0.08F)) * 0.5F + 0.5F;
 		matrices.translate(-0.5F, 0.75F + aboveHead, -0.5F);
 		MinecraftClient.getInstance().getBlockRenderManager().getModelRenderer().render(matrices.peek(), vertices, null, model, 1, 1, 1, light, OverlayTexture.DEFAULT_UV);
 		matrices.pop();
diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java
index dac0381a8..d088f5d9c 100644
--- a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java
+++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/ModelTestModClient.java
@@ -26,7 +26,6 @@ import net.minecraft.class_10096;
 import net.minecraft.client.render.block.BlockModels;
 import net.minecraft.client.render.entity.PlayerEntityRenderer;
 import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.render.model.ModelLoader;
 import net.minecraft.client.render.model.UnbakedModel;
 import net.minecraft.client.util.ModelIdentifier;
 import net.minecraft.resource.ResourceType;
@@ -69,6 +68,7 @@ public class ModelTestModClient implements ClientModInitializer {
 	public void onInitializeClient() {
 		ModelLoadingPlugin.register(pluginContext -> {
 			pluginContext.addModels(HALF_RED_SAND_MODEL_ID);
+
 			// remove bottom face of gold blocks
 			pluginContext.modifyModelAfterBake().register(ModelModifier.WRAP_PHASE, (model, context) -> {
 				Identifier id = context.resourceId();
@@ -79,17 +79,19 @@ public class ModelTestModClient implements ClientModInitializer {
 
 				return model;
 			});
+
 			// make fences with west: true and everything else false appear to be a missing model visually
 			ModelIdentifier fenceId = BlockModels.getModelId(Blocks.OAK_FENCE.getDefaultState().with(HorizontalConnectingBlock.WEST, true));
 			pluginContext.modifyModelOnLoad().register(ModelModifier.OVERRIDE_PHASE, (model, context) -> {
 				ModelIdentifier id = context.topLevelId();
 
 				if (id != null && id.equals(fenceId)) {
-					return context.getOrLoadModel(class_10096.field_53660);
+					return new DelegatingUnbakedModel(class_10096.field_53660);
 				}
 
 				return model;
 			});
+
 			// make brown glazed terracotta appear to be a missing model visually, but without affecting the item, by using pre-bake
 			// using load here would make the item also appear missing
 			pluginContext.modifyModelBeforeBake().register(ModelModifier.OVERRIDE_PHASE, (model, context) -> {
@@ -109,14 +111,15 @@ public class ModelTestModClient implements ClientModInitializer {
 				// All the block state models are top-level...
 				// Use a delegating unbaked model to make sure the identical models only get baked a single time.
 				Identifier wheatStage0Id = Identifier.ofVanilla("block/wheat_stage0");
-
-				UnbakedModel stage0Model = new DelegatingUnbakedModel(wheatStage0Id);
+				Identifier wheatStage7Id = Identifier.ofVanilla("block/wheat_stage7");
+				UnbakedModel wheatStage0Model = new DelegatingUnbakedModel(wheatStage0Id);
+				UnbakedModel wheatStage7Model = new DelegatingUnbakedModel(wheatStage7Id);
 
 				for (int age = 0; age <= 6; age++) {
-					context.setModel(state.with(CropBlock.AGE, age), stage0Model);
+					context.setModel(state.with(CropBlock.AGE, age), wheatStage0Model);
 				}
 
-				context.setModel(state.with(CropBlock.AGE, 7), context.getOrLoadModel(Identifier.ofVanilla("block/wheat_stage7")));
+				context.setModel(state.with(CropBlock.AGE, 7), wheatStage7Model);
 			});
 		});
 
diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java
deleted file mode 100644
index 7a4782fba..000000000
--- a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/NestedModelLoadingTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.fabricmc.fabric.test.model.loading;
-
-import static net.fabricmc.fabric.test.model.loading.ModelTestModClient.id;
-
-import com.mojang.logging.LogUtils;
-
-import net.minecraft.class_10096;
-
-import org.slf4j.Logger;
-
-import net.minecraft.client.render.model.ModelLoader;
-import net.minecraft.client.render.model.UnbakedModel;
-import net.minecraft.util.Identifier;
-
-import net.fabricmc.api.ClientModInitializer;
-import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
-
-/**
- * Tests that deep model resolution resolve each model a single time, depth-first.
- */
-public class NestedModelLoadingTest implements ClientModInitializer {
-	private static final Logger LOGGER = LogUtils.getLogger();
-
-	private static final Identifier BASE_MODEL = id("nested_base");
-	private static final Identifier NESTED_MODEL_1 = id("nested_1");
-	private static final Identifier NESTED_MODEL_2 = id("nested_2");
-	private static final Identifier NESTED_MODEL_3 = id("nested_3");
-	private static final Identifier NESTED_MODEL_4 = id("nested_4");
-	private static final Identifier NESTED_MODEL_5 = id("nested_5");
-	private static final Identifier TARGET_MODEL = Identifier.ofVanilla("block/stone");
-
-	@Override
-	public void onInitializeClient() {
-		ModelLoadingPlugin.register(pluginContext -> {
-			pluginContext.addModels(BASE_MODEL);
-
-			pluginContext.resolveModel().register(context -> {
-				Identifier id = context.id();
-				UnbakedModel ret = null;
-
-				if (id.equals(BASE_MODEL)) {
-					LOGGER.info("Nested model 1 started loading");
-					ret = context.getOrLoadModel(NESTED_MODEL_1);
-					LOGGER.info("Nested model 1 finished loading");
-				} else if (id.equals(NESTED_MODEL_1)) {
-					LOGGER.info(" Nested model 2 started loading");
-					ret = context.getOrLoadModel(NESTED_MODEL_2);
-					LOGGER.info(" Nested model 2 finished loading");
-				} else if (id.equals(NESTED_MODEL_2)) {
-					LOGGER.info("  Nested model 3 started loading");
-					ret = context.getOrLoadModel(NESTED_MODEL_3);
-					LOGGER.info("  Nested model 3 finished loading");
-				} else if (id.equals(NESTED_MODEL_3)) {
-					// Will be overridden by the model modifier below anyway.
-					LOGGER.info("   Returning dummy model for nested model 3");
-					ret = context.getOrLoadModel(class_10096.field_53660);
-				} else if (id.equals(NESTED_MODEL_4)) {
-					// Will be overridden by the model modifier below anyway.
-					LOGGER.info("    Returning dummy model for nested model 4");
-					ret = context.getOrLoadModel(class_10096.field_53660);
-				} else if (id.equals(NESTED_MODEL_5)) {
-					LOGGER.info("     Target model started loading");
-					ret = context.getOrLoadModel(TARGET_MODEL);
-					LOGGER.info("     Target model finished loading");
-				}
-
-				return ret;
-			});
-
-			pluginContext.modifyModelOnLoad().register((model, context) -> {
-				UnbakedModel ret = model;
-				Identifier id = context.resourceId();
-
-				if (id != null) {
-					if (id.equals(NESTED_MODEL_3)) {
-						LOGGER.info("   Nested model 4 started loading");
-						ret = context.getOrLoadModel(NESTED_MODEL_4);
-						LOGGER.info("   Nested model 4 finished loading");
-
-						if (!id.equals(context.resourceId())) {
-							throw new AssertionError("Context object should not have changed.");
-						}
-					} else if (id.equals(NESTED_MODEL_4)) {
-						LOGGER.info("    Nested model 5 started loading");
-						ret = context.getOrLoadModel(NESTED_MODEL_5);
-						LOGGER.info("    Nested model 5 finished loading");
-					}
-				}
-
-				return ret;
-			});
-		});
-	}
-}
diff --git a/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json b/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json
index 97eea29e4..3291587c7 100644
--- a/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json
+++ b/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json
@@ -12,7 +12,6 @@
   "entrypoints": {
     "client": [
       "net.fabricmc.fabric.test.model.loading.ModelTestModClient",
-      "net.fabricmc.fabric.test.model.loading.NestedModelLoadingTest",
       "net.fabricmc.fabric.test.model.loading.PreparablePluginTest"
     ]
   }
diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/MutableQuadView.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/MutableQuadView.java
index fa40d0769..cf946622a 100644
--- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/MutableQuadView.java
+++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/MutableQuadView.java
@@ -16,6 +16,7 @@
 
 package net.fabricmc.fabric.api.renderer.v1.mesh;
 
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.Nullable;
 import org.joml.Vector2f;
 import org.joml.Vector2fc;
@@ -292,14 +293,39 @@ public interface MutableQuadView extends QuadView {
 	/**
 	 * Enables bulk vertex data transfer using the standard Minecraft quad format.
 	 *
+	 * <p>The material applied to this quad view might be slightly different from the {@code material} parameter regarding diffuse shading.
+	 * If either the baked quad {@link BakedQuad#hasShade() does not have shade} or the material {@link MaterialFinder#disableDiffuse(boolean) does not have shade},
+	 * diffuse shading will be disabled for this quad view.
+	 * This is reflected in the quad view's {@link #material()}, but the {@code material} parameter is unchanged (it is immutable anyway).
+	 *
+	 * <p>The {@linkplain BakedQuad#getLightEmission() baked quad's light emission} will be applied to the lightmap values from the vertex data
+	 * after copying.
+	 *
 	 * <p>Calling this method does not emit the quad.
+	 */
+	default MutableQuadView fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace) {
+		return fromVanilla(quad, material, cullFace, true);
+	}
+
+	// TODO: If this is unmarked as experimental, update the javadoc of the other overloads.
+	/**
+	 * Enables bulk vertex data transfer using the standard Minecraft quad format.
 	 *
 	 * <p>The material applied to this quad view might be slightly different from the {@code material} parameter regarding diffuse shading.
 	 * If either the baked quad {@link BakedQuad#hasShade() does not have shade} or the material {@link MaterialFinder#disableDiffuse(boolean) does not have shade},
 	 * diffuse shading will be disabled for this quad view.
 	 * This is reflected in the quad view's {@link #material()}, but the {@code material} parameter is unchanged (it is immutable anyway).
+	 *
+	 * <p>If {@code applyLightEmission} is {@code true}, the {@linkplain BakedQuad#getLightEmission() baked quad's light emission} will be applied
+	 * to the lightmap values from the vertex data after copying. Otherwise, the light emission will be ignored.
+	 *
+	 * <p>Calling this method does not emit the quad.
+	 *
+	 * @apiNote This method is marked as experimental because future snapshots may change the item renderer to also respect quad light emission,
+	 * in which case this method will be removed. See <a href="https://bugs.mojang.com/browse/MC-275296">MC-275296</a>.
 	 */
-	MutableQuadView fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace);
+	@ApiStatus.Experimental
+	MutableQuadView fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace, boolean applyLightEmission);
 
 	/**
 	 * @deprecated Use {@link #color(int, int)} instead.
diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadEmitter.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadEmitter.java
index 1616d8def..c3632e442 100644
--- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadEmitter.java
+++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadEmitter.java
@@ -16,6 +16,7 @@
 
 package net.fabricmc.fabric.api.renderer.v1.mesh;
 
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.Nullable;
 import org.joml.Vector2f;
 import org.joml.Vector2fc;
@@ -138,7 +139,14 @@ public interface QuadEmitter extends MutableQuadView {
 	QuadEmitter fromVanilla(int[] quadData, int startIndex);
 
 	@Override
-	QuadEmitter fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace);
+	default QuadEmitter fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace) {
+		MutableQuadView.super.fromVanilla(quad, material, cullFace);
+		return this;
+	}
+
+	@ApiStatus.Experimental
+	@Override
+	QuadEmitter fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace, boolean applyLightEmission);
 
 	/**
 	 * Tolerance for determining if the depth parameter to {@link #square(Direction, float, float, float, float, float)}
diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadView.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadView.java
index 707098d76..2a49b9b2d 100644
--- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadView.java
+++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/api/renderer/v1/mesh/QuadView.java
@@ -21,6 +21,7 @@ import org.jetbrains.annotations.Nullable;
 import org.joml.Vector2f;
 import org.joml.Vector3f;
 
+import net.minecraft.client.render.LightmapTextureManager;
 import net.minecraft.client.render.VertexFormats;
 import net.minecraft.client.render.model.BakedQuad;
 import net.minecraft.client.texture.Sprite;
@@ -200,7 +201,23 @@ public interface QuadView {
 		// Mimic material properties to the largest possible extent
 		int outputColorIndex = material().disableColorIndex() ? -1 : colorIndex();
 		boolean outputShade = !material().disableDiffuse();
-		return new BakedQuad(vertexData, outputColorIndex, lightFace(), sprite, outputShade, 0 /* TODO 24w33a */);
+		// The output light emission is equal to the minimum of all four sky light values and all four block light values.
+		int outputLightEmission = 15;
+
+		for (int i = 0; i < 4; i++) {
+			int lightmap = lightmap(i);
+
+			if (lightmap == 0) {
+				outputLightEmission = 0;
+				break;
+			}
+
+			int blockLight = LightmapTextureManager.getBlockLightCoordinates(lightmap);
+			int skyLight = LightmapTextureManager.getSkyLightCoordinates(lightmap);
+			outputLightEmission = Math.min(outputLightEmission, Math.min(blockLight, skyLight));
+		}
+
+		return new BakedQuad(vertexData, outputColorIndex, lightFace(), sprite, outputShade, outputLightEmission);
 	}
 
 	/**
diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/impl/renderer/VanillaModelEncoder.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/impl/renderer/VanillaModelEncoder.java
index 73cf27200..b76ebd5dc 100644
--- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/impl/renderer/VanillaModelEncoder.java
+++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/impl/renderer/VanillaModelEncoder.java
@@ -78,7 +78,7 @@ public class VanillaModelEncoder {
 
 			for (int j = 0; j < count; j++) {
 				final BakedQuad q = quads.get(j);
-				emitter.fromVanilla(q, STANDARD_MATERIAL, cullFace);
+				emitter.fromVanilla(q, STANDARD_MATERIAL, cullFace, false);
 				emitter.emit();
 			}
 		}
diff --git a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlock.java b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlock.java
index 7004b5f8d..ed4d09160 100644
--- a/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlock.java
+++ b/fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/FrameBlock.java
@@ -16,8 +16,6 @@
 
 package net.fabricmc.fabric.test.renderer;
 
-import net.minecraft.util.ActionResult;
-
 import org.jetbrains.annotations.Nullable;
 
 import net.minecraft.block.Block;
@@ -27,6 +25,7 @@ import net.minecraft.block.Blocks;
 import net.minecraft.block.entity.BlockEntity;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.ItemStack;
+import net.minecraft.util.ActionResult;
 import net.minecraft.util.Hand;
 import net.minecraft.util.hit.BlockHitResult;
 import net.minecraft.util.math.BlockPos;
@@ -46,8 +45,6 @@ public class FrameBlock extends Block implements BlockEntityProvider, FabricBloc
 	@Override
 	public ActionResult onUseWithItem(ItemStack stack, BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult blockHitResult) {
 		if (world.getBlockEntity(pos) instanceof FrameBlockEntity frame) {
-			Block handBlock = Block.getBlockFromItem(stack.getItem());
-
 			@Nullable
 			Block currentBlock = frame.getBlock();
 
@@ -59,26 +56,31 @@ public class FrameBlock extends Block implements BlockEntityProvider, FabricBloc
 						frame.setBlock(null);
 					}
 
-					if (world.isClient)
-						return ActionResult.SUCCESS;
+					return ActionResult.SUCCESS;
+				} else {
+					return ActionResult.PASS;
 				}
-
-				return ActionResult.PASS_TO_DEFAULT_BLOCK_ACTION;
 			}
 
+			Block handBlock = Block.getBlockFromItem(stack.getItem());
+
 			// getBlockFromItem will return air if we do not have a block item in hand
 			if (handBlock == Blocks.AIR) {
-				return ActionResult.FAIL;
+				return ActionResult.PASS;
 			}
 
 			// Do not allow blocks that may have a block entity
 			if (handBlock instanceof BlockEntityProvider) {
-				return ActionResult.FAIL;
+				return ActionResult.PASS;
 			}
 
-			stack.decrement(1);
+			if (currentBlock == handBlock) {
+				return ActionResult.PASS;
+			}
 
 			if (!world.isClient()) {
+				stack.decrementUnlessCreative(1, player);
+
 				if (currentBlock != null) {
 					player.getInventory().offerOrDrop(new ItemStack(currentBlock));
 				}
@@ -86,11 +88,10 @@ public class FrameBlock extends Block implements BlockEntityProvider, FabricBloc
 				frame.setBlock(handBlock);
 			}
 
-			if (world.isClient())
-				return ActionResult.SUCCESS;
+			return ActionResult.SUCCESS;
 		}
 
-		return ActionResult.FAIL;
+		return ActionResult.PASS_TO_DEFAULT_BLOCK_ACTION;
 	}
 
 	@Nullable
diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameUnbakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameUnbakedModel.java
index 1a5406a6a..8f595f0db 100644
--- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameUnbakedModel.java
+++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/FrameUnbakedModel.java
@@ -16,8 +16,6 @@
 
 package net.fabricmc.fabric.test.renderer.client;
 
-import java.util.Collection;
-import java.util.Collections;
 import java.util.function.Function;
 
 import org.jetbrains.annotations.Nullable;
@@ -41,19 +39,8 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
 public class FrameUnbakedModel implements UnbakedModel {
 	private static final SpriteIdentifier OBSIDIAN_SPRITE_ID = new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, Identifier.ofVanilla("block/obsidian"));
 
-	// TODO 24w33a
-	/*@Override
-	public Collection<Identifier> getModelDependencies() {
-		return Collections.emptySet();
-	}
-
-	@Override
-	public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
-	}*/
-
 	@Override
 	public void method_62326(class_10103 arg, class_10102 arg2) {
-
 	}
 
 	/*
diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/OctagonalColumnUnbakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/OctagonalColumnUnbakedModel.java
index e42dc0a7a..478dc6b29 100644
--- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/OctagonalColumnUnbakedModel.java
+++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/OctagonalColumnUnbakedModel.java
@@ -16,8 +16,6 @@
 
 package net.fabricmc.fabric.test.renderer.client;
 
-import java.util.Collection;
-import java.util.Collections;
 import java.util.function.Function;
 
 import org.jetbrains.annotations.Nullable;
@@ -56,19 +54,8 @@ public class OctagonalColumnUnbakedModel implements UnbakedModel {
 		this.shadeMode = shadeMode;
 	}
 
-	// TODO 24w33a
-	/*@Override
-	public Collection<Identifier> getModelDependencies() {
-		return Collections.emptySet();
-	}
-
-	@Override
-	public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
-	}*/
-
 	@Override
 	public void method_62326(class_10103 arg, class_10102 arg2) {
-
 	}
 
 	@Override
diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarUnbakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarUnbakedModel.java
index 5d7d54c94..a76a17c78 100644
--- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarUnbakedModel.java
+++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/PillarUnbakedModel.java
@@ -16,8 +16,6 @@
 
 package net.fabricmc.fabric.test.renderer.client;
 
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Function;
 import java.util.stream.Stream;
@@ -31,7 +29,6 @@ import net.minecraft.client.render.model.UnbakedModel;
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.client.util.SpriteIdentifier;
 import net.minecraft.screen.PlayerScreenHandler;
-import net.minecraft.util.Identifier;
 
 import net.fabricmc.fabric.test.renderer.RendererTest;
 
@@ -40,19 +37,8 @@ public class PillarUnbakedModel implements UnbakedModel {
 			.map(suffix -> new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, RendererTest.id("block/pillar_" + suffix)))
 			.toList();
 
-	// TODO 24w33a
-	/*@Override
-	public Collection<Identifier> getModelDependencies() {
-		return Collections.emptySet();
-	}
-
-	@Override
-	public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
-	}*/
-
 	@Override
 	public void method_62326(class_10103 arg, class_10102 arg2) {
-
 	}
 
 	@Nullable
diff --git a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneUnbakedModel.java b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneUnbakedModel.java
index 91a72e8f7..ba2290f35 100644
--- a/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneUnbakedModel.java
+++ b/fabric-renderer-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/renderer/client/RiverstoneUnbakedModel.java
@@ -16,8 +16,6 @@
 
 package net.fabricmc.fabric.test.renderer.client;
 
-import java.util.Collection;
-import java.util.Collections;
 import java.util.function.Function;
 
 import org.jetbrains.annotations.Nullable;
@@ -34,21 +32,10 @@ public class RiverstoneUnbakedModel implements UnbakedModel {
 	private static final Identifier STONE_MODEL_ID = Identifier.ofVanilla("block/stone");
 	private static final Identifier GOLD_BLOCK_MODEL_ID = Identifier.ofVanilla("block/gold_block");
 
-	// TODO 24w33a
-	/*@Override
-	public Collection<Identifier> getModelDependencies() {
-		return Collections.emptySet();
-	}
-
-	@Override
-	public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
-		modelLoader.apply(STONE_MODEL_ID).setParents(modelLoader);
-		modelLoader.apply(GOLD_BLOCK_MODEL_ID).setParents(modelLoader);
-	}*/
-
 	@Override
 	public void method_62326(class_10103 arg, class_10102 arg2) {
-
+		arg.method_62642(STONE_MODEL_ID);
+		arg.method_62642(GOLD_BLOCK_MODEL_ID);
 	}
 
 	@Nullable
diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/IndigoRenderer.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/IndigoRenderer.java
index c4033ec78..d1eb246bc 100644
--- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/IndigoRenderer.java
+++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/IndigoRenderer.java
@@ -25,7 +25,6 @@ import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
 import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
 import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
 import net.fabricmc.fabric.impl.client.indigo.renderer.material.MaterialFinderImpl;
-import net.fabricmc.fabric.impl.client.indigo.renderer.material.RenderMaterialImpl;
 import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MeshBuilderImpl;
 
 /**
diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java
index 401bf8f67..a662227ee 100644
--- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java
+++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java
@@ -36,6 +36,7 @@ import net.minecraft.block.BlockState;
 import net.minecraft.client.render.LightmapTextureManager;
 import net.minecraft.client.render.WorldRenderer;
 import net.minecraft.client.render.block.BlockModelRenderer;
+import net.minecraft.client.render.model.BakedQuad;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Direction;
 import net.minecraft.util.math.MathHelper;
@@ -147,17 +148,18 @@ public abstract class AoCalculator {
 	// Because this instance is effectively thread-local, we preserve instances
 	// to avoid making a new allocation each call.
 	private final float[] vanillaAoData = new float[Direction.values().length * 2];
-	private final BitSet vanillaAoControlBits = new BitSet(3);
+	private final BitSet vanillaAoFlags = new BitSet(3);
 	private final int[] vertexData = new int[EncodingFormat.QUAD_STRIDE];
+	private final DummyBakedQuad dummyBakedQuad = new DummyBakedQuad();
 
 	private void calcVanilla(QuadViewImpl quad, float[] aoDest, int[] lightDest) {
-		vanillaAoControlBits.clear();
+		vanillaAoFlags.clear();
 		final Direction lightFace = quad.lightFace();
 		quad.toVanilla(vertexData, 0);
+		dummyBakedQuad.prepare(quad.lightFace(), quad.hasShade());
 
-		VanillaAoHelper.updateShape(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, lightFace, vanillaAoData, vanillaAoControlBits);
-		// TODO 24w33a
-		//vanillaCalc.apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, lightFace, vanillaAoData, vanillaAoControlBits, quad.hasShade());
+		VanillaAoHelper.getQuadDimensions(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, lightFace, vanillaAoData, vanillaAoFlags);
+		vanillaCalc.apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vanillaAoData, vanillaAoFlags, dummyBakedQuad);
 
 		System.arraycopy(vanillaCalc.brightness, 0, aoDest, 0, 4);
 		System.arraycopy(vanillaCalc.light, 0, lightDest, 0, 4);
@@ -582,4 +584,31 @@ public abstract class AoCalculator {
 		if (b == 0) return a;
 		return Math.min(a, b);
 	}
+
+	// This quad is passed to the vanilla AO calc. It only calls getFace, isEmissive, getLightEmission, and hasShade.
+	// Since Indigo already applies vanilla's light emission value in MutableQuadView#fromVanilla, this quad should not
+	// provide its own light emission.
+	private static class DummyBakedQuad extends BakedQuad {
+		private Direction lightFace;
+		private boolean shade;
+
+		DummyBakedQuad() {
+			super(new int[32], -1, Direction.UP, null, true, 0);
+		}
+
+		public void prepare(Direction lightFace, boolean shade) {
+			this.lightFace = lightFace;
+			this.shade = shade;
+		}
+
+		@Override
+		public Direction getFace() {
+			return lightFace;
+		}
+
+		@Override
+		public boolean hasShade() {
+			return shade;
+		}
+	}
 }
diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/VanillaAoHelper.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/VanillaAoHelper.java
index 840df16d9..88d47d74a 100644
--- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/VanillaAoHelper.java
+++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/VanillaAoHelper.java
@@ -19,21 +19,18 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.aocalc;
 import java.util.BitSet;
 
 import net.minecraft.block.BlockState;
+import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.block.BlockModelRenderer;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Direction;
 import net.minecraft.world.BlockRenderView;
 
-public class VanillaAoHelper {
+public final class VanillaAoHelper {
 	// Renderer method we call isn't declared as static, but uses no
 	// instance data and is called from multiple threads in vanilla also.
-	private static BlockModelRenderer blockRenderer;
+	private static BlockModelRenderer BLOCK_RENDERER = MinecraftClient.getInstance().getBlockRenderManager().getModelRenderer();
 
-	public static void initialize(BlockModelRenderer instance) {
-		blockRenderer = instance;
-	}
-
-	public static void updateShape(BlockRenderView blockRenderView, BlockState blockState, BlockPos pos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits) {
-		blockRenderer.getQuadDimensions(blockRenderView, blockState, pos, vertexData, face, aoData, controlBits);
+	public static void getQuadDimensions(BlockRenderView blockRenderView, BlockState blockState, BlockPos pos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits) {
+		BLOCK_RENDERER.getQuadDimensions(blockRenderView, blockState, pos, vertexData, face, aoData, controlBits);
 	}
 }
diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java
index 09cfbd5dd..64a24e2d4 100644
--- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java
+++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/mesh/MutableQuadViewImpl.java
@@ -30,6 +30,7 @@ import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingForma
 
 import org.jetbrains.annotations.Nullable;
 
+import net.minecraft.client.render.LightmapTextureManager;
 import net.minecraft.client.render.model.BakedQuad;
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.util.math.Direction;
@@ -194,7 +195,7 @@ public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEm
 	}
 
 	@Override
-	public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace) {
+	public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace, boolean applyLightEmission) {
 		fromVanilla(quad.getVertexData(), 0);
 		data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(0, cullFace);
 		nominalFace(quad.getFace());
@@ -204,6 +205,14 @@ public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEm
 			material = RenderMaterialImpl.setDisableDiffuse((RenderMaterialImpl) material, true);
 		}
 
+		if (applyLightEmission && quad.isEmissive()) {
+			int lightEmission = quad.getLightEmission();
+
+			for (int i = 0; i < 4; i++) {
+				lightmap(i, LightmapTextureManager.applyEmission(lightmap(i), lightEmission));
+			}
+		}
+
 		material(material);
 		tag(0);
 		return this;
diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java
index 8c49c69f0..a78e7d755 100644
--- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java
+++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderInfo.java
@@ -111,9 +111,7 @@ public class BlockRenderInfo {
 		if ((cullCompletionFlags & mask) == 0) {
 			cullCompletionFlags |= mask;
 
-			// TODO 24w33a - Double check this
-			//if (Block.shouldDrawSide(blockState, blockView, blockPos, face, searchPos.set(blockPos, face))) {
-			if (Block.shouldDrawSide(blockState, blockState, face)) {
+			if (Block.shouldDrawSide(blockState, blockView.getBlockState(searchPos.set(blockPos, face)), face)) {
 				cullResultFlags |= mask;
 				return true;
 			} else {
diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java
index 0fd0892e5..bc38e6d50 100644
--- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java
+++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ItemRenderContext.java
@@ -120,7 +120,7 @@ public class ItemRenderContext extends AbstractRenderContext {
 		return vanillaModelConsumer;
 	}
 
-	public void renderModel(ItemStack itemStack, ModelTransformationMode transformMode, boolean invert, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int lightmap, int overlay, BakedModel model) {
+	public void renderModel(ItemStack itemStack, ModelTransformationMode transformMode, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int lightmap, int overlay, BakedModel model) {
 		this.itemStack = itemStack;
 		this.transformMode = transformMode;
 		this.matrixStack = matrixStack;
diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BlockModelRendererMixin.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BlockModelRendererMixin.java
index 69f7bb6eb..f643cb832 100644
--- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BlockModelRendererMixin.java
+++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/BlockModelRendererMixin.java
@@ -31,7 +31,6 @@ import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.random.Random;
 import net.minecraft.world.BlockRenderView;
 
-import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.VanillaAoHelper;
 import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderContext;
 
 @Mixin(BlockModelRenderer.class)
@@ -47,9 +46,4 @@ public abstract class BlockModelRendererMixin {
 			ci.cancel();
 		}
 	}
-
-	@Inject(at = @At("RETURN"), method = "<init>*")
-	private void onInit(CallbackInfo ci) {
-		VanillaAoHelper.initialize((BlockModelRenderer) (Object) this);
-	}
 }
diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ItemRendererMixin.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ItemRendererMixin.java
index 1bc2e8d38..ff981d906 100644
--- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ItemRendererMixin.java
+++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/mixin/client/indigo/renderer/ItemRendererMixin.java
@@ -43,12 +43,10 @@ public abstract class ItemRendererMixin {
 	@Unique
 	private final ThreadLocal<ItemRenderContext> fabric_contexts = ThreadLocal.withInitial(() -> new ItemRenderContext(colors));
 
-	// FIXME: Method is unmapped in Yarn
-	@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/BakedModel;isBuiltin()Z"), method = "method_62476", cancellable = true)
-	public void hook_renderItem(ItemStack stack, ModelTransformationMode transformMode, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, int overlay, BakedModel model, boolean invert, CallbackInfo ci) {
+	@Inject(method = "method_62476", at = @At(value = "HEAD"), cancellable = true)
+	public void hook_renderItem(ItemStack stack, ModelTransformationMode transformMode, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, int overlay, BakedModel model, boolean notInHand, CallbackInfo ci) {
 		if (!model.isVanillaAdapter()) {
-			fabric_contexts.get().renderModel(stack, transformMode, invert, matrixStack, vertexConsumerProvider, light, overlay, model);
-			matrixStack.pop();
+			fabric_contexts.get().renderModel(stack, transformMode, matrixStack, vertexConsumerProvider, light, overlay, model);
 			ci.cancel();
 		}
 	}
diff --git a/settings.gradle b/settings.gradle
index 36a96f62c..87019d51b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -38,14 +38,14 @@ include 'fabric-key-binding-api-v1'
 include 'fabric-lifecycle-events-v1'
 include 'fabric-loot-api-v3'
 include 'fabric-message-api-v1'
-//include 'fabric-model-loading-api-v1'
+include 'fabric-model-loading-api-v1'
 include 'fabric-networking-api-v1'
 include 'fabric-object-builder-api-v1'
 include 'fabric-particles-v1'
 include 'fabric-recipe-api-v1'
 include 'fabric-registry-sync-v0'
-//include 'fabric-renderer-api-v1'
-//include 'fabric-renderer-indigo'
+include 'fabric-renderer-api-v1'
+include 'fabric-renderer-indigo'
 include 'fabric-rendering-fluids-v1'
 include 'fabric-rendering-v1'
 include 'fabric-resource-conditions-api-v1'