diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/BrewingRecipeRegistryBuilderCallback.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/BrewingRecipeRegistryBuilderCallback.java
deleted file mode 100644
index f167a7700..000000000
--- a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/BrewingRecipeRegistryBuilderCallback.java
+++ /dev/null
@@ -1,43 +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.api.registry;
-
-import net.minecraft.recipe.BrewingRecipeRegistry;
-
-import net.fabricmc.fabric.api.event.Event;
-import net.fabricmc.fabric.api.event.EventFactory;
-
-/**
- * Use this event to register custom brewing recipes.
- */
-public interface BrewingRecipeRegistryBuilderCallback {
-	/**
-	 * An event that is called when the brewing recipe registry is being built.
-	 */
-	Event<BrewingRecipeRegistryBuilderCallback> BUILD = EventFactory.createArrayBacked(BrewingRecipeRegistryBuilderCallback.class, listeners -> builder -> {
-		for (BrewingRecipeRegistryBuilderCallback listener : listeners) {
-			listener.build(builder);
-		}
-	});
-
-	/**
-	 * Called when the brewing recipe registry is being built.
-	 *
-	 * @param builder the {@link BrewingRecipeRegistry} instance
-	 */
-	void build(BrewingRecipeRegistry.class_9665 builder);
-}
diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/FabricBrewingRecipeRegistryBuilder.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/FabricBrewingRecipeRegistryBuilder.java
new file mode 100644
index 000000000..1f8d540a1
--- /dev/null
+++ b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/api/registry/FabricBrewingRecipeRegistryBuilder.java
@@ -0,0 +1,70 @@
+/*
+ * 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.api.registry;
+
+import net.minecraft.item.Item;
+import net.minecraft.potion.Potion;
+import net.minecraft.recipe.BrewingRecipeRegistry;
+import net.minecraft.recipe.Ingredient;
+import net.minecraft.registry.entry.RegistryEntry;
+import net.minecraft.resource.featuretoggle.FeatureSet;
+
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+/**
+ * An extension of {@link BrewingRecipeRegistry.Builder} to support ingredients.
+ */
+public interface FabricBrewingRecipeRegistryBuilder {
+	/**
+	 * An event that is called when the brewing recipe registry is being built.
+	 */
+	Event<FabricBrewingRecipeRegistryBuilder.BuildCallback> BUILD = EventFactory.createArrayBacked(FabricBrewingRecipeRegistryBuilder.BuildCallback.class, listeners -> builder -> {
+		for (FabricBrewingRecipeRegistryBuilder.BuildCallback listener : listeners) {
+			listener.build(builder);
+		}
+	});
+
+	default void registerItemRecipe(Item input, Ingredient ingredient, Item output) {
+		throw new AssertionError("Must be implemented via interface injection");
+	}
+
+	default void registerPotionRecipe(RegistryEntry<Potion> input, Ingredient ingredient, RegistryEntry<Potion> output) {
+		throw new AssertionError("Must be implemented via interface injection");
+	}
+
+	default void registerRecipes(Ingredient ingredient, RegistryEntry<Potion> potion) {
+		throw new AssertionError("Must be implemented via interface injection");
+	}
+
+	default FeatureSet getEnabledFeatures() {
+		throw new AssertionError("Must be implemented via interface injection");
+	}
+
+	/**
+	 * Use this event to register custom brewing recipes.
+	 */
+	@FunctionalInterface
+	interface BuildCallback {
+		/**
+		 * Called when the brewing recipe registry is being built.
+		 *
+		 * @param builder the {@link BrewingRecipeRegistry} instance
+		 */
+		void build(BrewingRecipeRegistry.Builder builder);
+	}
+}
diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/mixin/content/registry/BrewingRecipeRegistryBuilderMixin.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/mixin/content/registry/BrewingRecipeRegistryBuilderMixin.java
index cf5c0f0c4..4c8596514 100644
--- a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/mixin/content/registry/BrewingRecipeRegistryBuilderMixin.java
+++ b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/mixin/content/registry/BrewingRecipeRegistryBuilderMixin.java
@@ -16,19 +16,74 @@
 
 package net.fabricmc.fabric.mixin.content.registry;
 
+import java.util.List;
+
+import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
+import net.minecraft.item.Item;
+import net.minecraft.potion.Potion;
+import net.minecraft.potion.Potions;
 import net.minecraft.recipe.BrewingRecipeRegistry;
+import net.minecraft.recipe.Ingredient;
+import net.minecraft.registry.entry.RegistryEntry;
+import net.minecraft.resource.featuretoggle.FeatureSet;
 
-import net.fabricmc.fabric.api.registry.BrewingRecipeRegistryBuilderCallback;
+import net.fabricmc.fabric.api.registry.FabricBrewingRecipeRegistryBuilder;
 
