Retromod 1.3.0-snapshot.3 (NeoForge 26.2)
Curse Maven Snippet
What's new
Third snapshot of the 1.3.0 line (in development). Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
In-game verified on 26.2 Fabric (Prism, 2026-07-24): snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from retromod-input/ (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new Minecraft.screen hop (FastQuit carries both rewritten write sites), the Options.hideGui bridge and ON_OSX bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's MinecraftMixin @Shadow private Window handle no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
Fixed
The offline
transformcommand now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity withbatch/runtime).transformhas two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves,Identifierctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (class_8710,class_310, ...) and crashed on a 26.1+ host (NoClassDefFoundError, mixin@Injecttargets left asclass_XXXX). Found in-game (AppleSkin, whose version detection fell through). Both paths now shareregister26xTargetMappings; a Fabric mod through the fallback is fully remapped (verified:class_8710/allclass_/method_residue -> 0, "member mappings (10178)" applied). Tested (TransformFallbackParityTest).Nested intermediary class names now remap whole (
net/minecraft/class_X$class_Y), no moreOuter$class_Yhybrid. The tsv carries the combined entry (class_327$class_6415 -> Font$DisplayMode), but the FQ pattern inremapString/remapDescriptorstopped at$and mapped only the outer, leaving aFont$class_6415hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (.../class_\d+($class_\d+)+) now matches and looks up the whole nested name first; anonymous$1inners (which keep their index) and plain top-level names are unaffected. Fixes the documentedGlStateManager$class_4535gap. Tested (NestedIntermediaryClassRemapTest).Fabric networking
S2C/C2S->Clientbound/Serverboundpattern rename produced the WRONG spelling. The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naivename.replace("S2C", "Clientbound"), turningplayS2CintoplayClientbound. But 26.1's actual name moves the direction word to the FRONT:playS2C->clientboundPlay,configurationS2C->clientboundConfiguration(and theC2S->serverbound...mirror). So a networking mod that fell through diedNoSuchMethodError: PayloadTypeRegistry.playClientbound()at init. The rule now reconstructs the real name (clientbound+ the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (PatternHeuristicsNetworkingTest).COMPUTE_FRAMESno longer over-generalizes aThrowable+non-exception merge, fixing aVerifyErroron any modded try/catch that reuses a local across the try body and its catch. When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back tocommonSuperFallback. A prior fix (#94) made that returnThrowablewhenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging aThrowablewith a plain non-exception: the real common supertype isObject, and typing itThrowablemakes a predecessor that provides the non-exception fail the frame check. jade'sCommonProxy.lambda$loadComplete$5(a try body'sgotoand the catch's rethrow both fall into the same trailingreturn, mergingModMetadatawithThrowable) diedVerifyError: Inconsistent stackmap framesat load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge toThrowable(#94 preserved), a Throwable with a provable non-exception isObject(jade fixed), and an unresolvable operand is disambiguated by Java's*Exception/*Errornaming convention. Verified in-game (the jadeVerifyErrorclears). Tested (CommonSuperFallbackTest).Vec3.<init>(org.joml.Vector3f)widened to theVector3fcinterface (26.x joml concrete->interface modernization), in BOTH the direct-call and theVec3::newmethod-reference form. 26.x replaced the concrete-typedVec3(Vector3f)ctor withVec3(Vector3fc)(the interfaceVector3fimplements), so a 1.21.x mod constructing aVec3from a joml vector diesNoSuchMethodError. A converting redirect widens the directinvokespecial(no cast:Vector3fis-aVector3fc). Crucially, the same skew appears as a constructor REFERENCE (Vec3::newin a codecVECTOR3F.map(Vec3::new, ...)), which compiles to aninvokedynamicwhose impl handle the direct-call redirect never sees;visitInvokeDynamicInsnnow applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to thoseH_NEWINVOKESPECIAL/H_INVOKE*handles too. jade'sEntityAccessorImpl$SyncData.<clinit>died on the reference form (reached fromCommonProxy.onInitializevia a packetTYPEstatic init); both forms now widen. Verified in-game (the jade Vec3 crash clears; itsmainentrypoint fully initializes). Tested (Corpus26xDescriptorAdaptationTest).The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case). The 26.1 common shim (
Common_1_21_11_to_26_1_ClassMoves.register: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. ButfindShimChain(fabric, 1.21.1, 26.x)returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade'sVec3widening silently didn't apply throughbatch. The offlinebatch/transformauxiliary registration now callsCommon.registerunconditionally under theisUnobfuscatedTargetgate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.Util.backgroundExecutor()/ioPool()/nonCriticalIoPool()return-type change (26.xTracingExecutorwrapper). These returnedjava.util.concurrent.ExecutorServiceand now returnnet.minecraft.TracingExecutor(a record wrapping the service), so a mod calling them diesNoSuchMethodErrorat init (ModMenu's update checker, found in-game continuing the jade launch). A newregisterReturnUnwrapRedirectretargets the call to theTracingExecutor-returning form and appendsTracingExecutor.service()to recover theExecutorServicethe caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (Util.backgroundExecutor()->TracingExecutor+.service()). Tested (Corpus26xDescriptorAdaptationTest).Removed
LazyLoadedValuenow redirects tojava.util.function.Supplier(Mojang's actual replacement), fixing a mixin@Accessoron a vanilla field whose type changed. Mojang deletednet.minecraft.util.LazyLoadedValueand replaced its usages with a plainSupplier(e.g.InputConstants.Key.displayNameis nowSupplier<Component>). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin@Accessorsetter typed(LazyLoadedValue), no longer matching the now-Supplierfield, so Mixin silently skipped the accessor and the mod diedAbstractMethodError(jade'sKeyAccess/InputConstants$Key.setDisplayName, found in-game on 26.2 Fabric). The redirect now maps the TYPE toSupplierand rewritesnew LazyLoadedValue(supplier)to the polyfill's static factoryLazyLoadedValue.of(Supplier):Supplier(a memoizing wrapper that IS-ASupplier, so the accessor and its call sites all becomeSupplierconsistently, no cross-class ordering); the redirected.get()usesINVOKEINTERFACE(Supplieris now a known interface). The old type's only API wasget()(==Supplier.get()), so nothing is lost. Verified in-game (jade'sKeyAccessAbstractMethodErrorclears). Tested (TransformPolyfillRegressionTest).KeyMapping(keybind) constructor bridges for the 26.x category refactor (RetroKeyMapping). The keybind category changed from aStringtranslation key to aKeyMapping.Categoryrecord, so the oldnew KeyMapping(name, [type,] code, categoryString)constructors are gone and ANY mod that adds a keybind diesNoSuchMethodErrorat client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with anInputConstants.Type) and 3-arg (KEYSYM default) forms tocom.retromod.polyfill.minecraft.RetroKeyMapping, a reflective (Minecraft-free), fail-safe factory that resolves the category string to aCategory(a vanillakey.categories.*string maps to the builtin constant; a mod string registers a cachedretromod:category; else falls back toMISCso the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends aCHECKCASTsince the factory returnsObject. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (Corpus26xDescriptorAdaptationTest: the ctor-to-factory rewrite + the factory's fail-safe).SimpleJsonResourceReloadListener(Gson, String)bridge for the 1.21.5 resource-reload refactor (synthesized superclass). The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON diesNoSuchMethodErroron thesuper(gson, dir)call at init (jade'sThemeHelper, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesizedRetroSimpleJsonReloadListener(itextends26.x'sSimplePreparableReloadListenerand re-implements the old scan by delegatingprepare()to the reflectiveRetroReloadScan:FileToIdConverter.json(dir).listMatchingResources(rm)+GsonHelper.fromJson-> theMap<Identifier, JsonElement>the subclass'apply(...)still expects) and rewritessuper(...)to it. The synthetic declares both the concreteprepare()Map(a subclass'super.prepare()and its own override are keyed on that concrete return) and the erasedprepare()Objectbridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so synthetic embedding now runs on Fabric too (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade'sThemeHelper, which crashed on the super ctor, now loads. Tested (Corpus26xDescriptorAdaptationTest: the rebase + super-ctor rewrite + the scan helper's fail-safe).MinMaxBoundsintermediary-map skew (criterion->predicatespackage move). The bundled tsv mappedclass_2096/$Ints/$Doublestonet/minecraft/advancements/criterion/MinMaxBounds*, but 26.x moved the class tonet/minecraft/advancements/predicates/, so a mod referencing it (jade's allowed-version predicate) diedNoClassDefFoundErrorfor the stale path. Corrected the three tsv entries (verified:criterion/MinMaxBoundsabsent,predicates/MinMaxBoundspresent on 26.2; jade cleared it in-game). Tested (NestedIntermediaryClassRemapTest).PoseStack.mulPose(Quaternionf)/(Matrix4f)widened to the joml INTERFACE (Quaternionfc/Matrix4fc). Same concrete->interface modernization asVec3/addVertex: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them diesNoSuchMethodError. The value on the stack already implements the interface, so just widen the descriptor (no cast).PoseStack.mulPoseis ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (Corpus26xDescriptorAdaptationTest).Removed
RenderSystemshader setters neutralized (setShader/setShaderColor/setShaderTexture). The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these diesNoSuchMethodErrorat RENDER time, a game crash when the mod draws. They're now neutralized like the other removedRenderSystemstate setters (enableBlend/blendFunc/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (setShader26 mods,setShaderTexture18). Tested (RemovedRenderStateNeutralizeTest).Minecraft.screenbridged to 26.2'sGuiaccessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write). 26.2 moved the publicMinecraft.screenfield toGui(private Screen screenwith publicscreen()/setScreen(Screen), reached through the public finalMinecraft.guifield;Minecraft.setScreenAndShowis itself justgui.setScreen+renderFrame; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives inMc26_1To26_2CoreMoves). A new transformer mechanism,registerFieldHopAccessor, rewritesGETFIELD Minecraft.screentoGETFIELD Minecraft.gui+INVOKEVIRTUAL Gui.screen()andPUTFIELDto aSWAP-splicedGui.setScreen(...): pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the realsetScreen, i.e. modern close/init semantics instead of a silent field swap; net-better than theNoSuchFieldErrorcrash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/setScreensplices (javap), zero residualMinecraft.screenfieldrefs. Tested (ClientStructureBridge26xTest).Options.hideGuibridged to 26.2'sHud(12 corpus mods read, 1 writes). 26.2 deleted the publicOptions.hideGuiboolean; the F1 state moved toHud.isHidden(private, publicisHidden()/toggle(), NO absolute setter; vanilla readsmc.gui.hud.isHidden(), andOptions.keyToggleGuiis now consumed byGui.handleKeybinds->hud.toggle()). TheOptionsreceiver can't reach that state, so a second new mechanism,registerFieldStaticBridge(opcode-aware, unlike the opcode-blind field-to-method form ofregisterFieldRedirect), turnsGETFIELDintoINVOKESTATIC RetroClientEnv.isHideGui(Object)ZandPUTFIELDintosetHideGui(Object,Z)V, consuming the receiver as an ignored argument.RetroClientEnv(embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches theHudinstance (Minecraft.gui/Gui.hudare public final, so it is process-stable) and expresses a write as the conditional toggleif (isHidden() != v) toggle(), the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residualhideGuirefs, per-mod embedded copy present). Tested (ClientStructureBridge26xTest).Minecraft.ON_OSXsupplied by an embedded polyfill (10 corpus mods, 45 sites, all reads). Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successorInputQuirks.ON_OSXisprivate static final, unreachable by any field redirect. TheGETSTATICbecomesINVOKESTATIC RetroClientEnv.isOsx()Z, which recomputes the value the way vanilla'sUtil.getPlatform()does (os.namecontains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (KeyHelperbridges, zero residualON_OSXrefs). Tested (ClientStructureBridge26xTest).The 26.2 Gui->Hud member family bridged: all 17 moved members, including
getChat(). 26.2 moved 17 publicGuimembers onto the newHud(reached via the public finalGui.hud):getChat(how a client mod prints chat messages),getFont,getTabList,getBossOverlay,getDebugOverlay,getSpectatorGui, the whole title API (setTitle/setSubtitle/setTimes/clearTitles/resetTitleTimes),setOverlayMessage,setNowPlaying,getGuiTicks,clearCache,onDisconnected(full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (GuiToHudHop:static ret m(Gui g, args...) { return g.hud.m(args...); }) takes the receiver as arg 0, so the transformer's auto-devirtualize rewritesINVOKEVIRTUAL Gui.m(...)toINVOKESTATICwith the stack untouched; that shape is also handler-preserving for@Redirect/@WrapOperation/@WrapWithConditionmixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin@At-target rewrite composes the receiver-prepended descriptor so selectors keep matching.getMobEffectSprite(static on both sides) is a plain owner move. This retires the earlier scan backlog'ssetOverlayMessagehandler-mirror hazard. Tested (ClientStructureBridge26xTest).33 tag constants deleted at 26.2 recovered via
BlockItemTagsaccessors. 26.2 introducedBlockItemTags(paired block+item tag ids) and deleted the corresponding per-registry constants: 17 fromBlockTags(the wood-type*_LOGSset,LOGS_THAT_BURN,SAPLINGS, the*_ORESset,SMELTS_TO_GLASS, ...) and 16 fromItemTags(DOORS,SLABS,STAIRS,FLOWERS,CHAINS,LANTERNS, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deletedGETSTATICbecomesGETSTATIC BlockItemTags.X+INVOKEVIRTUAL BlockItemTagId.block()/.item()via the existing static-field-accessor mechanism (theColorCollectionprecedent). Corpus-scan hit: Darker Depths'RotatedPillarBlockMixinreadsBlockTags.LOGS_THAT_BURNin its handler body. Tested (ClientStructureBridge26xTest).LightTexturebridged (9 corpus mods; 26.1 render rewrite). Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE toutil/LightCoordsUtil(pack(II)I/block(I)I/sky(I)I, identical names/descriptors/bit layout), texture management went torenderer/Lightmap, which kept staticgetBrightness(DimensionType,I)Fwith the same descriptor. Fix: class moveLightTexture->Lightmap(healsgetBrightnessby itself) + method redirects for the coord statics +GameRenderer.lightTexture()bridged to a reflective helper returning the privateLightmapinstance (receiver-as-arg0; null fail-safe) +turnOn/turnOffLightLayerneutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them throughlightTexture(), so the chain stays type-correct and inert). Tested (RenderApiBridge26xTest).RenderTypeclass move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).RenderTypemoved torenderer/rendertype/RenderTypeon both 26.x jars; the ubiquitous static getters moved torendertype/RenderTypeswith a cull-naming FLIP (oldentityCutout= culled ->entityCutoutCull; oldentityCutoutNoCull->entityCutout), bridged with owner+desc-scoped redirects (entitySolid/entityCutoutx2/entityTranslucentx2/text); the block-layer getters (solid/cutout/cutoutMipped/translucent/tripwire, 9-15 mods each) have NO RenderType successor (chunk layers became theChunkSectionLayerenum) and are approximated by the surviving*MovingBlocktokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: theCompositeState/RenderStateShardbuilder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (RenderApiBridge26xTest).ItemBlockRenderTypesbridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite). The static block-to-RenderType table was deleted: the layer decision became per-quad model data (ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}). All five statics (getChunkRenderType,getRenderType(BlockState,Z),getMovingBlockRenderType,getRenderLayer(FluidState),getRenderType(ItemStack,Z)) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (collectParts->getQuads-> the quad material'slayer(), handling both the 26.1spriteInfoand 26.2materialInfoshapes) and returns the survivingRenderTypes.*MovingBlocktokens, the SAME tokens the RenderType getter redirects above use, so mod-side==comparisons stay consistent. Two verified traps encoded: the model-set probe order (getBlockStateModelSetFIRST;getBlockModelSetexists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased(Object,Z)overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (RenderApiBridge26xTest).PlayerSkinrestructure bridged (7 corpus mods; 26.1). The record movedclient/resources->world/entity/playerwith itsModelenum promoted toPlayerModelType(constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped:texture()/capeTexture()/elytraTexture()returningResourceLocationbecamebody()/cape()/elytra()returning theClientAsset$TextureINTERFACE; bridged by rename-capable return-unwrap converting redirects appendingtexturePath(), which required making the unwrap mechanism interface-aware (anINVOKEVIRTUALon an interface is anIncompatibleClassChangeError;registerReturnUnwrapRedirectgainedunwrapItf). Tested (ClientStructureBridge26xTest).ReceivingLevelScreen->LevelLoadingScreen(6 corpus mods; 26.1): class move; mods mostlyinstanceofit for screen detection.ItemInteractionResult(class_9062) merged-class bridge (6 corpus mods). The 1.20.5-1.21.1 sided item-use result was merged back intoInteractionResultat 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods dieNoClassDefFoundError. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated byPASS_TO_DEFAULT_BLOCK_INTERACTIONat 58, which 1.21.2 literally renamedTRY_WITH_EMPTY_HAND;CONSUME_PARTIAL->CONSUME;SKIP_DEFAULT_BLOCK_INTERACTION->FAIL, the closest no-swing chain-stop),sidedSuccess(Z)-> plainSUCCESS(the sided split is gone, vanilla's own migration),consumesAction()-> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), andresult()-> identity (post-merge the receiver IS the result). Tested (RenderApiBridge26xTest).GlStateManager$class_4534/4535hybrid spellings mapped + the 26.2 blend-factor teardown. Intermediary keepscom.mojang.blaze3dOUTER names plain but its inners gotclass_Nids, so distributed Fabric mods literally containGlStateManager$class_4535-style names (10 corpus mods) that neither the intermediary map (keyednet/minecraft/class_...) nor the existing inner-move entries matched: mapped to the promoted 26.1SourceFactor/DestFactor. Those promoted enums are then DELETED at 26.2: theirGETSTATICs become pushed nulls (static-field nuller) feeding the enumblendFunc/blendFuncSeparateoverloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead ofNoClassDefFoundError. Tested (ClientStructureBridge26xTest.blendFactorTeardown26_2).TextureSheetParticle->SingleQuadParticlerebase (6 corpus mods; 26.1 particle rework). The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (quadSize/rCol/sprite/...), the same protected sprite helpers, and a non-abstractgetGroup(), so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailingTextureAtlasSprite, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction viasetSprite/SpriteSet, exactly the old flow);pickSprite(SpriteSet)did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protectedspritefield; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the oldrender(VertexConsumer,...)loads but its override is never called (the contract becameextract(QuadParticleRenderState,...)): invisible, not crashed. Tested (RenderApiBridge26xTest).GUI 2D-transform migration Phase 3: the
doubletranslate overload andmulPoserotation.translate(DDD)Vmigrates exactly (drop z withPOP2, then aD2F+DUP_X2/POP/SWAPjuggle converts y and x; BasicVerifier-proven).mulPose(Quaternionf[c])becomes the 2Drotate(angle)via the generatedRetroQuat2D.zAngle(2*atan2(z,w): exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (Gui2DTransformMigrationTest).AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full
RetromodTransformer.transformClass. The formerHybridCompilerprimary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects likeVec3, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plaintransformdiedNoSuchFieldErrorwhen prepped viaaot/batch --aot(adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; thetransformClassSimple-> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standaloneaotrun of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).The transform command's all-shims fallback now gates by target version (pitfall-9, offline). The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's
Gui.screen(), the criterion class moves,getMainCamera) to mods where the ORIGINAL member still exists and worked, turning a working read intoNoSuchMethodError(adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (TransformFallbackParityTest.fallbackAllShimsGatedByTarget).RetroClientEnvno longer latches "unresolvable" during the client's own constructor. 26.2'sMinecraft.<init>publishes the singleton early (insn ~133) but assignsguimuch later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there sawmc != nullbutgui == null, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (transformClass's early exit andRetromodClassVisitor.visitMethod's wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (ClientStructureBridge26xTest.hopOnlyRegistrationTransforms).Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed). The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but
Mc26_1To26_2CoreMoveswas still chain-only, so abatch/transformto 26.2 silently missed EVERY 26.2 core move (EntityType->EntityTypesconstants,setScreen->setScreenAndShow, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated!mcVersionExceeds("26.2", target), idempotent). Tested (TransformFallbackParityTest.coreMoves262RegisteredFor262Target).GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack
translate/scale), and the whole migration now reaches Fabric. 26.x moved GUI rendering off the 3DPoseStackonto a 2Dorg.joml.Matrix3x2fStack(GuiGraphics.pose()returns the 2D stack), so a 1.21.x mod'sguiGraphics.pose().pushPose()/translate(x,y,z)/scale(x,y,z)links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-argpushPose/popPosepeephole): Phase 1 migrates the immediate arg-carrying float ops (gg.pose().translate(x,y,z)-> 2Dtranslate(x,y), drop z, pop the fluent result), and Phase 2 migrates the STORED-stack idiom most GUI code uses (var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();) by retyping the local. Both use an ASMSourceInterpreterdataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a singleGuiGraphics.pose()(Phase 1) or a local whose SOLE store is onepose()and whose EVERY load feeds a migratable op (Phase 2). A genuine 3DPoseStack- a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and thedouble/mulPoseoverloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASMBasicVerifierwith 0 errors; the "3DPoseStackuntouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (Gui2DTransformMigrationTest).#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken). A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g.
[1.19,1.20.1]on a 1.20.1 host, the shape ofpacketfixer/CrashAssistant/cosmeticcorpsecompatand the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, soneedsTransformationskips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges ([1.19,)) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored:[1.19,1.20.1)on 1.20.1 still transforms. Tested (ModVersionRangeContainmentTest, 5 cases including the report shapes).#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed. The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was
class_11890=world/entity/Avatar, the direct superclass of its target-dummy entity (NoClassDefFoundErrorat Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (AtlasManager->model/sprite/,ParticleGroupRenderState/CameraRenderState->state/level/). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern@Attarget selectors (Lowner;name:Ldesc;) were parsed as method names and left unmapped; (2)@Slice(from/to=@At(...))inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped@ModifyArgsand@ModifyConstant(now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept itsmethod_Nname and diedLambdaConversionException(a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (MixinSelectorRemapRegressionTest). In-game verify on a 26.1.2 host still pending.#162 (ENGRAM):
EntityType$Builder.build(String)bridged on pre-26.1 hosts. 1.21.2 flippedmethod_5905's descriptor from(String)to(ResourceKey); a 1.20.1-1.21.1 Fabric mod registering an entity diesNoSuchMethodErrorat<clinit>on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probingPre1_21_2EntityTypeBuildBridge(registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id)), all shape-discovered so intermediary id drift can't break it). Tested (Pre1_21_2EntityTypeBuildBridgeTest).#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken) (entry above under the detector fix).
#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy
@Modupgrade now matches the post-remap annotation spelling. Pass ordering can remapLnet/minecraftforge/fml/common/Mod;to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped@Modclasses untouched. Tested (Forge1122LifecycleTest).#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails. The old bare class redirects (
NetworkRegistry->PayloadRegistrar) were actively harmful: NeoForge's registrar never hadnewSimpleChannel, so every MCreator-style mod diedNoSuchMethodErrorin<clinit>. The whole surface now routes onto the embeddedNetworkShim(newSimpleChanneldescriptor-erased; themessageBuilder(Class,int,NetworkDirection)chain incl. theconsumerMainThreadidiom;registerMessageincl. theOptionaloverload;send(PacketTarget,msg)erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together withNetworkHooks/ITeleporter/LivingTickEventfrom the same family). Tested (SimpleChannelBridgeTest).SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries). The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/
<init>filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going toNoSuchXErroron that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships asscripts/harvest-srg-union.py(reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (f_62138_runtimebiomeSource->runtimeBiomeSource,m_47831_getTextureLocation->build,m_142469_createRenamer->getBoundingBox) but did NOT change them (left for maintainer review). Tested (SrgUnionCoverageTest).
Infrastructure
- CurseForge publish fixed for CF's new mandatory "Environment" version group. CurseForge started requiring every uploaded file to also carry a version from the Environment group (Client/Server), rejecting uploads that omit it with
errorCode 1021("You must select at least one version from the environment group of versions"); the publish script only sent[mcVersion, loader], so every jar failed.scripts/publish-curseforge.pynow resolves the Client and Server env ids (by theenvironmentversion-type, falling back to name-matching, with aCF_ENV_IDSpin as an escape hatch) and appends both to every upload payload. Validated against a mocked CF API (payload becomes[mc, loader, client, server]; loader resolution unaffected). Note: the failing run also used release-typereleasefor a-snapshot;betais the conventional CurseForge type for snapshots (a run-time choice, not a script bug). - Compat-report PRs can no longer conflict with each other: the DB is now one file per entry. Every auto-generated report PR used to append to the shared
docs/_data/compatdb.yml, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY,docs/_data/compatdb/, with oneissue-<N>.ymlper report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), andcompatdb.htmlcollects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
Research (documented, not yet shipped)
- Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan in
scripts/research/render-redesign-bridge-plans.md(7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work:BakedModel(21 mods; per-mod synthesizedLegacyBakedModelinterface over the 26.xBlockStateModel/BlockStateModelPartpair, per-host-version bodies),BufferUploader(13; embedded GpuDevice/RenderPass immediate-draw polyfill),ItemBlockRenderTypes(12; reflective per-state layer lookup with the verified model-set probe order),BlockRenderDispatcher/BlockModelShaper(13/12) andModelResourceLocation(7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.xBakedQuadis a different data structure), the FRAPI renderer family, and theCompositeState/RenderStateShardcustom-render-type world. - 87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural. Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual
net.minecraft/com.mojangbreaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (BakedModel25 mods,BufferUploader21,LightTexture19,ItemBlockRenderTypes16,BlockModelShaper/BlockRenderDispatcher/ModelResourceLocation/RenderType$CompositeState/RenderStateShard*/DimensionSpecialEffects9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (GuiGraphics->GuiGraphicsExtractorwherepose()now returns aMatrix3x2fStack, sopushPose/popPose/translate/scale/textdon't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (Minecraft.screenfield 45 mods;Minecraft.ON_OSX26;Options.hideGui20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (themulPosewidening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the publicGui.screen()/setScreenaccessors andHud.isHidden()/toggle()), and shipped in this snapshot; see theMinecraft.screen/Options.hideGui/Minecraft.ON_OSXentries above.
This mod has no additional files

