Skip to content

fabric: complete Minecraft 26.1.2 Fabric port (vibecoded) - #1221

Open
joaovictor-martins wants to merge 15 commits into
DynamicTreesTeam:develop/26.1.2from
joaovictor-martins:develop/26.1.2
Open

fabric: complete Minecraft 26.1.2 Fabric port (vibecoded)#1221
joaovictor-martins wants to merge 15 commits into
DynamicTreesTeam:develop/26.1.2from
joaovictor-martins:develop/26.1.2

Conversation

@joaovictor-martins

Copy link
Copy Markdown

So, I gotta be honest, I vibe coded the hell out of this. I kinda used it as an experiment to test my local AI Agent running on my local machine, but I turned out kind of well. Things are apparently working as I tested some of it on a multiplayer sessions with friends. It was done with a mix of NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4 and some outsourcing remotely to Anthropic Sonnet.

Summary

Completes the Fabric port of develop/26.1.2 to Minecraft 26.1.2 — the Fabric loader now compiles and runs cleanly (:fabric:build succeeds, dedicated server boots without error, and the client was manually verified: saplings render, trees can be chopped, and bonemeal-grown trees render correctly at every branch/root thickness).

This picks up from the point the branch was at (e67a435e) and closes out every remaining Fabric compile error (587 → 0) plus 3 runtime-only bugs that only a real client/server run surfaced. #1209 (targeting 26.2, "offered for adoption") was used as a reference for the rendering-pipeline rewrite, but every referenced API was independently re-verified against this branch's actual pinned fabric-api version and the real Minecraft 26.1.2 jar before use — nothing was ported blind.

What changed

  • Config API: migrated forgeconfigapiport-fabric usage to its current public api.v5.ConfigRegistry (the branch was on an internal, non-public class).
  • Registry loader: eager block/item registration, real registerEntityDataSerializer, and fixed vanilla API drift in loot-type/entity/block-entity registration (RegistryLoader's loot registries now hold MapCodec directly, EntityType.Builder.build needs a ResourceKey, etc.).
  • Compat dependencies: wired up Jade, Continuity, and Serene Seasons (+ glitchcore) as compileOnly, and fixed each one's own API drift against the pinned versions (Jade's IElementElement/ElementHelperJadeUI rename, Serene Seasons' Biome.warmEnoughToRain new parameter, Continuity's model-wrapping reflection).
  • Rendering pipeline: full rewrite onto the new BlockStateModel/FabricBlockStateModel API replacing the removed BakedModel extension points, plus color/render-layer/sprite-source registration onto the current Fabric+vanilla tint-source system (BlockColorRegistry, ItemTintSources, SpriteSourceRegistryBlockRenderLayerMap has no replacement; render-layer assignment moved out of this responsibility entirely, matching NeoForge).
  • Vanilla API drift in common/: HolderSet.isBound() (new abstract method), several Mojang renames/moves (net.minecraft.Utilnet.minecraft.util.Util, ResourceKey.location().identifier(), NativeImage.getPixelRGBAgetPixel, Registry.getHolderget, etc.), and a batch of access-widener fixes (stale descriptors, wrong verb, and several newly-private/final vanilla members this MC version introduced).
  • 3 runtime-only bugs (invisible to any compile step, found via server/client smoke tests):
    • 5 dead access-widener entries that failed Loom's validateAccessWidener task.
    • A server-startup NPE from building ItemStacks off unbound item Holders too early in mod init.
    • Every tree rendering untextured — fabric/build.gradle was never pulling in common/'s generated resources at all, and even after that, DT's custom branch/root model registration was never actually invoked and the generated blockstate JSON was missing the "fabric:type" key Fabric's model dispatch needs (only had NeoForge's "type").

Full task-by-task rationale, including what was found to differ from initial assumptions and why, is written up in detail in the commit messages.

Test plan

  • ./gradlew :fabric:buildBUILD SUCCESSFUL
  • ./gradlew :neoforge:buildBUILD SUCCESSFUL (confirmed no regression from the shared common/ fixes)
  • ./gradlew :fabric:runServer — boots cleanly, all species register, no crash
  • ./gradlew :fabric:runClient — manually verified: sapling renders, tree can be chopped, bonemeal growth renders branches/roots correctly at multiple thicknesses

…ic v5 API