-@Mixin(BrewingRecipeRegistry.class_9665.class)
-public class BrewingRecipeRegistryBuilderMixin {
-	@Inject(method = "method_59701", at = @At("HEAD"))
+@Mixin(BrewingRecipeRegistry.Builder.class)
+public abstract class BrewingRecipeRegistryBuilderMixin implements FabricBrewingRecipeRegistryBuilder {
+	@Shadow
+	@Final
+	private FeatureSet enabledFeatures;
+
+	@Shadow
+	private static void assertPotion(Item potionType) {
+	}
+
+	@Shadow
+	@Final
+	private List<BrewingRecipeRegistry.Recipe<Item>> itemRecipes;
+
+	@Shadow
+	@Final
+	private List<BrewingRecipeRegistry.Recipe<Potion>> potionRecipes;
+
+	@Inject(method = "build", at = @At("HEAD"))
 	private void build(CallbackInfoReturnable<BrewingRecipeRegistry> cir) {
-		BrewingRecipeRegistryBuilderCallback.BUILD.invoker().build((BrewingRecipeRegistry.class_9665) (Object) this);
+		FabricBrewingRecipeRegistryBuilder.BUILD.invoker().build((BrewingRecipeRegistry.Builder) (Object) this);
+	}
+
+	@Override
+	public void registerItemRecipe(Item input, Ingredient ingredient, Item output) {
+		if (input.isEnabled(this.enabledFeatures) && output.isEnabled(this.enabledFeatures)) {
+			assertPotion(input);
+			assertPotion(output);
+			this.itemRecipes.add(new BrewingRecipeRegistry.Recipe<>(input.getRegistryEntry(), ingredient, output.getRegistryEntry()));
+		}
+	}
+
+	@Override
+	public void registerPotionRecipe(RegistryEntry<Potion> input, Ingredient ingredient, RegistryEntry<Potion> output) {
+		if (input.value().isEnabled(this.enabledFeatures) && output.value().isEnabled(this.enabledFeatures)) {
+			this.potionRecipes.add(new BrewingRecipeRegistry.Recipe<>(input, ingredient, output));
+		}
+	}
+
+	@Override
+	public void registerRecipes(Ingredient ingredient, RegistryEntry<Potion> potion) {
+		if (potion.value().isEnabled(this.enabledFeatures)) {
+			this.registerPotionRecipe(Potions.WATER, ingredient, Potions.MUNDANE);
+			this.registerPotionRecipe(Potions.AWKWARD, ingredient, potion);
+		}
+	}
+
+	@Override
+	public FeatureSet getEnabledFeatures() {
+		return this.enabledFeatures;
 	}
 }
diff --git a/fabric-content-registries-v0/src/main/resources/fabric.mod.json b/fabric-content-registries-v0/src/main/resources/fabric.mod.json
index 3b6958e48..8820f2a84 100644
--- a/fabric-content-registries-v0/src/main/resources/fabric.mod.json
+++ b/fabric-content-registries-v0/src/main/resources/fabric.mod.json
@@ -27,6 +27,9 @@
   ],
   "accessWidener" : "fabric-content-registries-v0.accesswidener",
   "custom": {
-    "fabric-api:module-lifecycle": "stable"
+    "fabric-api:module-lifecycle": "stable",
+    "loom:injected_interfaces": {
+      "net/minecraft/class_1845\u0024class_9665": ["net/fabricmc/fabric/api/registry/FabricBrewingRecipeRegistryBuilder"]
+    }
   }
 }
diff --git a/fabric-content-registries-v0/src/testmod/java/net/fabricmc/fabric/test/content/registry/ContentRegistryTest.java b/fabric-content-registries-v0/src/testmod/java/net/fabricmc/fabric/test/content/registry/ContentRegistryTest.java
index b42c7772e..ecd347be5 100644
--- a/fabric-content-registries-v0/src/testmod/java/net/fabricmc/fabric/test/content/registry/ContentRegistryTest.java
+++ b/fabric-content-registries-v0/src/testmod/java/net/fabricmc/fabric/test/content/registry/ContentRegistryTest.java
@@ -30,6 +30,8 @@ import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.Items;
 import net.minecraft.item.PotionItem;
+import net.minecraft.potion.Potions;
+import net.minecraft.recipe.Ingredient;
 import net.minecraft.registry.Registries;
 import net.minecraft.registry.Registry;
 import net.minecraft.registry.RegistryKey;
@@ -37,6 +39,7 @@ import net.minecraft.registry.RegistryKeys;
 import net.minecraft.registry.entry.RegistryEntry;
 import net.minecraft.registry.tag.BlockTags;
 import net.minecraft.registry.tag.ItemTags;
+import net.minecraft.resource.featuretoggle.FeatureFlags;
 import net.minecraft.text.Text;
 import net.minecraft.util.ActionResult;
 import net.minecraft.util.Identifier;
