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.2 (Forge 1.21.11)

File nameretromod-1.3.0-snapshot.2+1.21.11.jar
Uploader
BownluxBownlux
Uploaded
Jul 15, 2026
Downloads
4
Size
3.3 MB
Mod Loaders
Forge
File ID
8438168
Type
R
Release
Supported game versions
  • 1.21.11

Curse Maven Snippet

Forge

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

Learn more about Curse Maven

What's new

Second snapshot of the 1.3.0 line. Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's NoiseChunkMixin), a broad Registry value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.

Added

  • Removed-@Shadow-field demotion, and YUNG's API NoiseChunkMixin translated with it (worldgen). New MixinShadowFieldDemotion: when a mixin @Shadow @Finals a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own @Inject handler captures, the field is demoted to a plain @Unique mixin field and the handler is prepended with this.<field> = <capturedParam>. NoiseChunk.noiseSettings was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the NoiseSettings constructor argument is unchanged, so YUNG's NoiseChunkMixin (whose aquifer-override reads noiseSettings.height()/minY()) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the NoiseChunkMixin blocklist strip. Tested (MixinShadowFieldDemotionTest, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
  • Registry value getter get(Identifier) -> getValue(Identifier) (26.1 rename). The ResourceLocation -> Identifier rename had a companion: the registry VALUE getter Registry.get(Identifier) (returns the value T) became getValue(Identifier), while get(Identifier) now returns Optional<Holder.Reference> (a different method). A 1.21.1 mod that calls the old value getter (e.g. BuiltInRegistries.SOUND_EVENT.get(id)) linked against the vanished get(Identifier)Object and died at construct time with NoSuchMethodError: DefaultedRegistry.get(Identifier) (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because get(Identifier) still "resolves" by name+params. A descriptor-scoped method redirect (registered for Registry/DefaultedRegistry/MappedRegistry/DefaultedMappedRegistry, keyed on the post-remap Identifier value-getter descriptor so the Optional-returning get is untouched) renames just that overload to getValue, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (RegistryValueGetterRenameTest).
  • @Inject TRAILING-parameter re-signature (MixinHandlerResignature). The #69 engine inserted a LEADING param (the ServerLevel prepend); it now also handles a param appended at the END of a target's capture list, right before the CallbackInfo trailer (shifting only the CallbackInfo slot). 26.1 appended a ResourceKey<Level> (the dimension key) to ChunkGenerator.tryGenerateStructure, so a 1.21.1 @Inject that captured the old 9 params was missing the trailing arg and died InvalidInjectionException. Registered for tryGenerateStructure with a StructureSet$StructureSelectionEntry first-param guard (YUNG's Better Strongholds DisableVanillaStrongholdsMixin, verified applied on 26.2). Tested (MixinHandlerResignatureTest).
  • Mixin discovery tooling (mixin-scan CLI + mixin-rank/mixin-crossjoin/mixin-refmap-harvest scripts). Enumerate every @Mixin injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in scripts/mixin-discovery.md and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
  • Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit). A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: Minecraft.getTimer() -> getDeltaTracker() (11 mods), Camera.getPosition() -> position() (9), GameRenderer.getMainCamera() -> mainCamera() (10, a 26.2 rename), and the JOML const-interface widening VertexConsumer/BufferBuilder.addVertex(Matrix4f,FFF) -> the Matrix4fc param (12 + 7; the concrete Matrix4f is-a Matrix4fc, so only the descriptor changes). Tested (Corpus26xRenameRedirectTest).
  • score <dir> --json corpus mode. The score command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen --mc-jar) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
  • Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit). A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): registerConvertingRedirect (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers Mth.cos(F)F/sin(F)F widened to (D)F (F2D the arg; 16+12 mods), Window.getGuiScale()D narrowed to ()I (I2D the result; 16 mods) and SoundManager.play(SoundInstance)V now returning a SoundEngine$PlayResult (POP the result; 24 mods); registerSingletonStaticRedirect re-expresses a no-arg static helper that became an instance method, covering Screen.hasControlDown()/hasShiftDown()/hasAltDown() -> Minecraft.getInstance().hasX() (24+24+12 mods); and the existing arg-drop redirect covers CompoundTag.getList(String,int) -> getListOrEmpty(String) (drop the type-hint int; 15 mods). Structural residuals from the same audit (ClickEvent/HoverEvent now interfaces, InputConstants input-handle redesign, the removed GlStateManager blend-factor enums, Minecraft.screen) are left for later. Tested (Corpus26xDescriptorAdaptationTest).
  • NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods). A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new Optional-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by BasicVerifier (0 new verify errors on the NBT-heavy jars): CompoundTag.contains(String,int) -> contains(String) (drop the tag-type-hint int), CompoundTag.getCompound(String) and ListTag.getCompound(int) -> getCompoundOrEmpty(...) (the plain getters now return Optional), CompoundTag.remove(String)V -> remove(String)Tag (POP the now-returned removed tag), and TagParser.parseTag(String) -> parseCompoundFully(String). Tested (Corpus26xDescriptorAdaptationTest). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - BufferBuilder/Tesselator/RenderSystem/GlStateManager/GuiGraphics drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 FastColor case.)
  • ClickEvent/HoverEvent constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits). Both became sealed INTERFACES with per-action record subtypes (ClickEvent.RunCommand/OpenUrl/ChangePage/..., HoverEvent.ShowText/...), so the old new ClickEvent(Action,String) / new HoverEvent(Action,Object) constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those news to com.retromod.polyfill.minecraft.RetroTextEvents, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> OpenUrl(URI), run/suggest-command + copy -> the String subtypes, change-page -> ChangePage(int), open-file -> OpenFile(File); an unmappable action or bad value goes inert as null). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a CHECKCAST since the reflective factory returns Object. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and BasicVerifier (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (Corpus26xDescriptorAdaptationTest: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
  • Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch). A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's loadClassTweakers / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and latest.log stops at the mod list. The RUNTIME (FabricModTransformer) path already remapped these; the offline batch/transform/AOT path (RetromodCli) did NOT, a parity gap: (1) a mod's access widener stays accessWidener v1 intermediary, and 26.1+ runs the official namespace, so Fabric's classTweaker reader throws ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official) and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an @Inject resolves to net/minecraft/class_XXXX and Mixin rejects it (InvalidInjectionException). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every class_/method_/field_ token to Mojang. The refmap remapper also gained the combined data."named:intermediary" -> "named:official" form current Fabric loom emits (AppleSkin's shape), which the previous plain-data.intermediary logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared AccessWidenerRemapper / MixinRefmapRemapper (one source of truth for runtime + offline). Tested (AccessWidenerRemapperTest, MixinRefmapRemapperTest); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar transform command doesn't apply the full intermediary bytecode remap that batch/runtime do, and AppleSkin needs a Fabric-API PayloadTypeRegistry networking shim - separate from these transform fixes.)
  • 26.x GUI 2D-transform migration, Phase 0 (GuiGraphics.pose().pushPose() peephole). 26.x moved GUI rendering off the 3D PoseStack onto a 2D org.joml.Matrix3x2fStack (GuiGraphics.pose() now returns the 2D stack, whose ops are pushMatrix()/popMatrix(), not pushPose()/popPose()), but PoseStack still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: docs/design/gui-2d-transform-migration.md). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for guiGraphics.pose().pushPose() / .popPose() (the pose() call adjacent to the void op, its result consumed on the spot) to pose():Matrix3x2fStack + pushMatrix()/popMatrix() + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (translate/scale/mulPose), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (Gui2DTransformMigrationTest). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.