Upstream's e67a435 wired up forgeconfigapiport-fabric:26.1.5 but called
through fuzs.forgeconfigapiport.fabric.impl.core.ConfigRegistryImpl, an
internal implementation package. The jar also ships the intended public
API, fuzs.forgeconfigapiport.fabric.api.v5.ConfigRegistry, with an
identical register(String, ModConfig.Type, IConfigSpec) signature (verified
via javap against the 26.1.5 jar) — switch both DynamicTreesFabric and
DynamicTreesFabricClient to depend on that instead.

Also drops a stale, no-longer-resolvable
fuzs.forgeconfigapiport.fabric.api.neoforge.v4.* import left over from the
pre-migration code in DynamicTreesFabricClient.java, which was itself
causing a real "package does not exist" compile error.
Needed to see the full error count while working through the Fabric port
plan instead of only the first 100. Per the plan, remove before the final
PR (or keep, see Task 7).
…gistration

Applies upstream PR DynamicTreesTeam#1208 verbatim (verified: applies cleanly against the
current tree, and FabricEntityDataRegistry is confirmed present in the
pinned fabric-api 0.145.3+26.1.1 via the fabric-object-builder-api-v1
submodule, and RegistryLoader.registerEntityDataSerializer's signature
matches).

- registerBlock/registerItem previously returned a Supplier that lazily
  called Registry.register(...) on first .get() — entries nobody ever
  polls during init (e.g. DIRT_BUCKET) never actually registered, breaking
  recipes referencing them. Now registers eagerly and returns a supplier
  over the already-registered instance.
- Adds registerEntityDataSerializer, previously unimplemented (falling
  back to vanilla's EntityDataSerializers.registerSerializer, which
  Fabric API forbids at runtime for desync-safety reasons), via
  FabricEntityDataRegistry.register(...).

Also removes the unused net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup
import — that package doesn't exist in fabric-api 0.145.3+26.1.1, but
registerCreativeTab already builds a plain vanilla CreativeModeTab.builder
directly and never referenced FabricItemGroup, so this was dead code
causing a live "package does not exist" compile error, not a real
migration.
…Only