@@ -47,8 +50,8 @@ import net.minecraft.world.World;
 import net.minecraft.world.event.GameEvent;
 
 import net.fabricmc.api.ModInitializer;
-import net.fabricmc.fabric.api.registry.BrewingRecipeRegistryBuilderCallback;
 import net.fabricmc.fabric.api.registry.CompostingChanceRegistry;
+import net.fabricmc.fabric.api.registry.FabricBrewingRecipeRegistryBuilder;
 import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
 import net.fabricmc.fabric.api.registry.FlattenableBlockRegistry;
 import net.fabricmc.fabric.api.registry.FuelRegistry;
@@ -84,6 +87,7 @@ public final class ContentRegistryTest implements ModInitializer {
 		//  - assign a loot table to the nitwit villager type
 		//  - right-clicking a 'test_event' block will emit a 'test_event' game event, which will have a sculk sensor frequency of 2
 		//  - instant health potions can be brewed from awkward potions with any item in the 'minecraft:small_flowers' tag
+		//  - if Bundle experiment is enabled, luck potions can be brewed from awkward potions with a bundle
 		//  - dirty potions can be brewed by adding any item in the 'minecraft:dirt' tag to any standard potion
 
 		CompostingChanceRegistry.INSTANCE.add(Items.OBSIDIAN, 0.5F);
@@ -156,11 +160,14 @@ public final class ContentRegistryTest implements ModInitializer {
 				dirtyPotion);
 		/* Mods should use BrewingRecipeRegistry.registerPotionType(Item), which is access widened by fabric-transitive-access-wideners-v1
 		 * This testmod uses an accessor due to Loom limitations that prevent TAWs from applying across Gradle subproject boundaries */
-		BrewingRecipeRegistryBuilderCallback.BUILD.register(builder -> {
-			builder.method_59702(dirtyPotion);
-			// TODO 1.20.5 Ingredient.fromTag(ItemTags.DIRT)
-			builder.method_59703(Items.POTION, Items.DIRT, dirtyPotion);
-			// registerPotionRecipe(Potions.AWKWARD, Ingredient.fromTag(ItemTags.SMALL_FLOWERS), Potions.HEALING);
+		FabricBrewingRecipeRegistryBuilder.BUILD.register(builder -> {
+			builder.registerPotionType(dirtyPotion);
+			builder.registerItemRecipe(Items.POTION, Ingredient.fromTag(ItemTags.DIRT), dirtyPotion);
+			builder.registerPotionRecipe(Potions.AWKWARD, Ingredient.fromTag(ItemTags.SMALL_FLOWERS), Potions.HEALING);
+
+			if (builder.getEnabledFeatures().contains(FeatureFlags.BUNDLE)) {
+				builder.registerPotionRecipe(Potions.AWKWARD, Ingredient.ofItems(Items.BUNDLE), Potions.LUCK);
+			}
 		});
 	}
 
diff --git a/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/api/event/client/player/ClientPlayerBlockBreakEvents.java b/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/api/event/client/player/ClientPlayerBlockBreakEvents.java
index d817088c3..ecefa3549 100644
--- a/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/api/event/client/player/ClientPlayerBlockBreakEvents.java
+++ b/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/api/event/client/player/ClientPlayerBlockBreakEvents.java
@@ -30,7 +30,10 @@ import net.fabricmc.fabric.api.event.EventFactory;
  * <p>For preventing block breaking client side and other purposes, see {@link net.fabricmc.fabric.api.event.player.AttackBlockCallback}.
  * For server side block break events, see {@link net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents}.
  */
-public class ClientPlayerBlockBreakEvents {
+public final class ClientPlayerBlockBreakEvents {
+	private ClientPlayerBlockBreakEvents() {
+	}
+
 	/**
 	 * Callback after a block is broken client side.
 	 *
diff --git a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/event/player/UseEntityCallback.java b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/event/player/UseEntityCallback.java
index 0a2e73d45..cf892d7fb 100644
--- a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/event/player/UseEntityCallback.java
+++ b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/event/player/UseEntityCallback.java
@@ -34,7 +34,7 @@ import net.fabricmc.fabric.api.event.EventFactory;
  *
  * <p>On the logical client, the return values have the following meaning:
  * <ul>
- *     <li>SUCCESS cancels further processing, causes a hand swing, and sends a packet to the server.</li>
+ *     <li>SUCCESS/SUCCESS_NO_ITEM_USED cancels further processing, causes a hand swing, and sends a packet to the server.</li>
  *     <li>CONSUME cancels further processing, and sends a packet to the server. It does NOT cause a hand swing.</li>
  *     <li>PASS falls back to further processing.</li>
  *     <li>FAIL cancels further processing and does not send a packet to the server.</li>
diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantingContext.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantingContext.java
index 8b5520989..7e139710e 100644
--- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantingContext.java
+++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantingContext.java
@@ -18,6 +18,7 @@ package net.fabricmc.fabric.api.item.v1;
 
 import net.minecraft.enchantment.EnchantmentHelper;
 import net.minecraft.item.ItemStack;
+import net.minecraft.resource.featuretoggle.FeatureSet;
 import net.minecraft.util.math.random.Random;
 
 /*
@@ -36,7 +37,7 @@ public enum EnchantingContext {
 	 * When generating a random enchantment for the item. This includes the enchanting table, random
 	 * mob equipment, and the {@code enchant_with_levels} loot function.
 	 *
-	 * @see EnchantmentHelper#generateEnchantments(Random, ItemStack, int, boolean)
+	 * @see EnchantmentHelper#generateEnchantments(FeatureSet, Random, ItemStack, int, boolean)
 	 */
 	RANDOM_ENCHANTMENT,
 	/**
diff --git a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java
index 1b39db7f4..80389272f 100644
--- a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java
+++ b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java
@@ -26,6 +26,7 @@ import net.minecraft.item.Items;
 import net.minecraft.item.PickaxeItem;
 import net.minecraft.item.ToolMaterials;
 import net.minecraft.network.codec.PacketCodecs;
+import net.minecraft.potion.Potions;
 import net.minecraft.registry.Registries;
 import net.minecraft.registry.Registry;
 import net.minecraft.text.Text;
@@ -36,6 +37,7 @@ import net.fabricmc.api.ModInitializer;
 import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
 import net.fabricmc.fabric.api.item.v1.EnchantingContext;
 import net.fabricmc.fabric.api.item.v1.EnchantmentEvents;
+import net.fabricmc.fabric.api.registry.FabricBrewingRecipeRegistryBuilder;
 import net.fabricmc.fabric.api.registry.FuelRegistry;
 import net.fabricmc.fabric.api.util.TriState;
 
@@ -58,8 +60,7 @@ public class CustomDamageTest implements ModInitializer {
 	public void onInitialize() {
 		Registry.register(Registries.ITEM, new Identifier("fabric-item-api-v1-testmod", "weird_pickaxe"), WEIRD_PICK);
 		FuelRegistry.INSTANCE.add(WEIRD_PICK, 200);
-		// TODO 1.20.5
-		// FabricBrewingRecipeRegistry.registerPotionRecipe(Potions.WATER, Ingredient.ofItems(WEIRD_PICK), Potions.AWKWARD);
+		FabricBrewingRecipeRegistryBuilder.BUILD.register(builder -> builder.registerPotionRecipe(Potions.WATER, WEIRD_PICK, Potions.AWKWARD));
 		EnchantmentEvents.ALLOW_ENCHANTING.register(((enchantment, target, enchantingContext) -> {
 			if (target.isOf(Items.DIAMOND_PICKAXE)
 					&& enchantment == Enchantments.SHARPNESS
diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/CommonLifecycleEvents.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/CommonLifecycleEvents.java
index 8c3f9a82e..27750b653 100644
--- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/CommonLifecycleEvents.java
+++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/CommonLifecycleEvents.java
@@ -21,7 +21,7 @@ import net.minecraft.registry.DynamicRegistryManager;
 import net.fabricmc.fabric.api.event.Event;
 import net.fabricmc.fabric.api.event.EventFactory;
 
-public class CommonLifecycleEvents {
+public final class CommonLifecycleEvents {
 	private CommonLifecycleEvents() {
 	}
 
diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/entity/FabricDefaultAttributeRegistry.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/entity/FabricDefaultAttributeRegistry.java
index 2da45120a..455382f87 100644
--- a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/entity/FabricDefaultAttributeRegistry.java
+++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/entity/FabricDefaultAttributeRegistry.java
@@ -71,11 +71,11 @@ public final class FabricDefaultAttributeRegistry {
 	 * <p>If a registration overrides another, a debug log message will be emitted. Existing registrations
 	 * can be checked at {@link net.minecraft.entity.attribute.DefaultAttributeRegistry#hasDefinitionFor(EntityType)}.</p>
 	 *
-	 * <p>For convenience, this can also be done on the {@link FabricEntityTypeBuilder} to simplify the building process.
+	 * <p>For convenience, this can also be done on the {@link FabricEntityType.Builder} to simplify the building process.
 	 *
 	 * @param type      the entity type
 	 * @param container the container for the default attribute
-	 * @see	FabricEntityTypeBuilder.Living#defaultAttributes(Supplier)
+	 * @see	FabricEntityType.Builder.Living#defaultAttributes(Supplier)
 	 */
 	public static void register(EntityType<? extends LivingEntity> type, DefaultAttributeContainer container) {
 		if (DefaultAttributeRegistryAccessor.getRegistry().put(type, container) != null) {
diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/entity/FabricEntityTypeBuilder.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/entity/FabricEntityTypeBuilder.java
index 9c0c93e07..d0bcb2964 100644
--- a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/entity/FabricEntityTypeBuilder.java
+++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/entity/FabricEntityTypeBuilder.java
@@ -115,7 +115,7 @@ public class FabricEntityTypeBuilder<T extends Entity> {
 	 * @param <T> the type of entity
 	 *
 	 * @return a new living entity type builder
-	 * @deprecated use {@link FabricEntityType.Builder#createLiving(UnaryOperator)}
+	 * @deprecated use {@link FabricEntityType.Builder#createLiving(EntityType.EntityFactory, SpawnGroup, UnaryOperator)}
 	 */
 	@Deprecated
 	public static <T extends LivingEntity> FabricEntityTypeBuilder.Living<T> createLiving() {
@@ -128,7 +128,7 @@ public class FabricEntityTypeBuilder<T extends Entity> {
 	 * @param <T> the type of entity
 	 *
 	 * @return a new mob entity type builder
-	 * @deprecated use {@link FabricEntityType.Builder#createMob(UnaryOperator)}
+	 * @deprecated use {@link FabricEntityType.Builder#createMob(EntityType.EntityFactory, SpawnGroup, UnaryOperator)}
 	 */
 	public static <T extends MobEntity> FabricEntityTypeBuilder.Mob<T> createMob() {
 		return new FabricEntityTypeBuilder.Mob<>(SpawnGroup.MISC, FabricEntityTypeBuilder::emptyFactory);
@@ -203,7 +203,7 @@ public class FabricEntityTypeBuilder<T extends Entity> {
 	 * @param dimensions the dimensions representing the entity's size
 	 *
 	 * @return this builder for chaining
-	 * @deprecated use {@link EntityType.Builder#setDimensions(float, float)}
+	 * @deprecated use {@link EntityType.Builder#dimensions(float, float)}
 	 */
 	@Deprecated
 	public FabricEntityTypeBuilder<T> dimensions(EntityDimensions dimensions) {
@@ -347,7 +347,7 @@ public class FabricEntityTypeBuilder<T extends Entity> {
 	 * An extended version of {@link FabricEntityTypeBuilder} with support for features on present on {@link LivingEntity living entities}, such as default attributes.
 	 *
 	 * @param <T> Entity class.
-	 * @deprecated use {@link EntityType.Builder#createLiving(UnaryOperator)}
+	 * @deprecated use {@link EntityType.Builder#createLiving(EntityType.EntityFactory, SpawnGroup, UnaryOperator)}
 	 */
 	@Deprecated
 	public static class Living<T extends LivingEntity> extends FabricEntityTypeBuilder<T> {
@@ -606,7 +606,7 @@ public class FabricEntityTypeBuilder<T extends Entity> {
 		 * <p>This is used by mobs to determine whether Minecraft should spawn an entity within a certain context.
 		 *
 		 * @return this builder for chaining.
-		 * @deprecated use {@link FabricEntityType.Builder.Mob#spawnRestriction(SpawnRestriction.Location, Heightmap.Type, SpawnRestriction.SpawnPredicate)}
+		 * @deprecated use {@link FabricEntityType.Builder.Mob#spawnRestriction(SpawnLocation, Heightmap.Type, SpawnRestriction.SpawnPredicate)}
 		 */
 		@Deprecated
 		public FabricEntityTypeBuilder.Mob<T> spawnRestriction(SpawnLocation spawnLocation, Heightmap.Type heightmap, SpawnRestriction.SpawnPredicate<T> spawnPredicate) {
diff --git a/fabric-particles-v1/src/client/java/net/fabricmc/fabric/api/client/particle/v1/ParticleFactoryRegistry.java b/fabric-particles-v1/src/client/java/net/fabricmc/fabric/api/client/particle/v1/ParticleFactoryRegistry.java
index 7cd96a156..9fa32b16b 100644
--- a/fabric-particles-v1/src/client/java/net/fabricmc/fabric/api/client/particle/v1/ParticleFactoryRegistry.java
+++ b/fabric-particles-v1/src/client/java/net/fabricmc/fabric/api/client/particle/v1/ParticleFactoryRegistry.java
@@ -16,6 +16,8 @@
 
 package net.fabricmc.fabric.api.client.particle.v1;
 
+import org.jetbrains.annotations.ApiStatus;
+
 import net.minecraft.client.particle.ParticleFactory;
 import net.minecraft.particle.ParticleEffect;
 import net.minecraft.particle.ParticleType;
@@ -29,6 +31,7 @@ import net.fabricmc.fabric.impl.client.particle.ParticleFactoryRegistryImpl;
  *
  * @see FabricParticleTypes
  */
+@ApiStatus.NonExtendable
 public interface ParticleFactoryRegistry {
 	static ParticleFactoryRegistry getInstance() {
 		return ParticleFactoryRegistryImpl.INSTANCE;
diff --git a/fabric-particles-v1/src/main/java/net/fabricmc/fabric/api/particle/v1/FabricParticleTypes.java b/fabric-particles-v1/src/main/java/net/fabricmc/fabric/api/particle/v1/FabricParticleTypes.java
index e6258ecca..e39aa1988 100644
--- a/fabric-particles-v1/src/main/java/net/fabricmc/fabric/api/particle/v1/FabricParticleTypes.java
+++ b/fabric-particles-v1/src/main/java/net/fabricmc/fabric/api/particle/v1/FabricParticleTypes.java
@@ -18,14 +18,13 @@ package net.fabricmc.fabric.api.particle.v1;
 
 import java.util.function.Function;
 
-import com.mojang.serialization.Codec;
 import com.mojang.serialization.MapCodec;
 
 import net.minecraft.network.RegistryByteBuf;
 import net.minecraft.network.codec.PacketCodec;
-import net.minecraft.particle.DefaultParticleType;
 import net.minecraft.particle.ParticleEffect;
 import net.minecraft.particle.ParticleType;
+import net.minecraft.particle.SimpleParticleType;
 
 /**
  * Methods for creating particle types, both simple and using an existing attribute factory.
@@ -33,8 +32,8 @@ import net.minecraft.particle.ParticleType;
  * <p>Usage:
  * <blockquote>
  * <pre>
- * public static final DefaultParticleType SIMPLE_TEST_PARTICLE = FabricParticleTypes.simple();
- * public static final DefaultParticleType CUSTOM_TEST_PARTICLE = FabricParticleTypes.simple();
+ * public static final SimpleParticleType SIMPLE_TEST_PARTICLE = FabricParticleTypes.simple();
+ * public static final SimpleParticleType CUSTOM_TEST_PARTICLE = FabricParticleTypes.simple();
  *
  * {@literal @}Override
  * public void onInitialize() {
@@ -50,7 +49,7 @@ public final class FabricParticleTypes {
 	/**
 	 * Creates a new, default particle type for the given id.
 	 */
-	public static DefaultParticleType simple() {
+	public static SimpleParticleType simple() {
 		return simple(false);
 	}
 
@@ -59,31 +58,29 @@ public final class FabricParticleTypes {
 	 *
 	 * @param alwaysSpawn True to always spawn the particle regardless of distance.
 	 */
-	public static DefaultParticleType simple(boolean alwaysSpawn) {
-		return new DefaultParticleType(alwaysSpawn) { };
+	public static SimpleParticleType simple(boolean alwaysSpawn) {
+		return new SimpleParticleType(alwaysSpawn) { };
 	}
 
 	/**
 	 * Creates a new particle type with a custom factory and codecs for packet/data serialization.
 	 *
-	 * @param factory	 A factory for serializing string command parameters into a particle effect.
 	 * @param codec The codec for serialization.
 	 * @param packetCodec The packet codec for network serialization.
 	 */
-	public static <T extends ParticleEffect> ParticleType<T> complex(ParticleEffect.Factory<T> factory, final Function<ParticleType<T>, Codec<T>> codecGetter, final MapCodec<T> codec, final PacketCodec<? super RegistryByteBuf, T> packetCodec) {
-		return complex(false, factory, codec, packetCodec);
+	public static <T extends ParticleEffect> ParticleType<T> complex(final MapCodec<T> codec, final PacketCodec<? super RegistryByteBuf, T> packetCodec) {
+		return complex(false, codec, packetCodec);
 	}
 
 	/**
 	 * Creates a new particle type with a custom factory and codecs for packet/data serialization.
 	 *
 	 * @param alwaysSpawn True to always spawn the particle regardless of distance.
-	 * @param factory	 A factory for serializing string command parameters into a particle effect.
 	 * @param codec The codec for serialization.
 	 * @param packetCodec The packet codec for network serialization.
 	 */
-	public static <T extends ParticleEffect> ParticleType<T> complex(boolean alwaysSpawn, ParticleEffect.Factory<T> factory, final MapCodec<T> codec, final PacketCodec<? super RegistryByteBuf, T> packetCodec) {
-		return new ParticleType<T>(alwaysSpawn, factory) {
+	public static <T extends ParticleEffect> ParticleType<T> complex(boolean alwaysSpawn, final MapCodec<T> codec, final PacketCodec<? super RegistryByteBuf, T> packetCodec) {
+		return new ParticleType<>(alwaysSpawn) {
 			@Override
 			public MapCodec<T> getCodec() {
 				return codec;
@@ -100,12 +97,11 @@ public final class FabricParticleTypes {
 	 * Creates a new particle type with a custom factory and codecs for packet/data serialization.
 	 * This method is useful when two different {@link ParticleType}s share the same {@link ParticleEffect} implementation.
 	 *
-	 * @param factory	 A factory for serializing string command parameters into a particle effect.
 	 * @param codecGetter A function that, given the newly created type, returns the codec for serialization.
 	 * @param packetCodecGetter A function that, given the newly created type, returns the packet codec for network serialization.
 	 */
-	public static <T extends ParticleEffect> ParticleType<T> complex(ParticleEffect.Factory<T> factory, final Function<ParticleType<T>, MapCodec<T>> codecGetter, final Function<ParticleType<T>, PacketCodec<? super RegistryByteBuf, T>> packetCodecGetter) {
-		return complex(false, factory, codecGetter, packetCodecGetter);
+	public static <T extends ParticleEffect> ParticleType<T> complex(final Function<ParticleType<T>, MapCodec<T>> codecGetter, final Function<ParticleType<T>, PacketCodec<? super RegistryByteBuf, T>> packetCodecGetter) {
+		return complex(false, codecGetter, packetCodecGetter);
 	}
 
 	/**
@@ -113,12 +109,11 @@ public final class FabricParticleTypes {
 	 * This method is useful when two different {@link ParticleType}s share the same {@link ParticleEffect} implementation.
 	 *
 	 * @param alwaysSpawn True to always spawn the particle regardless of distance.
-	 * @param factory	 A factory for serializing string command parameters into a particle effect.
 	 * @param codecGetter A function that, given the newly created type, returns the codec for serialization.
 	 * @param packetCodecGetter A function that, given the newly created type, returns the packet codec for network serialization.
 	 */
-	public static <T extends ParticleEffect> ParticleType<T> complex(boolean alwaysSpawn, ParticleEffect.Factory<T> factory, final Function<ParticleType<T>, MapCodec<T>> codecGetter, final Function<ParticleType<T>, PacketCodec<? super RegistryByteBuf, T>> packetCodecGetter) {
-		return new ParticleType<T>(alwaysSpawn, factory) {
+	public static <T extends ParticleEffect> ParticleType<T> complex(boolean alwaysSpawn, final Function<ParticleType<T>, MapCodec<T>> codecGetter, final Function<ParticleType<T>, PacketCodec<? super RegistryByteBuf, T>> packetCodecGetter) {
+		return new ParticleType<>(alwaysSpawn) {
 			@Override
 			public MapCodec<T> getCodec() {
 				return codecGetter.apply(this);
diff --git a/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTestSetup.java b/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTestSetup.java
index a6869df03..dc6b92fe1 100644
--- a/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTestSetup.java
+++ b/fabric-particles-v1/src/testmod/java/net/fabricmc/fabric/test/particle/ParticleTestSetup.java
@@ -48,7 +48,7 @@ public final class ParticleTestSetup implements ModInitializer {
 
 		CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
 			dispatcher.register(CommandManager.literal("addparticletestblocks").executes(context -> {
-				PlayerInventory inventory = context.getSource().getPlayer().getInventory();
+				PlayerInventory inventory = context.getSource().getPlayerOrThrow().getInventory();
 				inventory.offerOrDrop(new ItemStack(ALWAYS_TINTED));
 				inventory.offerOrDrop(new ItemStack(TINTED_OVER_WATER));
 				inventory.offerOrDrop(new ItemStack(NEVER_TINTED));
diff --git a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/CustomDataIngredient.java b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/CustomDataIngredient.java
index b2962f61a..8a8f9786e 100644
--- a/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/CustomDataIngredient.java
+++ b/fabric-recipe-api-v1/src/main/java/net/fabricmc/fabric/impl/recipe/ingredient/builtin/CustomDataIngredient.java
@@ -19,10 +19,7 @@ package net.fabricmc.fabric.impl.recipe.ingredient.builtin;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.mojang.brigadier.exceptions.CommandSyntaxException;
-import com.mojang.datafixers.util.Either;
 import com.mojang.serialization.Codec;
-import com.mojang.serialization.DataResult;
 import com.mojang.serialization.MapCodec;
 import com.mojang.serialization.codecs.RecordCodecBuilder;
 
@@ -94,17 +91,6 @@ public class CustomDataIngredient implements CustomIngredient {
 	private static class Serializer implements CustomIngredientSerializer<CustomDataIngredient> {
 		private static final Identifier ID = new Identifier("fabric", "custom_data");
 
-		// Supports decoding the NBT as a string as well as the object.
-		private static final Codec<NbtCompound> NBT_CODEC = Codec.xor(
-				Codec.STRING, NbtCompound.CODEC
-		).flatXmap(either -> either.map(s -> {
-			try {
-				return DataResult.success(StringNbtReader.parse(s));
-			} catch (CommandSyntaxException e) {
-				return DataResult.error(e::getMessage);
-			}
-		}, DataResult::success), nbtCompound -> DataResult.success(Either.left(nbtCompound.asString())));
-
 		private static final MapCodec<CustomDataIngredient> ALLOW_EMPTY_CODEC = createCodec(Ingredient.ALLOW_EMPTY_CODEC);
 		private static final MapCodec<CustomDataIngredient> DISALLOW_EMPTY_CODEC = createCodec(Ingredient.DISALLOW_EMPTY_CODEC);
 
@@ -118,7 +104,7 @@ public class CustomDataIngredient implements CustomIngredient {
 			return RecordCodecBuilder.mapCodec(instance ->
 					instance.group(
 							ingredientCodec.fieldOf("base").forGetter(CustomDataIngredient::getBase),
-							NBT_CODEC.fieldOf("nbt").forGetter(CustomDataIngredient::getNbt)
+							StringNbtReader.NBT_COMPOUND_CODEC.fieldOf("nbt").forGetter(CustomDataIngredient::getNbt)
 					).apply(instance, CustomDataIngredient::new)
 			);
 		}
diff --git a/fabric-sound-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/sound/client/SineStream.java b/fabric-sound-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/sound/client/SineStream.java
index 2d4fd4af4..3bfd628e9 100644
--- a/fabric-sound-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/sound/client/SineStream.java
+++ b/fabric-sound-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/sound/client/SineStream.java
@@ -39,7 +39,7 @@ class SineStream implements AudioStream {
 	}
 
 	@Override
-	public ByteBuffer getBuffer(int capacity) {
+	public ByteBuffer read(int capacity) {
 		ByteBuffer buffer = BufferUtils.createByteBuffer(capacity);
 
 		for (int i = 0; i < capacity; i++) {
diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/VariantCodecs.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/VariantCodecs.java
index 1ec91fa30..f8cc74ced 100644
--- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/VariantCodecs.java
+++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/VariantCodecs.java
@@ -17,9 +17,12 @@
 package net.fabricmc.fabric.impl.transfer;
 
 import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
 import com.mojang.serialization.codecs.RecordCodecBuilder;
 
 import net.minecraft.component.ComponentChanges;
+import net.minecraft.component.ComponentMapImpl;
+import net.minecraft.item.ItemStack;
 import net.minecraft.network.RegistryByteBuf;
 import net.minecraft.network.codec.PacketCodec;
 import net.minecraft.network.codec.PacketCodecs;
@@ -32,11 +35,13 @@ import net.fabricmc.fabric.impl.transfer.fluid.FluidVariantImpl;
 import net.fabricmc.fabric.impl.transfer.item.ItemVariantImpl;
 
 public class VariantCodecs {
-	public static final Codec<ItemVariant> ITEM_CODEC = RecordCodecBuilder.create(instance -> instance.group(
+	// AIR is valid (for some reason), don't use ItemStack#ITEM_CODEC
+	private static final Codec<ItemVariant> UNVALIDATED_ITEM_CODEC = RecordCodecBuilder.create(instance -> instance.group(
 			Registries.ITEM.getEntryCodec().fieldOf("item").forGetter(ItemVariant::getRegistryEntry),
 			ComponentChanges.CODEC.optionalFieldOf("components", ComponentChanges.EMPTY).forGetter(ItemVariant::getComponents)
 		).apply(instance, ItemVariantImpl::of)
 	);
+	public static final Codec<ItemVariant> ITEM_CODEC = UNVALIDATED_ITEM_CODEC.validate(VariantCodecs::validateComponents);
 	public static final PacketCodec<RegistryByteBuf, ItemVariant> ITEM_PACKET_CODEC = PacketCodec.tuple(
 			PacketCodecs.registryEntry(RegistryKeys.ITEM), ItemVariant::getRegistryEntry,
 			ComponentChanges.PACKET_CODEC, ItemVariant::getComponents,
@@ -53,4 +58,8 @@ public class VariantCodecs {
 			ComponentChanges.PACKET_CODEC, FluidVariant::getComponents,
 			FluidVariantImpl::of
 	);
+
+	private static DataResult<ItemVariant> validateComponents(ItemVariant variant) {
+		return ItemStack.validateComponents(ComponentMapImpl.create(variant.getItem().getComponents(), variant.getComponents())).map(v -> variant);
+	}
 }
diff --git a/gradle.properties b/gradle.properties
index ed36cce09..17bd06b32 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,9 +4,9 @@ fabric.loom.multiProjectOptimisation=true
 
 version=0.96.15
 minecraft_version=1.20.5-pre1
-yarn_version=+build.2
+yarn_version=+build.5
 loader_version=0.15.6
-installer_version=0.11.1
+installer_version=1.0.1
 
 prerelease=true
 curseforge_minecraft_version=1.20.5-Snapshot
diff --git a/settings.gradle b/settings.gradle
index 9be28e117..3e01c0569 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -42,7 +42,7 @@ include 'fabric-message-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-particles-v1'
 include 'fabric-recipe-api-v1'
 include 'fabric-registry-sync-v0'
 include 'fabric-renderer-api-v1'