Retromod 1.3.0-snapshot.2 (NeoForge 26.2)
Curse Maven Snippet
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 APINoiseChunkMixintranslated with it (worldgen). NewMixinShadowFieldDemotion: 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@Injecthandler captures, the field is demoted to a plain@Uniquemixin field and the handler is prepended withthis.<field> = <capturedParam>.NoiseChunk.noiseSettingswas deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while theNoiseSettingsconstructor argument is unchanged, so YUNG'sNoiseChunkMixin(whose aquifer-override readsnoiseSettings.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 theNoiseChunkMixinblocklist strip. Tested (MixinShadowFieldDemotionTest, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar). Registryvalue getterget(Identifier)->getValue(Identifier)(26.1 rename). TheResourceLocation->Identifierrename had a companion: the registry VALUE getterRegistry.get(Identifier)(returns the valueT) becamegetValue(Identifier), whileget(Identifier)now returnsOptional<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 vanishedget(Identifier)Objectand died at construct time withNoSuchMethodError: DefaultedRegistry.get(Identifier)(YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help becauseget(Identifier)still "resolves" by name+params. A descriptor-scoped method redirect (registered forRegistry/DefaultedRegistry/MappedRegistry/DefaultedMappedRegistry, keyed on the post-remapIdentifiervalue-getter descriptor so theOptional-returninggetis untouched) renames just that overload togetValue, 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).@InjectTRAILING-parameter re-signature (MixinHandlerResignature). The #69 engine inserted a LEADING param (theServerLevelprepend); it now also handles a param appended at the END of a target's capture list, right before theCallbackInfotrailer (shifting only theCallbackInfoslot). 26.1 appended aResourceKey<Level>(the dimension key) toChunkGenerator.tryGenerateStructure, so a 1.21.1@Injectthat captured the old 9 params was missing the trailing arg and diedInvalidInjectionException. Registered fortryGenerateStructurewith aStructureSet$StructureSelectionEntryfirst-param guard (YUNG's Better StrongholdsDisableVanillaStrongholdsMixin, verified applied on 26.2). Tested (MixinHandlerResignatureTest).- Mixin discovery tooling (
mixin-scanCLI +mixin-rank/mixin-crossjoin/mixin-refmap-harvestscripts). Enumerate every@Mixininjector 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 inscripts/mixin-discovery.mdand 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 wideningVertexConsumer/BufferBuilder.addVertex(Matrix4f,FFF)-> theMatrix4fcparam (12 + 7; the concreteMatrix4fis-aMatrix4fc, so only the descriptor changes). Tested (Corpus26xRenameRedirectTest). score <dir> --jsoncorpus mode. Thescorecommand 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) coversMth.cos(F)F/sin(F)Fwidened to(D)F(F2D the arg; 16+12 mods),Window.getGuiScale()Dnarrowed to()I(I2D the result; 16 mods) andSoundManager.play(SoundInstance)Vnow returning aSoundEngine$PlayResult(POP the result; 24 mods);registerSingletonStaticRedirectre-expresses a no-arg static helper that became an instance method, coveringScreen.hasControlDown()/hasShiftDown()/hasAltDown()->Minecraft.getInstance().hasX()(24+24+12 mods); and the existing arg-drop redirect coversCompoundTag.getList(String,int)->getListOrEmpty(String)(drop the type-hint int; 15 mods). Structural residuals from the same audit (ClickEvent/HoverEventnow interfaces,InputConstantsinput-handle redesign, the removedGlStateManagerblend-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 byBasicVerifier(0 new verify errors on the NBT-heavy jars):CompoundTag.contains(String,int)->contains(String)(drop the tag-type-hint int),CompoundTag.getCompound(String)andListTag.getCompound(int)->getCompoundOrEmpty(...)(the plain getters now returnOptional),CompoundTag.remove(String)V->remove(String)Tag(POP the now-returned removed tag), andTagParser.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/GuiGraphicsdrawing - 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.1FastColorcase.) ClickEvent/HoverEventconstructor 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 oldnew 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 thosenews tocom.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 asnull). 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 aCHECKCASTsince the reflective factory returnsObject. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) andBasicVerifier(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 andlatest.logstops at the mod list. The RUNTIME (FabricModTransformer) path already remapped these; the offlinebatch/transform/AOT path (RetromodCli) did NOT, a parity gap: (1) a mod's access widener staysaccessWidener v1 intermediary, and 26.1+ runs theofficialnamespace, so Fabric's classTweaker reader throwsClassTweakerFormatException: 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@Injectresolves tonet/minecraft/class_XXXXand Mixin rejects it (InvalidInjectionException). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and everyclass_/method_/field_token to Mojang. The refmap remapper also gained the combineddata."named:intermediary"->"named:official"form current Fabric loom emits (AppleSkin's shape), which the previous plain-data.intermediarylogic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to sharedAccessWidenerRemapper/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-jartransformcommand doesn't apply the full intermediary bytecode remap thatbatch/runtime do, and AppleSkin needs a Fabric-APIPayloadTypeRegistrynetworking 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 3DPoseStackonto a 2Dorg.joml.Matrix3x2fStack(GuiGraphics.pose()now returns the 2D stack, whose ops arepushMatrix()/popMatrix(), notpushPose()/popPose()), butPoseStackstill 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 forguiGraphics.pose().pushPose()/.popPose()(thepose()call adjacent to the void op, its result consumed on the spot) topose():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
transformCLI command now loads the removed-API polyfills (parity with analyze/batch/runtime). The standalonetransformcommand registered only the version-shim chain (or the all-shims fallback) and skippedPolyfillRegistry.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.transformCommandnow runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (TransformPolyfillRegressionTest).batchand AOT now apply the removed-API polyfills too. The sharedregisterAuxiliaryRedirectsstep (the ONLY way thebatchJIT 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 thoughbatch/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: rawnet/minecraft/util/Tuplerefs 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 bundledintermediary-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()becamegetDeltaTracker(), so a distributed 1.21.1 mod callingmethod_60646was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Addedmethod_60646 -> getDeltaTracker(the 26.x idmethod_61966already 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->ARGBcolor helpers). Following up thegetTimerskew 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 -Explosionparticle getters,ItemInteractionResult, theARMOR_MATERIALregistry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1FastColor$ARGB32/ABGR32->ARGBclass rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + theFastColor -> ARGBclass-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.2ARGB, 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
@Injecthandler shared across BOTH a write (addAdditionalSaveData) and a read (readAdditionalSaveData) target is now DECLINED by the ValueIO adapter (oneCompoundTagparam can't become bothValueOutputandValueInput) and soft-fail-stripped instead of adapted to one type and left throwingInvalidInjectionExceptionon the other; (2)collectUnrepairablenow scopes its stale-CompoundTagcheck to captures BEFORE theCallbackInfotrailer, so a modern handler with aCompoundTag@Localis no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded@Injectsharing a stripped handler's name survives; (4)SyntheticEmbedder.embedIntoJarnow bounds its reads withZipSecurity(per-entry + aggregate cap) instead of rawreadAllBytes(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 offlinetransformentry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7)clearRedirectsForTestingnow clears the new converting/singleton redirect maps (test isolation). Tested (new cases inMixinValueIoAdapterTest; the rest exercised by the existing suite).
Research (documented, not yet shipped)
- Yung's API
BeardifierMixinconfirmed genuinely un-translatable (headless-proven, stays stripped). Its companionNoiseChunkMixinshipped as a real translation (see Added), butBeardifierMixindid NOT: research showed it is byte-translatable (its@InjecttargetsforStructuresInChunk/computesurvive on 26.2) and it applies cleanly, yet the headless worldgen pass proved it is not launch-safe. WithBeardifierMixinactive, chunk generation throwsIllegalStateException: Parent chunk missing(ChunkMap.applyStep) during initial spawn generation and the dedicated server never reachesDone- 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 reachDoneand generate 49 force-loaded chunks. SoBeardifierMixin+ the deadBeardifierAccessorstay 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 aninstanceofno-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):
StructureProcessorTyperegistration 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 theStructureProcessorType-as-SAM model the mod is built on. Investigated (attempted aDeferredRegisterbridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1)StructureProcessorTypelost itscodec()abstract method (now a non-functional marker interface;Registries.STRUCTURE_PROCESSORholdsMapCodecdirectly), so the mod'sStructureProcessorTypeSAM lambdas reach theMapCodecregistry unconverted (ClassCastException: …$$Lambda cannot be cast to MapCodec-> unboundprocessor_list->FatalStartupException); (2)StructureProcessor.getType():StructureProcessorTypewas renamed tocodec():MapCodec, so the mod's processor subclasses (which overridegetType()and return theirStructureProcessorTypefield) don't implement 26.2's abstractcodec()and would be un-instantiable at decode, with no mechanical way to reconstruct theStructureProcessorType->MapCodecrelationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs viainvokedynamicagainst the now-methodlessStructureProcessorType. Converting only theDeferredRegisterregistration (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.
All Relations
- All Relations
- Embedded Library
- Optional Dependency
- Required Dependency
- Tool
- Incompatible
- Include
This mod has no related projects