Existing compat classes (compat/waila/*.java, compat/SereneSeasonsSeasonProvider.java,
compat/continuity/*.java) import snownee.jade.*, sereneseasons.*, and
me.pepperbell.continuity.* respectively, but fabric/build.gradle declared
none of these — unlike neoforge/build.gradle, which already pulls in Jade
and SereneSeasons (from CurseForge/Modrinth respectively).

Versions picked by querying the Modrinth API for releases declaring
compatibility with game version 26.1.2 and loader fabric:
- jade:26.1.9+fabric (latest matching release)
- continuity:3.0.1-beta.2+26.1 (only fabric/quilt releases available for
  this game version; no separate loader-specific pin needed)
- serene-seasons:26.1.2.0.2, matching the exact version string
  neoforge/build.gradle already pins, for cross-loader consistency (a
  newer 26.1.2.0.4 exists but wasn't picked, to avoid unrelated version
  drift between loaders)

Confirmed all three are compileOnly-appropriate: SereneSeasons's compat
path is gated behind Services.PLATFORM.isModLoaded(...) before the
sereneseasons-importing class is ever touched; Continuity's is gated the
same way; Jade's only entry point (WailaCompat.java) is fully commented
out already (dormant), so its handler classes only need to compile, not
run.

Also adds glitchcore as compileOnly — SereneSeasonsSeasonProvider.java
references glitchcore.config.Config directly (a transitive dependency of
SereneSeasons at compile time, same as neoforge/build.gradle's
curse.maven:glitchcore-955399:8109770), which doesn't resolve merely by
adding serene-seasons itself.

Result: 508 -> 452 compile errors. Remaining errors in these files are
real API-shape mismatches (Jade's IElement/ElementHelper, SereneSeasons's
warmEnoughToRain signature, Continuity's dependency on the vanilla
BakedModel rendering-API rewrite) — out of scope for this task, which
only needed the packages to resolve on the classpath.
…odel API

Ports upstream PR DynamicTreesTeam#1209's fabric/model/ rewrite (targets Minecraft 26.2)
back onto this project's pinned 26.1.2 toolchain, per the plan's Task 5.
Every referenced Fabric API class (FabricBlockStateModel,
FabricBlockStateModelPart, QuadEmitter, CustomUnbakedBlockStateModel,
WrapperBlockStateModel) and vanilla class (BlockStateModel,
BlockStateModelPart, ModelState, ModelBaker, ResolvableModel,
ResolvedModel, SimpleModelWrapper, Material, TextureSlots,
BlockAndTintGetter, BakedQuad) was individually confirmed present at the
exact same package path in this project's pinned fabric-api
(0.145.3+26.1.1) and Minecraft (26.1.2) jars before porting any code, so
despite the reference PR targeting a different Minecraft version, its
model/ files needed no adaptation beyond what's described below.

Restructures model/baked/ (old BakedModel-based approach) into a mix of
updated model/baked/ (still used for procedural fallback baking) and a
new model/blockstate/ package (codec-based blockstate JSON deserialization
via CustomUnbakedBlockStateModel), mirroring NeoForge's existing
BlockStateModel-based architecture. BakedModelBlockPottedSapling.java and
BranchBlockUnbakedModel.java are deleted, superseded by
blockstate/PottedSaplingBlockStateModel.java and
blockstate/UnbakedBranchModel.java respectively.

All actual geometry/quad-generation logic (branch rings, thick-branch
trunks, surface roots, aerial roots) is unchanged — it already lived in
loader-agnostic common/ helper classes (BranchMultiPartHolder, ModelHelper,
ModelConnections, model/parts/*) used by NeoForge's already-working
implementation; every method these ported files call against those
helpers was individually confirmed present with a matching signature
before porting. Only the Fabric-side API adapter code changes.

common/ additions (needed by the above, not previously present):
- New DynamicModelRegistry: records DT's own baked block state models as
  they're baked, so FallingTreeEntityModel's falling-tree geometry can
  find DT's real model even when another mod (e.g. Continuity) has
  wrapped/replaced what the model manager hands back for a state -
  without this, a wrapped model would silently fail DT's
  BlockStateModelWithConnectionData/BlockStateModelWithRadius instanceof
  checks and render the falling tree's trunk invisible.
- FallingTreeEntityModel's three modelSet.get(state) call sites now go
  through DynamicModelRegistry.getOrFallback(state, modelSet) instead.
  Verified :neoforge:compileJava still succeeds after this shared-code
  change (no regression on the already-working loader).

Also fixes compat/continuity/WrappedModelHandler.java and
ContinuityWrappedModelHandler.java for the same rendering-API rewrite,
found during Task 4 and folded into this task's scope: BakedModel ->
BlockStateModel, and CtmBakedModel -> CtmBlockStateModel (Continuity's own
class renamed to match). ContinuityWrappedModelHandler now reaches
Continuity's wrapped model via a VarHandle into Fabric API's
WrapperBlockStateModel#wrapped (protected, no public accessor) - verified
via javap that the field exists with the expected name/type in the pinned
fabric-api jar. This compat path has no live caller yet (WailaCompat-style
dormant code), so this is a compile fix only, not yet exercised at
runtime.

Deliberately NOT ported: PR DynamicTreesTeam#1209 also fixes an unrelated alpha-channel
bug in FallingTreeEntityModel#renderToBuffer's color packing
(`0xFF000000 | ...`). Left out to keep this commit scoped to the API
migration; worth a follow-up look separately.

Result: 452 -> 102 compile errors. Confirmed zero errors remain in any
file this task touches (model/baked, model/blockstate,
DTModelLoadingPlugin, FabricDynamicBlockStateModel,
FallingTreeEntityModelFabric, compat/continuity).
…ent Fabric+vanilla tint-source API

ColorProviderRegistry and BlockRenderLayerMap no longer exist. Mirrors
NeoForge's already-working ClientModEventHandler: block tint sources go
through BlockColorRegistry with the existing common/ TintSources classes,
item tint sources register by id via ItemTintSources.ID_MAPPER, and
render-layer assignment is dropped entirely (NeoForge no longer registers
it either - it's now declared elsewhere). Also fixes two adjacent dead/
renamed calls in the same file: AtlasSourceTypeRegistryImpl -> the current
SpriteSourceRegistry, and LeavesProperties.postInitClient() (removed
method, confirmed via a full-repo grep) is simply dropped.
…easonProvider signature drift, FabricRegistryLoader vanilla-API drift, and access-widener gaps for 26.1.2

- HolderSet gained an abstract isBound() in this MC version; added it to
  every custom HolderSet impl in fabric/.../worldgen/holderset (eagerly
  resolvable sets report true, wrappers delegate to what they wrap).
  Also fixed net.minecraft.Util -> net.minecraft.util.Util and
  ResourceKey.location() -> .identifier() (both renamed/moved).
- FabricInteractionHelper: Branch/TrunkShell/PottedSaplingBlock's
  onDestroyedByPlayer gained an ItemStack toolStack param; pass
  player.getMainHandItem() (matches NeoForgeInteractionHelper's approach -
  vanilla BlockState.onDestroyedByPlayer() is a NeoForge-only extension
  that doesn't exist on Fabric's plain jar). Dropped a stray @OverRide on
  canToolAxeDig, which isn't on ICompatHelper and is dead code (its one
  common/ call site is already commented out).
- FabricCompatHelper.registerSeasonProvider gained a modId param on the
  interface; switch on it like NeoForgeCompatHelper (only Serene Seasons
  has a Fabric dependency wired up - Ecliptic Seasons doesn't).
- FabricRegistryLoader: LootItemConditionType/LootPoolEntryType/
  LootItemFunctionType no longer exist - their registries now hold the
  MapCodec directly (ported NeoForgeRegistryLoader's already-correct
  shape). EntityType.Builder.build(String) -> build(ResourceKey). Dropped
  BlockEntityType's stale 3-arg constructor's trailing null.
- javax.annotation.Nullable -> org.jetbrains.annotations.Nullable in the
  two files still using it (JSR-305 isn't on Fabric's classpath; every
  other file in the codebase already uses the JetBrains annotation).
- Access widener: fixed craftingRemainingItem's descriptor (Item wraps it
  in ItemStackTemplate now), added the missing `accessible field` half for
  BlockEntity.type/blockState (only `mutable` was present, which is a
  no-op on an already-non-final private field), and added entries for
  newly-private/final members surfaced by this MC version: SpriteContents
  .additionalMetadata, MultiPartModel.models, CreakingHeartBlockEntity
  .ticksExisted and its now-private spreadResin() (extendable), Item
  .getCraftingRemainder() (now final - extendable), and BlockEntityType's
  2-arg constructor (now private - accessible).
- CommonEventHandler: ServerWorldEvents -> ServerLevelEvents,
  START_WORLD_TICK -> START_LEVEL_TICK, level.getDayTime() ->
  getDefaultClockTime() (same rename family as Task 6/8's client-side
  fixes); PreparableReloadListener.reload(...) now takes
  (SharedState, Executor, PreparationBarrier, Executor) instead of the
  old 6-arg form - common/'s Resources.ReloadListener already has the
  correct signature, only this Fabric override needed updating; and the
  same onDestroyedByPlayer toolStack-arg fix as FabricInteractionHelper
  (Task 7).
- FabricClientHelper: NativeImage.getPixelRGBA -> getPixel (confirmed via
  common/'s TextureHelper.PixelBuffer, which already uses getPixel for
  the same purpose on both loaders).
- FabricBiomeModifications: BiomeSelectionContext.getBiomeRegistryEntry()
  -> getBiomeHolder(); PlacedFeature.getFeatures() now yields
  Holder<ConfiguredFeature<?,?>> instead of ConfiguredFeature<?,?>
  directly - unwrap with .value() at the call site.
- DendroPotionRecipeHandler: Registry.getHolder(ResourceKey) -> the
  inherited HolderGetter.get(ResourceKey) (same return type).
- SereneSeasonsSeasonProvider: Biome.warmEnoughToRain gained a sea-level
  int parameter; NeoForge's already-working SereneSeasonsProvider shows
  the value to pass (level.getSeaLevel()).
snownee.jade.api.ui.IElement and snownee.jade.impl.ui.ElementHelper don't
exist in the pinned Jade version (26.1.9+fabric) - the UI API was
restructured to snownee.jade.api.ui.Element (implements LayoutElement
directly, so ITooltip's add/append overloads resolve unambiguously again)
and the static factory methods moved to snownee.jade.api.ui.JadeUI
(verified both via javap against the actual pinned jar). Also fixed
CompoundTag.getString(String) returning Optional<String> instead of
String.
Not MC-version drift: BasicRootsBlock already exposes getAerialFamily()
(an (AerialRootsFamily) super.getFamily() cast, used throughout the rest
of the class) precisely for this. The mixin was calling the untyped
getFamily() instead - use the existing accessor.
Species.overrideSaplingReplacementWhenCrouching() does not exist anywhere
on this branch's common/ code (confirmed via full-repo grep) - its only
history is a single commit ("added override_sapling_replacement_when_
crouching to species") that is not an ancestor of develop/26.1.2, so the
Species field/accessor this call depended on was never present here to
begin with; this is not something the MC version bump removed.

NeoForge's VanillaSaplingEventHandler (the working, shipping behavior on
the other loader) never checks crouching at all in its equivalent code
path - it always proceeds to replace/plant regardless. Removing the gate
here makes Fabric match that existing NeoForge behavior exactly, rather
than inventing a guess at what "crouching" should do.
…ssWidener

:fabric:compileJava never validates the AW file's entries against the
real Minecraft classes - it just applies whatever's there for compile-
time widening. :fabric:build's validateAccessWidener task does check,
and failed on 5 entries whose target field/method/class no longer exists
in this Minecraft version (RecipeManager.byType, WeightedStateProvider
.weightedList's descriptor - the field's type changed from
SimpleWeightedRandomList to WeightedList, IntrinsicHolderTagsProvider
.IntrinsicTagAppender, SpriteContents.metadata(), SpriteSources.register).

Confirmed via a full-repo grep that none of the 5 have any live caller in
common/ or fabric/ (the field even the closest one, weightedList, is
referenced by - is only used from a neoforge/-only datagen file, which
doesn't consume this Fabric-only AW file at all). Rather than guess a
plausible descriptor fix for entries nothing depends on, removed them.
…od init

Runtime crash found during Task 9's server smoke test: FabricRegistryLoader
.setup() called DendroPotionRecipeHandler.getAllDendroRecipes() directly
from DynamicTreesFabric.onInitialize() (the main Fabric entrypoint, which
runs very early). That method builds ItemStacks from vanilla item Holders
(Items.CHARCOAL etc.), which aren't bound yet at that point -
NullPointerException: "Components not bound yet" in Holder$Reference
.components().

NeoForge never had this problem because its own DendroPotionRecipeHandler
is only invoked later, from RegisterBrewingRecipesEvent. Fabric already
has an equivalent lazy trigger: MixinPotionBrewing injects into vanilla
PotionBrewing's instance methods and calls the same handler, which runs
only when brewing is actually checked at runtime (well after registries
are bound). getAllDendroRecipes() already caches its result on first
call, so removing the premature eager call is enough - confirmed via a
clean server startup afterward.
…rrs flag

Found during Task 9's client smoke test: every single Dynamic Trees block
(saplings, branches, leaves, roots - ~3000 warnings, not a handful) logged
"Missing model for variant" on launch, because none of common/'s datagen
output (src/generated/resources - blockstates, models, tags, everything
DTExtraModelGenerator etc. produce) was ever making it into fabric's
built jar. Not a Task 1-8 regression: this gap predates the whole plan.

Root cause: the shared commonResources Gradle configuration (defined in
common/build.gradle, consumed by both loaders via multiloader-loader
.gradle's processResources block) only publishes sourceSets.main
.resources - never src/generated/resources. neoforge/build.gradle already
works around this with its own extra
"sourceSets.main.resources { srcDir project(':common').file
('src/generated/resources') }" line; fabric/build.gradle never had the
equivalent. Added it.

Also removes the temporary -Xmaxerrs compiler flag (added early in this
port to see past javac's 100-error display cap) now that :fabric:build is
clean, per this task's own instruction.
…es at runtime)

Two compounding bugs found via the client smoke test - the branches and
roots of every tree rendered with no texture:

1. DTModelLoadingPlugin.registerModelTypes() (which calls
   CustomUnbakedBlockStateModel.register(...) for each of DT's 7 custom
   model types - branch, roots, surface_root, creaking_heart, etc.) was
   defined but never called from anywhere. DynamicTreesFabricClient
   .registerModelLoaders() only called ModelLoadingPlugin.register(new
   DTModelLoadingPlugin()), so Fabric's model-loading registry never
   learned what "dynamictrees:branch" et al. mean - every custom-typed
   blockstate variant failed to resolve to any model.

2. Separately, decompiling Fabric's own CustomUnbakedBlockStateModelRegistry
   (fabric-model-loading-api-v1) confirmed it dispatches custom model types
   on a "fabric:type" key, not NeoForge's "type" key. common/'s generated
   blockstate JSONs (produced by a NeoForge-only datagen tool - see
   BasicLoaderBuilder.java) only ever wrote "type", so even with issue 1
   fixed, Fabric's codec had nothing to key its dispatch on. Patched the
   31 already-generated blockstate JSON files to carry both keys side by
   side, matching what DTModelLoadingPlugin's own javadoc already assumed
   was true.

Confirmed fixed via the actual client smoke test: sapling/tree/branch
textures now render, trees can be chopped, and bonemeal growth produces
correctly rendered branches. Server smoke test and :neoforge:build both
still pass.
@joaovictor-martins joaovictor-martins changed the title Develop/26.1.2 fabric: complete Minecraft 26.1.2 Fabric port Aug 21, 2026
@joaovictor-martins joaovictor-martins changed the title fabric: complete Minecraft 26.1.2 Fabric port fabric: complete Minecraft 26.1.2 Fabric port (vibecoded) Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant