promotional bannermobile promotional banner

Retromod

Retromod Is a Minecraft mod that allows you to play older mods on newer versions.
Back to Files

Retromod 1.3.0-snapshot.3 (NeoForge 26.2)

File nameretromod-1.3.0-snapshot.3+26.2.jar
Uploader
BownluxBownlux
Uploaded
Jul 25, 2026
Downloads
53
Size
3.8 MB
Mod Loaders
NeoForge
File ID
8509755
Type
R
Release
Supported game versions
  • 26.2

Curse Maven Snippet

NeoForge

implementation "curse.maven:retromod-1532616:8509755"

Learn more about Curse Maven

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 transform command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with batch/runtime). transform has 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, Identifier ctor 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 @Inject targets left as class_XXXX). Found in-game (AppleSkin, whose version detection fell through). Both paths now share register26xTargetMappings; a Fabric mod through the fallback is fully remapped (verified: class_8710/all class_/method_ residue -> 0, "member mappings (10178)" applied). Tested (TransformFallbackParityTest).

  • Nested intermediary class names now remap whole (net/minecraft/class_X$class_Y), no more Outer$class_Y hybrid. The tsv carries the combined entry (class_327$class_6415 -> Font$DisplayMode), but the FQ pattern in remapString/remapDescriptor stopped at $ and mapped only the outer, leaving a Font$class_6415 hybrid 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 $1 inners (which keep their index) and plain top-level names are unaffected. Fixes the documented GlStateManager$class_4535 gap. Tested (NestedIntermediaryClassRemapTest).

  • Fabric networking S2C/C2S -> Clientbound/Serverbound pattern 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 naive name.replace("S2C", "Clientbound"), turning playS2C into playClientbound. But 26.1's actual name moves the direction word to the FRONT: playS2C -> clientboundPlay, configurationS2C -> clientboundConfiguration (and the C2S -> serverbound... mirror). So a networking mod that fell through died NoSuchMethodError: 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_FRAMES no longer over-generalizes a Throwable+non-exception merge, fixing a VerifyError on 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 to commonSuperFallback. A prior fix (#94) made that return Throwable whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a Throwable with a plain non-exception: the real common supertype is Object, and typing it Throwable makes a predecessor that provides the non-exception fail the frame check. jade's CommonProxy.lambda$loadComplete$5 (a try body's goto and the catch's rethrow both fall into the same trailing return, merging ModMetadata with Throwable) died VerifyError: Inconsistent stackmap frames at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to Throwable (#94 preserved), a Throwable with a provable non-exception is Object (jade fixed), and an unresolvable operand is disambiguated by Java's *Exception/*Error naming convention. Verified in-game (the jade VerifyError clears). Tested (CommonSuperFallbackTest).

  • Vec3.<init>(org.joml.Vector3f) widened to the Vector3fc interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the Vec3::new method-reference form. 26.x replaced the concrete-typed Vec3(Vector3f) ctor with Vec3(Vector3fc) (the interface Vector3f implements), so a 1.21.x mod constructing a Vec3 from a joml vector dies NoSuchMethodError. A converting redirect widens the direct invokespecial (no cast: Vector3f is-a Vector3fc). Crucially, the same skew appears as a constructor REFERENCE (Vec3::new in a codec VECTOR3F.map(Vec3::new, ...)), which compiles to an invokedynamic whose impl handle the direct-call redirect never sees; visitInvokeDynamicInsn now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those H_NEWINVOKESPECIAL/H_INVOKE* handles too. jade's EntityAccessorImpl$SyncData.<clinit> died on the reference form (reached from CommonProxy.onInitialize via a packet TYPE static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its main entrypoint 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. But findShimChain(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's Vec3 widening silently didn't apply through batch. The offline batch/transform auxiliary registration now calls Common.register unconditionally under the isUnobfuscatedTarget gate (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.x TracingExecutor wrapper). These returned java.util.concurrent.ExecutorService and now return net.minecraft.TracingExecutor (a record wrapping the service), so a mod calling them dies NoSuchMethodError at init (ModMenu's update checker, found in-game continuing the jade launch). A new registerReturnUnwrapRedirect retargets the call to the TracingExecutor-returning form and appends TracingExecutor.service() to recover the ExecutorService the 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 LazyLoadedValue now redirects to java.util.function.Supplier (Mojang's actual replacement), fixing a mixin @Accessor on a vanilla field whose type changed. Mojang deleted net.minecraft.util.LazyLoadedValue and replaced its usages with a plain Supplier (e.g. InputConstants.Key.displayName is now Supplier<Component>). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin @Accessor setter typed (LazyLoadedValue), no longer matching the now-Supplier field, so Mixin silently skipped the accessor and the mod died AbstractMethodError (jade's KeyAccess/InputConstants$Key.setDisplayName, found in-game on 26.2 Fabric). The redirect now maps the TYPE to Supplier and rewrites new LazyLoadedValue(supplier) to the polyfill's static factory LazyLoadedValue.of(Supplier):Supplier (a memoizing wrapper that IS-A Supplier, so the accessor and its call sites all become Supplier consistently, no cross-class ordering); the redirected .get() uses INVOKEINTERFACE (Supplier is now a known interface). The old type's only API was get() (== Supplier.get()), so nothing is lost. Verified in-game (jade's KeyAccess AbstractMethodError clears). Tested (TransformPolyfillRegressionTest).

  • KeyMapping (keybind) constructor bridges for the 26.x category refactor (RetroKeyMapping). The keybind category changed from a String translation key to a KeyMapping.Category record, so the old new KeyMapping(name, [type,] code, categoryString) constructors are gone and ANY mod that adds a keybind dies NoSuchMethodError at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an InputConstants.Type) and 3-arg (KEYSYM default) forms to com.retromod.polyfill.minecraft.RetroKeyMapping, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a Category (a vanilla key.categories.* string maps to the builtin constant; a mod string registers a cached retromod: category; else falls back to MISC so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a CHECKCAST since the factory returns Object. 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 dies NoSuchMethodError on the super(gson, dir) call at init (jade's ThemeHelper, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized RetroSimpleJsonReloadListener (it extends 26.x's SimplePreparableReloadListener and re-implements the old scan by delegating prepare() to the reflective RetroReloadScan: FileToIdConverter.json(dir).listMatchingResources(rm) + GsonHelper.fromJson -> the Map<Identifier, JsonElement> the subclass' apply(...) still expects) and rewrites super(...) to it. The synthetic declares both the concrete prepare()Map (a subclass' super.prepare() and its own override are keyed on that concrete return) and the erased prepare()Object bridge. 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's ThemeHelper, which crashed on the super ctor, now loads. Tested (Corpus26xDescriptorAdaptationTest: the rebase + super-ctor rewrite + the scan helper's fail-safe).

  • MinMaxBounds intermediary-map skew (criterion -> predicates package move). The bundled tsv mapped class_2096/$Ints/$Doubles to net/minecraft/advancements/criterion/MinMaxBounds*, but 26.x moved the class to net/minecraft/advancements/predicates/, so a mod referencing it (jade's allowed-version predicate) died NoClassDefFoundError for the stale path. Corrected the three tsv entries (verified: criterion/MinMaxBounds absent, predicates/MinMaxBounds present 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 as Vec3/addVertex: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies NoSuchMethodError. The value on the stack already implements the interface, so just widen the descriptor (no cast). PoseStack.mulPose is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (Corpus26xDescriptorAdaptationTest).

  • Removed RenderSystem shader 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 dies NoSuchMethodError at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed RenderSystem state 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 (setShader 26 mods, setShaderTexture 18). Tested (RemovedRenderStateNeutralizeTest).

  • Minecraft.screen bridged to 26.2's Gui accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write). 26.2 moved the public Minecraft.screen field to Gui (private Screen screen with public screen()/setScreen(Screen), reached through the public final Minecraft.gui field; Minecraft.setScreenAndShow is itself just gui.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 in Mc26_1To26_2CoreMoves). A new transformer mechanism, registerFieldHopAccessor, rewrites GETFIELD Minecraft.screen to GETFIELD Minecraft.gui + INVOKEVIRTUAL Gui.screen() and PUTFIELD to a SWAP-spliced Gui.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 real setScreen, i.e. modern close/init semantics instead of a silent field swap; net-better than the NoSuchFieldError crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/setScreen splices (javap), zero residual Minecraft.screen fieldrefs. Tested (ClientStructureBridge26xTest).

  • Options.hideGui bridged to 26.2's Hud (12 corpus mods read, 1 writes). 26.2 deleted the public Options.hideGui boolean; the F1 state moved to Hud.isHidden (private, public isHidden()/toggle(), NO absolute setter; vanilla reads mc.gui.hud.isHidden(), and Options.keyToggleGui is now consumed by Gui.handleKeybinds -> hud.toggle()). The Options receiver can't reach that state, so a second new mechanism, registerFieldStaticBridge (opcode-aware, unlike the opcode-blind field-to-method form of registerFieldRedirect), turns GETFIELD into INVOKESTATIC RetroClientEnv.isHideGui(Object)Z and PUTFIELD into setHideGui(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 the Hud instance (Minecraft.gui/Gui.hud are public final, so it is process-stable) and expresses a write as the conditional toggle if (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 residual hideGui refs, per-mod embedded copy present). Tested (ClientStructureBridge26xTest).

  • Minecraft.ON_OSX supplied 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 successor InputQuirks.ON_OSX is private static final, unreachable by any field redirect. The GETSTATIC becomes INVOKESTATIC RetroClientEnv.isOsx()Z, which recomputes the value the way vanilla's Util.getPlatform() does (os.name contains "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 (KeyHelper bridges, zero residual ON_OSX refs). Tested (ClientStructureBridge26xTest).

  • The 26.2 Gui->Hud member family bridged: all 17 moved members, including getChat(). 26.2 moved 17 public Gui members onto the new Hud (reached via the public final Gui.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 rewrites INVOKEVIRTUAL Gui.m(...) to INVOKESTATIC with the stack untouched; that shape is also handler-preserving for @Redirect/@WrapOperation/@WrapWithCondition mixins (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's setOverlayMessage handler-mirror hazard. Tested (ClientStructureBridge26xTest).

  • 33 tag constants deleted at 26.2 recovered via BlockItemTags accessors. 26.2 introduced BlockItemTags (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from BlockTags (the wood-type *_LOGS set, LOGS_THAT_BURN, SAPLINGS, the *_ORES set, SMELTS_TO_GLASS, ...) and 16 from ItemTags (DOORS, SLABS, STAIRS, FLOWERS, CHAINS, LANTERNS, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted GETSTATIC becomes GETSTATIC BlockItemTags.X + INVOKEVIRTUAL BlockItemTagId.block()/.item() via the existing static-field-accessor mechanism (the ColorCollection precedent). Corpus-scan hit: Darker Depths' RotatedPillarBlockMixin reads BlockTags.LOGS_THAT_BURN in its handler body. Tested (ClientStructureBridge26xTest).

  • LightTexture bridged (9 corpus mods; 26.1 render rewrite). Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to util/LightCoordsUtil (pack(II)I/block(I)I/sky(I)I, identical names/descriptors/bit layout), texture management went to renderer/Lightmap, which kept static getBrightness(DimensionType,I)F with the same descriptor. Fix: class move LightTexture -> Lightmap (heals getBrightness by itself) + method redirects for the coord statics + GameRenderer.lightTexture() bridged to a reflective helper returning the private Lightmap instance (receiver-as-arg0; null fail-safe) + turnOn/turnOffLightLayer neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through lightTexture(), so the chain stays type-correct and inert). Tested (RenderApiBridge26xTest).

  • RenderType class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite). RenderType moved to renderer/rendertype/RenderType on both 26.x jars; the ubiquitous static getters moved to rendertype/RenderTypes with a cull-naming FLIP (old entityCutout = culled -> entityCutoutCull; old entityCutoutNoCull -> 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 the ChunkSectionLayer enum) and are approximated by the surviving *MovingBlock tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the CompositeState/RenderStateShard builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (RenderApiBridge26xTest).

  • ItemBlockRenderTypes bridged 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's layer(), handling both the 26.1 spriteInfo and 26.2 materialInfo shapes) and returns the surviving RenderTypes.*MovingBlock tokens, the SAME tokens the RenderType getter redirects above use, so mod-side == comparisons stay consistent. Two verified traps encoded: the model-set probe order (getBlockStateModelSet FIRST; getBlockModelSet exists 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).

  • PlayerSkin restructure bridged (7 corpus mods; 26.1). The record moved client/resources -> world/entity/player with its Model enum promoted to PlayerModelType (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: texture()/capeTexture()/elytraTexture() returning ResourceLocation became body()/cape()/elytra() returning the ClientAsset$Texture INTERFACE; bridged by rename-capable return-unwrap converting redirects appending texturePath() , which required making the unwrap mechanism interface-aware (an INVOKEVIRTUAL on an interface is an IncompatibleClassChangeError; registerReturnUnwrapRedirect gained unwrapItf). Tested (ClientStructureBridge26xTest).

  • ReceivingLevelScreen -> LevelLoadingScreen (6 corpus mods; 26.1): class move; mods mostly instanceof it 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 into InteractionResult at 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 die NoClassDefFoundError. 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 by PASS_TO_DEFAULT_BLOCK_INTERACTION at 58, which 1.21.2 literally renamed TRY_WITH_EMPTY_HAND; CONSUME_PARTIAL->CONSUME; SKIP_DEFAULT_BLOCK_INTERACTION->FAIL, the closest no-swing chain-stop), sidedSuccess(Z) -> plain SUCCESS (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), and result() -> identity (post-merge the receiver IS the result). Tested (RenderApiBridge26xTest).

  • GlStateManager$class_4534/4535 hybrid spellings mapped + the 26.2 blend-factor teardown. Intermediary keeps com.mojang.blaze3d OUTER names plain but its inners got class_N ids, so distributed Fabric mods literally contain GlStateManager$class_4535-style names (10 corpus mods) that neither the intermediary map (keyed net/minecraft/class_...) nor the existing inner-move entries matched: mapped to the promoted 26.1 SourceFactor/DestFactor. Those promoted enums are then DELETED at 26.2: their GETSTATICs become pushed nulls (static-field nuller) feeding the enum blendFunc/blendFuncSeparate overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of NoClassDefFoundError. Tested (ClientStructureBridge26xTest.blendFactorTeardown26_2).

  • TextureSheetParticle -> SingleQuadParticle rebase (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-abstract getGroup(), so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing TextureAtlasSprite, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via setSprite/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 protected sprite field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old render(VertexConsumer,...) loads but its override is never called (the contract became extract(QuadParticleRenderState,...)): invisible, not crashed. Tested (RenderApiBridge26xTest).

  • GUI 2D-transform migration Phase 3: the double translate overload and mulPose rotation. translate(DDD)V migrates exactly (drop z with POP2, then a D2F+DUP_X2/POP/SWAP juggle converts y and x; BasicVerifier-proven). mulPose(Quaternionf[c]) becomes the 2D rotate(angle) via the generated RetroQuat2D.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 former HybridCompiler primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like Vec3, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain transform died NoSuchFieldError when prepped via aot/batch --aot (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the transformClassSimple -> 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 standalone aot run 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 into NoSuchMethodError (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).

  • RetroClientEnv no longer latches "unresolvable" during the client's own constructor. 26.2's Minecraft.<init> publishes the singleton early (insn ~133) but assigns gui much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw mc != null but gui == 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 and RetromodClassVisitor.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_2CoreMoves was still chain-only, so a batch/transform to 26.2 silently missed EVERY 26.2 core move (EntityType->EntityTypes constants, 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 3D PoseStack onto a 2D org.joml.Matrix3x2fStack (GuiGraphics.pose() returns the 2D stack), so a 1.21.x mod's guiGraphics.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-arg pushPose/popPose peephole): Phase 1 migrates the immediate arg-carrying float ops (gg.pose().translate(x,y,z) -> 2D translate(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 ASM SourceInterpreter dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single GuiGraphics.pose() (Phase 1) or a local whose SOLE store is one pose() and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D PoseStack - 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 the double/mulPose overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM BasicVerifier with 0 errors; the "3D PoseStack untouched" 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 of packetfixer/CrashAssistant/cosmeticcorpsecompat and 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, so needsTransformation skips 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 (NoClassDefFoundError at 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 @At target 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 @ModifyArgs and @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 its method_N name and died LambdaConversionException (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 flipped method_5905's descriptor from (String) to (ResourceKey); a 1.20.1-1.21.1 Fabric mod registering an entity dies NoSuchMethodError at <clinit> on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing Pre1_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 @Mod upgrade now matches the post-remap annotation spelling. Pass ordering can remap Lnet/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 @Mod classes 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 had newSimpleChannel, so every MCreator-style mod died NoSuchMethodError in <clinit>. The whole surface now routes onto the embedded NetworkShim (newSimpleChannel descriptor-erased; the messageBuilder(Class,int,NetworkDirection) chain incl. the consumerMainThread idiom; registerMessage incl. the Optional overload; 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 with NetworkHooks/ITeleporter/LivingTickEvent from 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 to NoSuchXError on 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 as scripts/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.py now resolves the Client and Server env ids (by the environment version-type, falling back to name-matching, with a CF_ENV_IDS pin 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-type release for a -snapshot; beta is 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 one issue-<N>.yml per 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), and compatdb.html collects 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 synthesized LegacyBakedModel interface over the 26.x BlockStateModel/BlockStateModelPart pair, 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) and ModelResourceLocation (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x BakedQuad is a different data structure), the FRAPI renderer family, and the CompositeState/RenderStateShard custom-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.mojang breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (BakedModel 25 mods, BufferUploader 21, LightTexture 19, ItemBlockRenderTypes 16, BlockModelShaper/BlockRenderDispatcher/ModelResourceLocation/RenderType$CompositeState/RenderStateShard*/DimensionSpecialEffects 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (GuiGraphics -> GuiGraphicsExtractor where pose() now returns a Matrix3x2fStack, so pushPose/popPose/translate/scale/text don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (Minecraft.screen field 45 mods; Minecraft.ON_OSX 26; Options.hideGui 20). 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 (the mulPose widening 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 public Gui.screen()/setScreen accessors and Hud.isHidden()/toggle()), and shipped in this snapshot; see the Minecraft.screen/Options.hideGui/Minecraft.ON_OSX entries above.

This mod has no additional files