Fixed

  • transform CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime). The standalone transform command registered only the version-shim chain (or the all-shims fallback) and skipped PolyfillRegistry.loadAndRegister, so a transform-only pass left references to removed-vanilla classes dangling: e.g. net/minecraft/util/LazyLoadedValue (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. transformCommand now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (TransformPolyfillRegressionTest).
  • batch and AOT now apply the removed-API polyfills too. The shared registerAuxiliaryRedirects step (the ONLY way the batch JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (net/minecraft/util/Tuple, LazyLoadedValue, ...) was left dangling, even though batch/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw net/minecraft/util/Tuple refs across the 40-mod corpus went 40 -> 0). Tested (TransformPolyfillRegressionTest).
  • Fabric intermediary-map version skew (Minecraft.getTimer/getDeltaTracker). A top-40 Fabric 1.21.1 corpus audit found the bundled intermediary-to-mojang.tsv (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: getTimer() became getDeltaTracker(), so a distributed 1.21.1 mod calling method_60646 was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added method_60646 -> getDeltaTracker (the 26.x id method_61966 already mapped). Tested (IntermediaryMethodSkewTest). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. GlStateManager$class_4535.)
  • Intermediary version skew, systematized (FastColor$ARGB32/ABGR32 -> ARGB color helpers). Following up the getTimer skew fix, a reusable composer (scripts/harvest-intermediary-skew.py) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - Explosion particle getters, ItemInteractionResult, the ARMOR_MATERIAL registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 FastColor$ARGB32/ABGR32 -> ARGB class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the FastColor -&gt; ARGB class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (method_57173/57174/58144/59553/59554/60676 -> color/opaque/color/as8BitChannel/colorFromFloat/average), each verified present on 26.2 ARGB, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (IntermediaryMethodSkewTest).
  • Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface). Confirmed and fixed: (1) a save-data @Inject handler shared across BOTH a write (addAdditionalSaveData) and a read (readAdditionalSaveData) target is now DECLINED by the ValueIO adapter (one CompoundTag param can't become both ValueOutput and ValueInput) and soft-fail-stripped instead of adapted to one type and left throwing InvalidInjectionException on the other; (2) collectUnrepairable now scopes its stale-CompoundTag check to captures BEFORE the CallbackInfo trailer, so a modern handler with a CompoundTag @Local is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded @Inject sharing a stripped handler's name survives; (4) SyntheticEmbedder.embedIntoJar now bounds its reads with ZipSecurity (per-entry + aggregate cap) instead of raw readAllBytes (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline transform entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) clearRedirectsForTesting now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in MixinValueIoAdapterTest; the rest exercised by the existing suite).

Research (documented, not yet shipped)

  • Yung's API BeardifierMixin confirmed genuinely un-translatable (headless-proven, stays stripped). Its companion NoiseChunkMixin shipped as a real translation (see Added), but BeardifierMixin did NOT: research showed it is byte-translatable (its @Inject targets forStructuresInChunk/compute survive on 26.2) and it applies cleanly, yet the headless worldgen pass proved it is not launch-safe. With BeardifierMixin active, chunk generation throws IllegalStateException: Parent chunk missing (ChunkMap.applyStep) during initial spawn generation and the dedicated server never reaches Done - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach Done and generate 49 force-loaded chunks. So BeardifierMixin + the dead BeardifierAccessor stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an instanceof no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
  • YUNG's NeoForge structure mods (Better Strongholds): StructureProcessorType registration is structurally incompatible on 26.2, not mechanically bridgeable. With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the StructureProcessorType-as-SAM model the mod is built on. Investigated (attempted a DeferredRegister bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) StructureProcessorType lost its codec() abstract method (now a non-functional marker interface; Registries.STRUCTURE_PROCESSOR holds MapCodec directly), so the mod's StructureProcessorType SAM lambdas reach the MapCodec registry unconverted (ClassCastException: …$$Lambda cannot be cast to MapCodec -> unbound processor_list -> FatalStartupException); (2) StructureProcessor.getType():StructureProcessorType was renamed to codec():MapCodec, so the mod's processor subclasses (which override getType() and return their StructureProcessorType field) don't implement 26.2's abstract codec() and would be un-instantiable at decode, with no mechanical way to reconstruct the StructureProcessorType->MapCodec relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via invokedynamic against the now-methodless StructureProcessorType. Converting only the DeferredRegister registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in Mods That Can't Be Translated.

This mod has no related projects