Repository navigation
Releases
Remold v0.2.8
v0.2.8 Release Notes
Status: released 2026-11-12 (v0.2.8, GitHub release published; tag f18851e).
Previous release: v0.2.7 (0852506).
Summary (plain language)
What a regular mod developer will notice:
- More "incompatible edit" cases are refused before the reload. Annotation default values, Record
components, method/field staticness flips, deleted constructors, enum reordering or constant
add/remove, and deletion of members that still have outside readers now fail closed with a message
naming the member — instead of reporting success and crashing later. - Three silent lambda mistakes are closed. Removed lambdas no longer return
null/0to old call
sites (non-void orphans keep the previous round's return value); ambiguous fallback pairing now warns
and refuses instead of silently binding the wrong body; and classes compiled without-g(no
LocalVariableTable) now warn that captured-lambda reordering is undecidable, with an opt-in strict
mode that rejects the reload. - Still fewer "read 0/null" moments during hot reload. New/changed fields could briefly expose their
default value to other threads; that window is now also closed on the single-class dynamic path — the
route Mixin uses when a target gains a new@Uniquefield read by an@Injecthandler on the next tick. - Signature drift is no longer silent. Parameter-annotation and generic-signature changes now emit a
member-level[SIG-DRIFT]warning. - Logs are English-only, so GBK consoles and log collectors no longer garble hot-swap diagnostics.
- The Gradle plugin is less surprising. The agent jar now carries its version; the plugin compares it
with the configuredremold.versionand replaces a stale local jar. A newcleanRemoldCachetask
clears project-level caches after a version change. - Scope of this release: v0.2.8 is a safety-and-stability release. It contains the full batch of
compatibility gates merged after the v0.2.7 tag (annotation defaults, Record/staticness/constructors,
enum, signature drift, three lambda cases) plus two hot-reload reliability features (orphan lambda body
retention, two-phase dynamic redefine). The large Mixin feature set shipped in v0.2.7.
New Features
- Two-phase dynamic redefine (
redefineDynamic). The single-class entry point now consumes
InitFix.requiresTwoPhaseand runs stage A (new shape + old method bodies) → instance patch → stage C
synchronously inside the same call, so callers still see the final state and the public return
contract is unchanged. Stage C reuses the already generatedruntimeBytes(no second transformer
bridge pass, no secondInitFix.transform). Method-body-only edits keep the single-redefine fast
path. Covered byDynamicTwoPhaseRedefineTest(2/2, recordedInstrumentationasserts the call order
and bytes; mutationtwoPhase=falseturns it red) and by the real-DCEVM
redefineDynamicNoBaselineProbe(13/0, logtwo-phase: A -> patch -> C, existing instanceb=42). - Orphan lambda body retention (non-void, on by default). A removed lambda's ghost stub now keeps
the previous round's method body (frames and exception table included) when a liveness gate proves the
body only references members that still exist; otherwise it falls back to the default stub and reports
why.voidorphans stay no-op by default (-Dremold.agent.orphan_retain_void=trueopts in). Retention
logs once on first real execution and survives multiple rounds. Spec:
docs/topology/11-orphan-lambda-body-retention.md; testsOrphanBodyRetentionTest6/6. - Gradle plugin: agent version reconciliation +
cleanRemoldCache.remold-agent.jarnow writes
Remold-Version/Implementation-Versioninto its manifest; the plugin refuses to start a stale
auto-discovered agent, re-resolves by the configured version and overwrites the local copy (naming the
ignored file), while an explicitagentJaris only warned about.cleanRemoldCacheclears the
project's resolved agent/plugin copies and generated launch scripts; the cross-project shared download
cache is kept unless--globalis passed. It is deliberately not wired intoclean. - JDK-internal packages opened by the agent.
ModuleOpenerusesInstrumentation.redefineModulein
premain/agentmainto export/open nine JDK-internal packages to every unnamed module, so users no
longer need--add-exports/--add-opens. AnEVERYONE_MODULEfallback covers loaders created after
premain, and plugin initialization opens them once more for host loaders that did not exist at premain.
Safety & Compatibility Changes
- Annotation default drift fails closed. Changing, removing or adding an annotation member default is
refused by default (already-loaded consumers keep the old default; a redefine cannot backfill them):
the batch path aborts the whole round, the dynamic path refuses with a named reason. The explicit
-Dremold.agent.annotation_default_change=allowescape hatch still warns loudly. - Enum gates consolidated. Enum declaration reorder is now detected (
CHANGED_ENUM_ORDER, switch
cascades included); enum constant add/remove is diffed (addedEnumConstants/removedEnumConstants)
and refused withENUM_CONSTANT_SET_CHANGED; and the rejection reason names the measured blockers
(already JIT-compiled readers holding the old constant table until deopt,$SwitchMap$/$SWITCH_TABLE$
holders that cannot gain new cases, existingEnumMap/EnumSetinstances that cannot be resized).
The Mixin member aligner no longer swallows enum reorders by treating$VALUESand other JVM/javac
synthetics as Mixin-generated members; enum classes get no field-table reordering or tombstoning.
Backed by 30 independent-JVM probes (tests/enum-append-probe/),
docs/topology/10-enum-constant-append-infeasibility.mdandD-ENUM-1. - Lambda fallback pairing fails closed. When an old call site has no deterministic owner, the aligner
no longer silently binds it to a new body: it warns and refuses the pairing. Gate-state self-reporting,
permanent regression guards and a larger corpus (N=37) were added. - Strict mode for captured lambdas without
-g. Without aLocalVariableTable, captured parameter
names are unavailable and reorder detection silently degrades. The aligner now warns explicitly and
recordsmissingLvtLambdas;-Dremold.agent.lambda_strict=truethrows
LambdaAlignmentStrictException([HOTSWAP-REJECT], transaction rolled back so the class keeps its old
version). Covered byLambdaLvtStrictGateTest6/6. - Member-metadata drift is named. Parameter-annotation and generic-signature changes emit a
member-level[SIG-DRIFT]warning (derived-class eviction range widened); class-level generic
signatures remain a known uncovered boundary. Covered byParameterAnnotationDriftTestand
:tests:metadataDriftWarnProbe(wired into:tests:check). - Staticness flips, deleted constructors and environment-flag overrides are gated. A
static↔
instance flip is detected and refused (old call instructions would throwIncompatibleClassChangeError);
deleted constructors are checked against out-of-batch readers;-Denvironment-flag overrides are
detected by the same gate. Covered byMemberStaticnessAndConstructorScanTest(7/7, 8/8). - Record components and
@RemoldReinitextractability. Adding a Record component with live instances
is refused;@RemoldReinitauthorization now verifies that the initializer slice is actually
extractable (Record component, constructor parameter self-assignment, or no legal initializer ⇒
LAYOUT-REJECT) before allowing the overwrite. Covered by
RecordAndReinitExtractabilityGateTest5/5. @RemoldReinitcontract tightened.-Dremold.agent.force_reinitis now a real escape hatch
(defaultfalse), a per-field[REINIT] @RemoldReinit trigger on <class>line explains why a field was
replayed, an inherited-scope gate refusesINCLUDE_SUBCLASSESwhen a registered subclass writes the
same inherited field (scope = SELFis not blocked), and the Javadoc documents the "does not do" list
and the exactCONDITIONALsemantics.- Removed-member reader scan no longer fails closed on unrelated JDK references. Only provably
unrelated references are skipped (constructor owner mismatch; platform-prefix owners for non-platform
fields/static/private). Virtual instance methods stay fail-closed; rejection logs name the first three
reader classes. - Plain classes are no longer reordered as if they were Mixin output. The member aligner previously
restored baseline member order for ordinary classes too; that false positive is fixed, and the
two-phase trigger now only considers fields that would actually be written with non-zero values. - Runtime logs are English-only (17 CJK literals across 6 files translated). A new
./gradlew :tests:verifyLogLanguageguard (wired intocheck) fails on CJK/full-width punctuation in
main-source string literals. - Release-coordinate self-check is now case-sensitive.
verifyReleaseCoordinatesno longer only
asserts that POM files exist (case-insensitive filesystems hid spelling drift); it validates
<groupId>/<artifactId>/<version>as case-sensitive literals for both coordinate families. The
eight JitPack publications and all templates/docs use the real account casing
com.github.I-hope1.Remold(matchinggit remoteI-hope1). Verified: 16/16 coordinates resolve from
mavenLocal, and both templates resolvegradle-plugin:v0.2.8viahelp --offline.
Fixes
- Linux aarch64 native library (
UnsatisfiedLinkError). The platform matrix mapped Linux aarch64 to
the Android NDK artifact (bionic ABI), which cannot load und...
v0.2.7 · Mixin 热更与对齐子系统收口
v0.2.7 Release Notes
Status: released 2026-11-11 (v0.2.7, GitHub release published; tag 0852506).
Previous release: v0.2.6 (2159d21).
Highlights
- A Mixin-generated anonymous class may never change its member schema in place. The generated
Target$Anonymous$<uuid>class carries its captured variables as synthetic fields, so a changed
capture list changes the field table and the<init>descriptors. On a JBR-21 DCEVM host that is
not a local edit:redefineClassesmakes the VM re-lay-out instances and rewrites unrelated heaps.
Measured on a real client ([观测]): one such redefine wiped
LootContextPredicate.combinedConditionon 230 of 244 instances
([HEAPWATCH-DIFF] null{before=0 after=230 newlyNull=230}, both channelsagree=1220 conflict=0),
and it did so even when the synthetic class had zero instances on the heap
(live{raw=0 reachable=0}) — so the trigger is "member schema differs", not "an old instance exists".
The gate therefore refuses any schema change (fieldTableChanged, computed from the live class's
reflection table includingisStatic/isSynthetic) and reports it asgateRejected. Body-only edits
keep the fast in-place path. - The synthetic class and its host target now fail or commit together. Refusing the synthetic class
while still redefining the target is a split state: the target would call a<init>the VM never got,
and the next tick throwsNoSuchMethodError. A newBytecodeTransformerBridge.Abortsignal carries
"do not redefine this class in this round" through the bridge chain:applyTransformerBridges
rethrows it,redefineDynamicreturns failure without callingredefineClasses, and the batch
path drops the class. Neither the newly woven bytes nor the un-woven source bytes are ever handed to
the VM (the first keeps the split state, the second washes out the mixin hooks). The log now says
was NOT redefined … metrics: …instead of blaming the layout gate. - Same-session self-healing, and one refusal per candidate. A refused candidate is remembered by the
hash of the bytes that would have been installed — never by the target class's own source bytes,
which do not change when a mixin is edited (that mistake locked a session permanently: restoring the
capture list still read "stays aborted"). Repeating the identical candidate is suppressed without
double-counting; changing the capture structure is evaluated again and refused again; restoring the
capture list to match the live class clears the record and the body edit goes through.BLOCKEDis
cleared per round, so a session recovers without a restart. - Reload structure contracts broken open.
@Mixin(priority)(afinalfield inside aTreeSet),
@Mixin(targets)(in-place metadata migration across four indexes with snapshot rollback instead of
detach + re-register, which now tripsMixinTargetAlreadyLoadedException) andinterfaces(two-way
alignment; superclass changes still rejected, interface add/remove allowed on JBR/DCEVM) no longer
require a restart. - New mixins admitted at runtime, from wherever the IDE puts them. A four-level byte-source chain
(registered memory → every watch directory → batch cache → host provider) plus aMixinBytecodeOverlay
that also swaps eachMixinConfig's start-up-captured service, plus apendingUnresolvedMixinsrelay
(TTL + retry budget) for "mixins.jsonsaved first,.classcompiled seconds later". A pure-IDEA
out/production/classeslayout now hot-swaps without a second output directory and without a restart. - Access levels only ever move up.
AccessAlignaligns a re-woven product against the JVM's loaded
bytes (public/protectedmust not narrow, strippedACC_FINALmust stay stripped) behind a
fail-closed[ACCESS-GATE]self-check, andreadOriginalBytesreplays the loader's pre-Mixin
transforms (PackageAccessFixer,AccessWidener,AccessTransformer). Before this, one hot-swap of
MobEntity.isInAttackRangesent 112 members back toprotectedand the VM answered
VerifyError: Bad access to protected data→InternalError. - A Mixin subsystem documentation set.
docs/mixin/01..08now mirrorsdocs/topology/: invariants
and classloader authority, admission and the bytecode overlay, the re-weave pipeline and member
alignment (including why baseline synthetic methods and@Uniquefields may only be tombstoned, never
deleted), the three reload structure contracts, the synthetic-class single exit, end-to-end
walkthroughs, runtime gates and the two-channel heap sentinel, and the decision/evidence ledger.
Fixes
- Read-through
NullPointerExceptionbetweenredefineClassesandInitFix: closed on the internal
batch path by the two-phase redefine (stage A = new structure + old bodies, then instance patches, then
stage C = new bodies).-Dremold.agent.two_phase=falserestores the single-phase behaviour. - Mixin handler's first anonymous class:
Error loading class: Outer$1 (ClassNotFoundException)followed
byinnerClass is null— the new bytes are offered to Mixin's own provider beforereloadMixin, and
the upstream negativeClassInfocache entries are evicted. - Body-only edit of an already-loaded generated inner class: used to end in a silent
[DYNAMIC-REDEFINE] no-opwhile the old body kept running; the synthetic class is now redefined in
place and the trailing targetno-opline is explained in the log. NoSuchMethodError: <Target>$Anonymous$<hash>.<init>(…)on a retarget round trip: the tombstone stub
and field policy keeps baseline synthetic members in place, andClassInfo.cacheis re-anchored to
MixinInfo.getClassInfo()after every reload/registration sotransformSingleDescriptorkeeps
remappingthis$0.NoClassDefFoundError: LoadedTargetSuppressorunder a plugin/core version skew: the suppressor now
crosses the core boundary as plain JDKFunction/Consumermethod references.- Same-shape anonymous-class ambiguity: decided by body-structure similarity, then by a whole-group
zero-live-instance fallback; the orphan warning names the holder fields (proven by assignment flow, or
listed as supertype/interface candidates) and the@RemoldReinitescape hatch. - A field whose initializer allocates a Mixin-generated anonymous class was refused
(Nest scan incomplete/cannot determine protected status of constructor); synthetic bytes can now
be registered inBytecodeSources, and the diagnostic names the weaving-generated class. - A mixin name removed from
*.mixins.jsoncould be silently re-registered when its class file was
rewritten; the removal now holds until the name is re-added (Forge/NeoForge included, via
ModLauncherClassTrackerSpoofinside theprepare()window). - Batch stall watchdog: previously covered only the debounce batch; now arms through a scoped
enter/exitAPI covering the manualtriggerHotswap()entry andretransformLoadedas well. - Gradle plugin: agent JVM arguments were injected only into the loader
clientcontainer, so
./gradlew runServerand the gametest runs started without-javaagent; all run containers now get
the same arguments, and auto-deploy wiresrunServer. - Release tooling: JitPack publications published the bare version while the packaged shims and both
templates request…:v<version>; publications now use thev-prefixed version and the real account
casing (com.github.I-Hope1.Remold).
Added
api:BytecodeTransformerBridge.Abort(the "abandon this class for this round" signal),
HotswapBatchReport/HotswapBatchListener.afterInternalBatchReport(),HotswapPlugin.isHostReady().core:BytecodeSources,BatchReportScope,BatchWatchdog,StartupGate,ClassCopySelector,
AccessAlign,MixinMemberAlign,HeapFieldWatch,ReflectionCacheManagerSPIs.plugin-mixin:MixinBytecodeOverlay,MixinNewMixinBytes,MixinClassByteSources,
MixinClassBaseline(premain(loader, dotName)recorder),MixinInnerClassHotSwapsingle exit,
SyntheticAbortLedger,MixinSyntheticSchemaschema views,MixinRetarget,MixinReloadPolicy.docs/mixin/01..08plusdocs/topology/04/07/08additions (Tier 3.5 body-token disambiguation,
Tier 4.5 whole-group zero-scan pairing,findHoldingFieldsholder naming).:tests:verifyTestCountbaseline raised to 718 (from 405) — the guard fails the build when a run
silently loses cases or starts skipping.
Verification
- JUnit: 718 tests, 0 skipped, 0 failures (
./gradlew :tests:test; the CITest Matrixleg reports
the sameJUnit 测试数 = 718, skipped = 0; release window 2026-11-11), plus:tests:initFixOracle
ALL ASSERTIONS PASSED. - CI (run
43026871337):Test Matrix (javac 8/11/17/21/25),Test on DCEVM (JBR 21),
P0.5 Probe (JBR 21)and(JBR 17), and the native-library build all green. - Gate / ledger suites:
MixinSyntheticGateTest8,MixinSyntheticLivenessTest4,
MixinSyntheticRetryAndFlagsTest4,MixinSyntheticSchemaTest4,MixinHotSwapWiringTest3,
BridgeAbortTransactionTest2. - Mutation checks in this cycle's campaign ledger: schema-gate ordering, candidate-signature suppression,
apply()accounting, flag comparison and host abort — each turned the named case red and was restored
(mut-20261111101423-6dd7,mut-20261111103512-cd37,mut-20261111040924-1e27). One mutant
("suppression never fires") is an equivalent mutant: the gate already refuses unconditionally, so
the ledger only changes the log wording. MixinSyntheticGateProbeon JBR-21 (-XX:+AllowEnhancedClassRedefinition):
RESULT PASS cases=4 pass=4 skip(non-jbr)=0 enhanced=true— P1 body-only redefine keeps the live
instance's field values and swaps the body (desc=v2:7:payload), P2 shrink refused, P3 enhanced-shrink
negative control, P4alive{raw=1}refused, P...
Remold v0.2.6
v0.2.6 Release Notes
Status: released 2026-11-09 (v0.2.6, GitHub release published; tag 2159d21).
Previous release: v0.2.5 (78673b2).
Highlights
- Two-phase Schema-First redefine closes the read-through window.
redefineClassesreturns before
InitFixhas written the newly added fields, so a reader in that window can observenull/0(a
realNullPointerExceptionon render/logic threads). On the internal batch path (file watch →
processChanges) Remold now commits stage A — the new class structure with the old method
bodies, while constructors and<clinit>keep the new bodies — then patches live instances, then
commits stage C (the new bodies). The old bodies cannot reference fields that did not exist at
compile time, so they cannot read through the window; instances created between A and C are
initialized by the new constructors. Pure method-body edits keep the single-redefine fast path. On by
default;-Dremold.agent.two_phase=falserestores the previous behaviour. - Initializer replay for objects that already exist — both channels opt-in. A double-brace
initializer (new Unit() {{ health = 250; }}) that writes an inherited field, and a Kotlinobject/
top-levelvalholder whose<clinit>runsTurret("duo").apply { health = 250 }, now replay the new
compile-time constant onto live objects. Both are gated by an explicit allowlist
(-Dremold.agent.init_drift.parents=...,-Dremold.agent.init_drift.apply_holders=...; empty = off)
and write through the newHotswapBridge.KIND_CONDITIONAL_EXPECTEDprotocol: a value is written only
while the instance still holds the old constant, so a runtime-modified value (a damaged unit's
health) is never overwritten. Neither channel writesFieldLedgeror changespatchGenerated. - Mixin anonymous-class / inner-class hot-swap. The first anonymous class introduced inside an
@Injecthandler used to kill the reload (Error loading class: Outer$1 (ClassNotFoundException),
theninnerClass is null); editing only the body of an already-loaded generated inner class used to
end in a silent[DYNAMIC-REDEFINE] no-opwhile the old body kept running. Remold now feeds the new
bytes into Mixin's own bytecode provider (MixinBytecodeOverlay, with the upstream negative
ClassInfocache entries evicted) and redefines the generatedTarget$Anonymous$<uuid>class in place
(MixinInnerClassHotSwap,MixinInnerClassSupport). Weaving-generated classes can also register
their bytes incoreBytecodeSources, so Nest scanning and InitFix's constructor-visibility check
resolve them instead of failing closed;refeedForTargetpre-generates a freshly woven target's
synthetic inner classes after weaving and before the InitFix scan, then restoresloadCounterso the
platform still performs the realdefineClass. - Same-shape anonymous-class ambiguity and holder-field naming. When two anonymous classes in one
method share an interface and structure, topology alone can no longer decide and the edit used to be
orphaned. A body-structure similarity tier (anonymous-class references masked, literals excluded,
strict margin, both-way unique) now decides the R2b case; only if it fails does an approved liveness
fallback pair a whole ambiguity group whose old candidates all have zero live instances. The orphan
warning names the holder fields — proven by the assignment flow (NEW <anon> → <init> → PUTFIELD) or
listed as candidates when the field is declared as the anonymous class's supertype/interface (the
Supplier-typed holder case). - Start-up readiness gate and batch stall watchdog.
apigainedHotswapPlugin.isHostReady()
(defaulttrue);StartupGateholds batches until the host says it is ready, with
-Dremold.agent.ready_timeout_ms(default0= never releases on time).BatchWatchdogdumps all
thread stacks once per batch with[HOTSWAP-STALL]when a batch exceeds
-Dremold.agent.batch_stall_ms(default15000, floor5000); the funnel now covers the debounce
batch, the manualtriggerHotswap()entry andretransformLoaded. - Structured batch report, client HotSwap toast, template back on 1.21.1.
apigained
HotswapBatchReportandHotswapBatchListener.afterInternalBatchReport()— batch elapsed time, Mixin
member deltas (with an explicit "delta unknown" state) and InitFix patch counts — populated by the new
coreBatchReportScope. The Fabric template's client toast consumes that report and shows an
explicit "legacy agent" card on the published 0.2.5 agent instead of fabricating zeros. The template
baseline moves back to Minecraft 1.21.1 (yarn 1.21.1+build.3, loader 0.16.9,
fabric-api 0.105.0+1.21.1); the 1.21.4-only client gametest is preserved as the standalone
tests/fabric-gametest-1.21.4/probe. - Class and asset debounce are separate streams. Every
.class/.jarevent resets the Class
quiet window (default lowered to 800 ms); resource/config events use their own 250 ms window
and never postpone a Class batch. A pureDebounceWindowstate machine (unit-tested without sleeping)
decides which stream is due; the manualtriggerHotswap()entry still cancels the pending timer,
clears both deadlines and force-drains all queues. The Gradle DSL gainedassetShakeMs.
Fixes
- Read-through
NullPointerException: a reader running betweenredefineClassesandInitFixobserved
null/0for a newly added field. Closed on the internal batch path by the two-phase redefine
above; the external IDE/JDWP andredefineDynamicchannels still run single-phase and are listed
under Known limits. - Mixin handler's first anonymous class:
ClassNotFoundExceptionforOuter$1followed by
Cannot invoke "ClassInfo.isProbablyStatic()" because "innerClass" is nullno longer happens — the new
bytes are offered to Mixin's bytecode provider beforereloadMixin, and the negativeClassInfo
cache entries are evicted. - Body-only edit of an already-loaded Mixin-generated inner class: used to end in
[DYNAMIC-REDEFINE] no-opwhile the live class kept the old body; the generated
Target$Anonymous$<uuid>class is now redefined in place. The "no-op" line that follows for the
target class is expected and is now explicitly explained in the log. NoClassDefFoundError: LoadedTargetSuppressordisabled both the success and the failure branch of the
Mixin hot-swap handler under a plugin/core version skew. The "target already loaded" suppressor now
crosses the core boundary as plain JDKFunction/Consumermethod references, so the same skew
degrades to a diagnosable "suppression unavailable" path instead of a linkage error.- Same-shape anonymous-class edit: previously orphaned (
topology-equal candidates 2 -> 2, topology-decided 0) with the field-held live instances still running the old body. It is now decided
by body evidence, then by the liveness fallback; the warning names the old classes with their
live-instance counts, the holder fields and the@RemoldReinitescape hatch. - A field whose initializer allocates a Mixin-generated anonymous class was refused on hot reload
(Nest scan incomplete;Field init patch refused ... cannot determine protected status of constructor). Synthetic bytes can now be registered inBytecodeSources, and the diagnostics name
the weaving-generated synthetic class as the cause instead of failing unattributed. - A Mixin name removed from
*.mixins.jsoncould be silently re-registered when its class file was
rewritten; the removal now holds until the name is re-added, and a re-add whose asset event was missed
is re-checked on the next class rewrite (the recorded config path is re-read). On Forge/NeoForge,
ModLauncherClassTrackerSpoofremoves target names fromModLauncherClassTracker.loadedClasses
inside theprepare()window, restores them infinally, and fails closed when the tracker shape is
unknown;ForgeMixinConfigDiffwiresonAssetsChangedto the shared engine. - The batch stall watchdog previously covered only the debounce Class batch (manual
triggerHotswap()
andretransformLoadedwere uncovered). It now arms through a scopedenter/exitAPI with at
most one armed watchdog per batch. - Gradle plugin: the agent JVM arguments were injected only into the loader
clientrun container, so
./gradlew runServer(Fabric Loom / ForgeGradle / ModDevGradle) and Fabric gametest runs
(clientGameTest/gameTest) started without-javaagent. Client, dedicated-server and gametest
runs now receive the same arguments; auto-deploy also wiresrunServer, and the IDEA Loom sync covers
Minecraft_Server.xmlas well asMinecraft_Client.xml. - Release tooling: the Gradle plugin's JitPack publication published the bare version while the packaged
remold.gradle(.kts)shims and both project templates request
com.github.I-hope1.Remold:gradle-plugin:v<version>, so offline /mavenLocalresolution of the
plugin dangled. Publications now use thev-prefixed version,panamagained its missing JitPack
publication, and all consumer coordinates are canonicalized to the real repository account casing
(com.github.I-hope1.Remold), which also removes a Gradle module-metadata group-case mismatch.
Added
api:HotswapBatchReport(immutable, safe to pass across threads;MixinChangewith an explicit
deltaKnownstate,InitFixStatsthat only counts completed patches),
HotswapBatchListener.afterInternalBatchReport(),HotswapPlugin.isHostReady().core:BatchReportScope(per-batch scope collecting elapsed time, redefined classes, Mixin deltas
and InitFix stats),BytecodeSources(JDK-only registration port for weaving-generated class bytes,
consumed byHierarchyTreeOracle.readClassBytes),BatchWatchdog,StartupGate.- `tests/fabric-mixin...
Remold v0.2.5
v0.2.5 Release Notes (draft for commander review)
Status: prepared 2026-11-07 (tag pending commander approval).
Previous release: v0.2.4 (ddfb194).
Highlights
- Forge / NeoForge: newly declared
@Mixinmembers hot-swap. Adding an@Injecthandler method
or a@Uniquefield to an existing@Mixinused to be rejected at reload
(Conform error ... please restart client, then aClassInfo$Method.isUnique()NPE) because
MixinInfo.reloadMixinreuses the pre-reloadClassInfo.plugin-forgenow adds the new
members to that sameClassInfobeforeIMixinTransformer.reloadand rolls them back if the
reload is rejected. Every member lookup is resolved against the host ClassLoader's own
FieldNode/MethodNodetypes, so a stale agent-side ASM copy cannot be matched. - One ClassInfo member-sync engine.
core/src/remold/reflect/ClassInfoMemberSync.javais now the
only implementation;plugin-mixin(Fabric) andplugin-forge(Forge/NeoForge) are thin adapters
(type erasure + log prefix). The engine builds its rollback handle before mutating the member
sets, which also fixes a latent Fabric defect (see Fixes). - Real-container verification on three sides — Forge 1.21.1, NeoForge 21.1.265 and Fabric
1.21.1 — all against the same shared engine, each with its own red baseline where the failure is
attributable (a reload rejection / re-weave failure), not "nothing happened". - JPMS diagnostics. If a host ships Mixin as a named module without an opens edge, the sync path
and the re-weave bridge now print the exact
--add-opens org.spongepowered.mixin/org.spongepowered.asm.mixin.transformer=ALL-UNNAMEDargument
instead of a bare reflective failure.
Fixes
plugin-mixinMixinClassInfoSync: a failure between the field phase and the method phase leaked
the members it had already added (the rollback handle was only built after both phases succeeded).
The shared engine rolls back what was added; pinned by
ClassInfoMemberSyncTest#halfWayFailureRollsBackWhatWasAlreadyAdded.plugin-forge: Mixin templates that gained a new@Injecthandler /@Uniquefield were rejected
by the conform check, forcing a restart. Now hot-swapped (see Highlights).- Forge/NeoForge re-weave failures caused by reflective access denial now carry the actionable JVM
argument instead of only the rawIllegalAccessException/InaccessibleObjectException.
Added
tests/fabric-mixin-probe/forge-classinfo/: real Forge 1.21.1 dedicated-server E2E (stage 1 adds
an@Injecthandler, stage 2 adds a@Uniquefield) with a witness-basedVerdictwhose
discriminating power is pinned by synthetic red / green / not-attempted / self-contradictory
fixtures.tests/neoforge-mixin-probe/: real NeoForge 21.1 E2E whose Mixin config is registered by the
loader itself throughMETA-INF/neoforge.mods.toml[[mixins]](no manual
MixinEnvironment.addConfiguration); its verdict additionally rejects silent degradation
(Failed to sync Mixin class metadata, "no new members" when members were expected) and manual
registration.docs/neoforge/01-loader-and-classinfo-boundary-audit.md: read-only audit of the NeoForge
boundary (16-row code-surface truth table against NeoForge 21.1 and 26.x, artifact-level loader
facts, B1-B7 breakpoints with falsification tests).
Verification
- JUnit: 469 tests, 0 skipped, 0 failures (
./gradlew check), including the new engine contract
(5), Fabric adapter (2) and Forge cross-ClassLoader adapter (4) cases. ./gradlew check: all tasks pass, includingverifyDocStatusMarkersandverifyTestCount.- E2E, same probe sequence for red and green:
- Forge 1.21.1: red
VERDICT RED(stage 1 reload rejected withConform error ... ClassInfo$Method.isUnique() ... "method" is null, stage 2 re-weave failed) -> green
VERDICT GREEN(new handler +1 per call, new@Uniquereads its initializer 456, existing
members preserved). Re-run after the SSOT refactor:VERDICT GREENagain. - NeoForge 21.1.265 (MC 1.21.1, ModLauncher 11.0.5, sponge-mixin 0.16.4, ModDevGradle
2.0.148): native[[mixins]]registration, red baseline with the sync-disabled plugin jar ->
VERDICT RED(Conform error ... isUnique() ... "method" is null); fixed build ->
VERDICT GREENwithMixin class-info sync: ... addedMethods=1(stage 1) andaddedFields=1
(stage 2). NoInaccessibleObjectException, no--add-opensneeded in this dev runtime. - Fabric 1.21.1 (KnotServer, Fabric Loader 0.16.9, sponge-mixin 0.15.4+mixin.0.8.7): the
existing@Injectself-evolution probe re-run unchanged after the refactor ->RESULT PASS
(failures=[]), withclass-info sync ... addedMethods=1for both handler additions and the
added handler removed again in the final stage.
- Forge 1.21.1: red
- Mutation checks: reverse-applying the engine/adapter diff turns the unit cases red; reverting the
NeoForgeVerdictturns its acceptance red (both recorded in the campaign evidence ledger), and the
NeoForge red baseline is itself the "sync disabled" revert experiment. - Evidence files:
tests/fabric-mixin-probe/forge-classinfo/evidence/forge-1.21.1-{baseline,after-fix,after-ssot-refactor}.txt,
tests/neoforge-mixin-probe/evidence/neoforge-21.1-{baseline-red,after-fix}.txt,
tests/fabric-mixin-probe/evidence/inject-evolution-after-ssot.txt.
Known limits / manual acceptance
- NeoForge 26.x / FML 10+ is not supported and not verified. That generation removed ModLauncher
(cpw.mods:*is absent from its library list; entry pointnet.neoforged.fml.startup.Client,
FML loader 11.x). The cross-ClassLoader bridge in this release is written against ModLauncher
semantics; do not extrapolate the 21.1 evidence. - NeoForge production installer launch path is not verified. The E2E runs a dev runtime
(ModDevGradle directory classpath). Module ownership can differ in a production launch, so the
JPMS--add-opensrequirement must be re-tested there; the agent currently prints a precise
diagnostic instead of attempting runtime unsealing (runtimeredefineModuleis recorded as the
preferred follow-up indocs/status.md). - NeoForge evidence is one dev-run sequence. Mixin session ids are per-process; the recorded
hashes are not cross-run comparable. - Real client GUI paths and the manual acceptance checklist (
docs/acceptance/01-manual-checklist.md)
remain user-side; container probes cover dedicated servers. - Fabric evidence for this release is the existing probe re-run (KnotServer), not a client GUI run.
Remold v0.2.4
v0.2.4 Release Notes (draft for commander review)
Status: released 2026-11-07 (v0.2.4).
Previous release: v0.2.3 (0036550).
Highlights
@RemoldReinitscope: newscopeelement withSELF/INCLUDE_SUBCLASSES(default preserves
existing behaviour).SELFreinitializes only exact declaring-class instances; hostClassis
canonicalized so JBR's old-version "shadow"Classcannot corrupt identity filtering after a
structural redefine. A scope on a static field is ignored with a single[REINIT-SCOPE]warn.- Forge Mixin template hot update:
plugin-forgenow drivesIMixinTransformer.reload
reflectively and cascades a re-weave +redefineDynamicto loaded targets (ForgeMixinHotSwap).
Previously a changed@Mixinclass was defined into the target loader whileMixinInfostayed
stale, so@Injectbodies in targets never updated. - Mixin
@Injectself-evolution: added/renamed@Injecthandler methods now hot-swap.
MixinClassInfoSyncruns beforereloadMixin(the conform check reads that metadata) and
rolls back members when reload is rejected. - Profiler / entity probe robustness:
ProfilerTransformer.transformuses per-call local state
(reentrant, no shared-field clobbering); stale static-fieldClassrefs are normalized via
ClassIdentity.canonicalize;toggleEntityProbecatchesThrowableand rolls back from a
pre-call snapshot (failed enable restores the registration, failed disable re-registers). - Verification tooling:
probeAllaggregate task (not incheck) with zero exclusions; new
seconds-scale probes for multi-mixin targets,@Injectevolution,sourceBytecodeCachereal-path
matrix,LayoutGatereject→recover,@RemoldReinitscope, shadow-Class identity.
Fixes
plugin-mixin:MixinInfo.reloadMixinvalidated new@Injecthandlers against the previous
ClassInfo, rejecting added/renamed handlers withConform error ... please restart client;
sync now happens before reload, withSyncTokenrollback.plugin-forge: Mixin template changes were not reloaded (targets kept old handlers); new
ForgeMixinHotSwapreloads the template and cascades target re-weaving.coreprofiler: shared mutable transformer state causedNullPointerException: this.classReader is nulland intermittentClassFormatErrorwhenClassWriter.getCommonSuperClassre-entered the
transformer;transformis now stateless per call.coreprofiler/LibTool: static-fieldClassreferences rewritten by JBR redefinition (shadow
Classwith old fields,getInstances=0, silent no-op retransform) are canonicalized at API
boundaries.corelayout: type-change reject summaries no longer claim the reader scan failed; the note is
limited toREMOVED_FIELD.plugin-arc:Injectorused the package-privateRemoldAgent.instfield across the
app/bootstrap loader boundary (IllegalAccessError); it now uses publicRemoldAgent.getInst().
Verification
- JUnit: 461 tests, 0 skipped, 0 failures.
:tests:initFixOracle: all assertions pass../gradlew probeAll(explicit, not wired intocheck): all Gradle-hosted JVM probes pass,
includingalignBaselineProbeandoffsetStabilityProbe(47/47). Aggregate probes include
scopeReinitProbe35/0,cacheSourceMatrixProbe23/0,layoutRejectRecoverProbe41/0,
doubleBraceProbe80/0, P0.5 63/0, two-phase 42/0, lambda-bridge 35/0, retry 15/0,
initFix-fallback 14/0, inject-conflict 8/0, reader-scan 1/0, and the entity/shadow/class-identity
probes. External container scripts (Fabric C3/C5/multi-mixin, Forge C5/multi-mixin) are listed by
probeAllbut run separately with their own evidence files.- Mutation checks (seconds-scale, recorded in evidence): scope exact-class filter removal → red;
cache first-load write /putIfAbsentremoval → red; layout live-instance reject removal → red;
cancellablerevert and reload-order revert → red.
Known limits / manual acceptance
- Manual acceptance still pending:
docs/acceptance/01-manual-checklist.md(template scenarios
1–8, build/result columns intentionally empty for the user to confirm; real Fabric 1.21.1 client). - Forge upstream Mixin 0.8.5/0.8.7 semantics: cross-mixin same-name
@Uniquefields are merged
into a single field (Fabricsponge-mixindisambiguates to<sessionId>$<modId>$<name>$0).
Remold's re-weave keeps the merged field stable; this is upstream behaviour, not a Remold defect. - Forge
ClassInfomember sync for newly added Mixin members is not implemented on the Forge
side (Fabric covers it viaMixinClassInfoSync); structural contract changes still require a
restart. - NeoForge Mixin compatibility is not verified end-to-end.
NeoForgeHotswapPluginand the
sharedModLauncherMixinBridge/ForgeMixinHotSwapcode path exist, but there is no NeoForge
MDK probe and no realrunServerevidence: NeoForge mixin re-weaving, template hot update,
@Unique/@Injectretention and mixin-config registration are all unverified on NeoForge
(tracked as 🔶 indocs/status.md§Forge/NeoForge rows). Do not treat Forge 1.21.1 evidence as
covering NeoForge. - Container probes cover real servers (Fabric KnotServer, Forge
runServer), not a client GUI;
evidence hashes are per-process (Mixin sessionId) and not cross-run comparable. alignBaselineProbe/offsetStabilityProbeare JBR-sensitive and require
-XX:+AllowEnhancedClassRedefinition.offsetStabilityProbeProbe E's strict "cached Field
accessor auto-updated" assertion holds on JBR 25.0.2 (the intended p05 JDK); on JBR 21 the
accessor keeps the stale offset and reads the adjacents2=200Lslot, which is an
environment difference, not offset corruption.probeAllmust be run with JBR 25.0.2
(-Pp05.jbr=<jbr-25.0.2>); Probe D/D2 now pollcompilation.logfor Tier-4 compilation
(up to 30 rounds) instead of a fixed 100 ms warmup, which removes the batch-load flake.
Artifacts (build check)
core/build/libs/remold-agent.jar— 1.4 MB; MANIFEST:Premain-Class/Agent-Class=
remold.RemoldAgent,Boot-Class-Path: remold-agent.jar,Can-Redefine-Classes: true,
Can-Retransform-Classes: true; shaded content onlyremold/+libs/.plugin-{fabric,forge,mixin,arc}/build/libs/remold-plugin-*-0.2.4.jar.
Remold v0.2.3
Remold v0.2.3 Release Notes
This release hardens the Mixin hot-swap main path, fixes a silent baseline corruption in the
entity profiler, adds L0 initializer-drift detection, and backs the result with real-environment
end-to-end probes for both Fabric 1.21.1 and Forge 1.21.1 (ModLauncher).
Highlights
-
Mixin main path is no longer silently broken by baseline re-weaving
- The external-redefine / baseline-capture channel used to feed the already-woven
classfileBuffer
back into the Mixin bridge, appending hash-mangled duplicate@Uniquefields. - Those duplicates were written into the baseline cache; the next real hot-swap was then rejected by
LayoutGateasfield(s) removed, and the edit silently stopped taking effect. - Baseline capture now uses the JVM input bytes directly and never re-weaves them.
- The external-redefine / baseline-capture channel used to feed the already-woven
-
Entity-probe toggle no longer pollutes the hot-swap baseline
DynamicProfilerAPI.toggleEntityProberetransforms loaded subclasses; for Mixin targets that went
through the same external channel and double-wove them.- The retransform now runs inside
RemoldAgent.enterInternalRetransform(exitInternalRedefinein
finally): the profiler still instruments and un-instruments, butAnnotationTransformerleaves
the class and caches untouched.
-
Real-environment end-to-end verification
- Fabric 1.21.1 (KnotServer + real
-javaagent): add a method, add a field, two no-op rounds,
and edit the Mixin class itself (MixinHotSwap) —RESULT PASS, zero[INIT-DRIFT]. - Forge 1.21.1 (ModLauncher / ForgeGradle 7): the hot-swapped class is also a real Mixin target;
@Unique/@Injectsurvive the cross-ClassLoader re-weave and InitFix patches the live instance. - Evidence and probe sources live under
tests/fabric-mixin-probe/.
- Fabric 1.21.1 (KnotServer + real
Fixed
- Mixin baseline double-weave (duplicate
@Uniquefields ->LAYOUT-REJECTon the next edit). - Entity-probe retransform corrupting the baseline cache.
redefineDynamicwithoutbytecodeCache: baseline now falls back
bytecodeCache -> fetchOriginalBytecode -> sourceBytecodeCache, and fails closed when all are missing.- No-op
redefineDynamicis now visible: the "runtime bytes equal baseline, return true but nothing
was redefined" branch logs an explicit[DYNAMIC-REDEFINE] no-opline instead of being DEBUG-gated. - Retry bookkeeping / inject-conflict false positives: per-class retry bookkeeping moved out of the
redefinecatch;injectNewClassverifies already-loaded bytes before treating aLinkageErroras success. - Source baseline rollback and raw-byte fingerprints: classes that did not actually apply roll back
sourceBytecodeCache; disk fingerprints now hash the rawFiles.readAllBytesoutput, not aligned/woven bytes. - Offset drift: inherited fields of descendants are detected; drift bindings dedup by
declaringClass#field;LayoutPredictornormalizes dot/slash class names. - L0 warning hygiene: runs only for classes that actually reached the JVM, drops the
@OnReload
recommendation, skips instance enumeration when aggregating more than five classes, and warns separately
when an inherited field has a parent@RemoldReinit. - Fabric plugin agent-arg split: split only on the platform path separator.
Added
- L0 initializer-drift detection:
InitializerDriftcompares<init>slices per existing field and
reportsINIT-DRIFTwhen a field is still present but its initializer changed. Includes an instance gate,
inherited-field wording, aggregated warnings, andINSTANCE_ENUMERATIONSdiagnostics. - Regression tests: Mixin bridge call counting (baseline capture / internal retransform must not call
the bridge, a real source redefine must), entity-probe internal-retransform context, L0 drift,
inherited offset drift,redefineDynamicno-op and fail-closed cases.
Verification
- JUnit: 446 tests, 0 skipped, 0 failures, 0 errors.
- Probes:
initFixOracle350/0,doubleBraceProbe80/0,retryBookkeepingProbe15/0,
injectConflictProbe8/0,fingerprintDiskHashProbe18/0,redefineDynamicNoBaselineProbe13/0,
pendingAlignedMultiLoaderProbe5/0,retransformBaselineProbe10/0. MixinProbeexit-code assertion: exit 0 with the fix, exit 3 against the pre-fix agent.
Full Changelog: v0.2.2...v0.2.3
Remold v0.2.2
Remold v0.2.2 Release Notes
v0.2.2 focuses on the Gradle binary plugin, Fabric/Mixin hot-swap correctness, and on making
hot-swap failures observable instead of silent.
Highlights
-
Binary Gradle plugin + Plugin Portal
- New
:gradle-pluginbinary plugin (io.github.i-hope1.remold) with a type-safeRemoldExtension
DSL and configuration-cache support. - The root
remold.gradle/remold.gradle.ktsscripts are now thin shims over the binary plugin
(previously two parallel implementations). - Mod templates migrated to JitPack binary-plugin resolution; English plugin docs added for the
Gradle Plugin Portal review.
- New
-
Fabric / Mixin hot-swap correctness
@Uniquefield add/remove is supported during Fabric hot-swap.- Target class bytecode is resolved from the source cache / watch dirs, and duplicated Loom VM args
are removed. - A layout-prediction copy (ephemeral class loader) can no longer become the hot-swap target.
Before this fix, every round redefined a never-initialized copy: the log still said
HotSwap successful, but InitFix skipped the class (not initialized) and@OnReloadnever fired. - Stale same-mod jars in
run/modsare quarantined (renamed to.jar.remold-disabled) instead of
only warning, preventing the loader from silently running old bytes.
-
Failures are surfaced
- Post-processing failures (
InitFixpatches,@OnReload, cache eviction) are no longer swallowed by
the scheduler'sFuture; the entry point catchesThrowableand reports it. - Redundant dynamic redefines are skipped when the runtime bytecode is unchanged.
- Post-processing failures (
Fixed
- Layout-prediction copy stealing the hot-swap target ("field added but never applied").
- Silent post-processing failures after
redefineClasses. @Uniquefield add/remove during Fabric hot-swap.- Mixin target bytecode resolution from source cache / watch dirs; duplicated Loom VM args.
out/productionvsbuild/classesconflict viabuildModeand timestamp arbitration.- Loom run-config generation (
Make-based configs, IDEA build auto-detection fromgradle.xml). - Release packaging: pick the binary Gradle plugin jar (not the javadoc jar); skip example remold
integration when the local plugin jar is absent.
Added
:plugin-fabricmodule (remold-plugin-fabric.jar): loader integration (class path groups, stale-jar
inspection,modswatch exclusion) moved out ofcoreinto the genericHotswapPluginwindows
(onPremain,onWatchPathsChanged,isWatchPathIgnored).RemoldAgentsplit: layout decision, removed-member scanning, watch-path registry, watcher thread,
jar extraction and environment probing extracted into dedicated modules; public API unchanged.HotswapPlugingained three default methods;PluginWatchPathTestlocks down "no loader-specific
rules in core / plugin votes are honored / a throwing plugin does not break path decisions".- Kotlin DSL modernization and Javadoc/jump-link cleanups across all mod templates.
Verification
- CI: full test matrix (javac 8/11/17/21/25), DCEVM (JBR 21) live tests, and P0.5 probes on JBR 17/21.
Full Changelog: v0.2.1...v0.2.2
Remold v0.2.1
🚀 Remold v0.2.1 Release Notes
Remold v0.2.1 进一步拓展了模组开发生态,正式带来 Minecraft Forge & NeoForge 统一自适应热重载插件,并深度优化了 Fabric 开发工作流(解除 Gradle Daemon 独占锁定、自动化部署)与 IDE 原生单文件编译实时监听,带来极致流畅的原生热重载体验!
🌟 核心亮点 (Highlights)
-
Minecraft Forge & NeoForge 统一自适应插件 (
:plugin-forge):- 新增扩展插件
:plugin-forge(分发产物remold-plugin-forge.jar),提供remold.HotswapPluginSPI 驱动; - 支持
forge与neoforge双别名挂载,自动识别 ModLauncher 与 NeoForge 运行时环境; - 跨 ClassLoader 穿透重织入 (
ModLauncherMixinBridge):完美突破 ModLauncher / NeoForge 模块化加载层与 Agent 探针间的 ClassLoader 隔离,穿透执行 Mixin 增量重织入; - 事件总线自动刷新 (
ForgeEventBusRefresher):监听重定义批次,自动识别@SubscribeEvent/@EventBusSubscriber的监听器增删,并在MinecraftForge.EVENT_BUS/ NeoForge Bus 上自动注销旧监听器并重新注册; - Gradle 脚本(
remold.gradle/remold.gradle.kts)自动识别 Forge/NeoForge 生态(autoForge),并自动向 ModDevGradle(runs.client)与 ForgeGradle(minecraft.runs.client)注入探针参数。
- 新增扩展插件
-
Fabric Loom 原生 Application 启动与守护进程解绑:
- 模板默认配置
loom.runs.configureEach { preferGradleTask = false },解除游戏客户端对 Gradle 守护进程(Daemon)的排他占用,转为原生 Application 运行; - 自动在 IDEA
Minecraft Client与Minecraft Server启动项的 BeforeRunTask 中同步配置deployArtifact任务,运行前自动将最新构建产物同步至run/mods/; - 优化
Minecraft_Client.xml与Minecraft_Server.xml的 JVM 参数同步机制,使用Matcher.quoteReplacement原位更新-javaagent,杜绝重复追加与 regex 符号转义损坏。
- 模板默认配置
-
IDE 原生编译目录实时监控 (
out/production):- 脚本层自动探测并创建工程根目录下的
out/production,纳入-javaagent监控目录表; RemoldAgent增强:显式指定未建的out/production时自动创建,当监控包含build/classes/...时自动向上查找到项目根目录探查并挂载out/production;- 在 IDEA 中通过单文件编译快捷键(
Ctrl + Shift + F9)编译的 class 改动能直接在 200ms 内触发热重载,无需等待 Gradle 完整构建流程。
- 脚本层自动探测并创建工程根目录下的
-
测试套件与规范守卫:
- 新增
ForgePluginTest与WatchPathTest,测试基线提升至 403 个 JUnit 测试; - 规范状态标记扫描 (
verifyDocStatusMarkers) 将plugin-forge/src纳入守卫范围,全量代码与客观设计文档 100% 保持无易腐标记。
- 新增
🛠️ 快速上手 (Quick Start)
1. 声明 JitPack 仓库与 API 依赖
在你的 build.gradle / build.gradle.kts 中添加:
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
// 编译期 API 与热更控制注解 (@RemoldReinit, @OnReload)
compileOnly 'com.github.I-hope1.Remold:api:v0.2.1'
}2. 引入热重载 Gradle 脚本或插件
buildscript {
repositories { maven { url 'https://jitpack.io' } }
dependencies { classpath 'com.github.I-Hope1.Remold:gradle-plugin:v0.2.1' }
}
apply plugin: 'io.github.i-hope1.remold'或直接引入脚本插件:
def remoldRef = (findProperty('remold.version') ?: '0.2.1')
apply from: "https://raw.githubusercontent.com/I-hope1/Remold/v${remoldRef}/remold.gradle"3. 启动与热重载
- 执行
./gradlew runRemold或在 IDEA 中直接运行自动生成的Run with Remold/Minecraft Client启动项; - 保持进程运行,在 IDEA 中修改代码后按
Ctrl + Shift + F9,改动瞬间生效!
📦 资产列表 (Release Assets)
- JavaAgent 探针:
remold-agent.jar/remold-agent-0.2.1.jar
- 生态插件:
remold-plugin-forge.jar/remold-plugin-forge-0.2.1.jar(✨ 新增)remold-plugin-mixin.jar/remold-plugin-mixin-0.2.1.jarremold-plugin-arc.jar/remold-plugin-arc-0.2.1.jar
- Gradle 插件:
remold-gradle-plugin.jar/remold-gradle-plugin-0.2.1.jar
- API 与源码:
remold-api-0.2.1.jar
- 模版工程:
minecraft-fabric-template.zipmindustry-mod-template.zip
- Native 动态库:
libtool64.so(Linux x86_64)libtoolarm64.so(Android arm64-v8a)
📑 完整变更日志 (Changelog)
详见仓库内 CHANGELOG.md。
Full Git Commits: v0.2.0...v0.2.1
Remold v0.2.0
🚀 Remold v0.2.0 Release Notes
Remold v0.2.0 是一个里程碑式的重要更新版本!本次发布不仅将热重载能力延伸至 Minecraft Fabric (Mixin) 与 Mindustry (Arc 全套资源) 两大游戏模组生态,还在底层架构上补齐了字段类型动态变更、物理内存布局漂移安全门、第三方反射缓存驱逐 SPI、Lambda 单向捕获桥等核心安全防线,提供更加极致、零侵入、零重启的 Java 运行时开发体验。
🌟 核心亮点 (Highlights)
-
Minecraft Fabric 生态全面就绪 (
:plugin-mixin):- 深度集成 SpongePowered Mixin 字节码重织入桥;
- 支持在热重载时动态修改
@Mixin切入点与注入逻辑,无需重启 Minecraft 客户端; - 支持在 Mixin 类中使用
@Unique配合@RemoldReinit注入全新实例字段; - 随版本分发开箱即用的 minecraft-fabric-template。
-
Arc 全套多媒体与界面资源原位直刷 (
:plugin-arc):- PNG 贴图:Tier 1 原位直刷显存(
Gl.texSubImage2D),零重启修改界面与图集像素;支持运行期新增贴图动态注册; - 字体 / 语言包 / 音频 / 着色器:字体精确路由 UI 槽位,国际化 Bundle 安全回写内容缓存,音效音乐原位复用重载,内置着色器原位重编;
- 随版本分发开箱即用的 mindustry-mod-template。
- PNG 贴图:Tier 1 原位直刷显存(
-
字段类型变更(Field Type Changed)安全热重载:
- 当普通字段描述符类型变更(oldDesc != newDesc)且批次外无悬空读者时:无活体实例或标有
@RemoldReinit时安全放行; - 自动清除旧类型指纹缓存,重定义后由 InitFix 引擎安全写入新类型的初始值。
- 当普通字段描述符类型变更(oldDesc != newDesc)且批次外无悬空读者时:无活体实例或标有
-
物理内存布局预测器与漂移安全门 (
LayoutPredictor/SuperclassDriftGate):- 事前使用抛弃式 ClassLoader 干净预测物理布局(实测与 DCEVM 布局 100% 吻合);
- 结合堆存活探测,精准阻断父类增删字段引起的子类物理偏移漂移;
- 协同预注入整条非 JDK 祖先链,彻底防御 package-private 运行时包隔离访问异常。
-
第三方实例级反射与序列化缓存驱逐 SPI (
ReflectionCacheManager):- 内置 Jackson
ObjectMapper、GsonGson与 ArcJson实例级驱逐器; - 类重定义完成后自动清空解析缓存,防止反序列化错读陈旧字段元数据。
- 内置 Jackson
-
Lambda 表达式增强对齐与单向捕获桥 (
LambdaAligner):- 新增单向捕获桥,安全支持删除旧捕获变量;
- 支持新增捕获变量的纯源重放(字面量常量、
static final、final实例字段、可折叠算式、构造器参数回溯); - 引入 LVT 形参名定捕获身份,拦截同签名换序的静默错值。
🛠️ 快速上手 (Quick Start)
1. 声明 JitPack 仓库与 API 依赖
在你的 build.gradle / build.gradle.kts 中添加:
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
// 编译期 API 与热更控制注解 (@RemoldReinit, @OnReload)
compileOnly 'com.github.I-hope1.Remold:api:v0.2.0'
}2. 引入热重载 Gradle 插件
buildscript {
repositories { maven { url 'https://jitpack.io' } }
dependencies { classpath 'com.github.I-Hope1.Remold:gradle-plugin:v0.2.0' }
}
apply plugin: 'io.github.i-hope1.remold'3. 启动与热重载
- 执行
./gradlew runRemold(或在 IntelliJ IDEA 中直接使用自动生成的Run with Remold运行配置); - 保持进程运行,修改代码后按
Ctrl + Shift + F9,即刻体验秒级原位热更新!
📦 资产列表 (Release Assets)
- JavaAgent 探针:
remold-agent.jar/remold-agent-0.2.0.jar
- 生态插件:
remold-plugin-arc.jar/remold-plugin-arc-0.2.0.jarremold-plugin-mixin.jar/remold-plugin-mixin-0.2.0.jar
- Gradle 插件:
remold-gradle-plugin.jar/remold-gradle-plugin-0.2.0.jar
- API 与源码:
remold-api-0.2.0.jar
- 模版工程:
minecraft-fabric-template.zipmindustry-mod-template.zip
- Native 动态库:
libtool64.so(Linux x86_64)libtoolarm64.so(Android arm64-v8a)
📑 完整变更日志 (Changelog)
详见仓库内 CHANGELOG.md。
Full Git Commits: v0.1.3...v0.2.0
v0.1.3: Core Maven Publishing Fix & Multi-Tier Resolution
What's Changed in v0.1.3
🛠️ Maven & JitPack Artifact Fix
- Corrected core Maven Publication:
- Configured shadowJar to publish as the primary artifact without classifier (\classifier = null), eliminating the previous <packaging>pom\ issue where the 1.23 MB agent JAR was not resolved under \com.github.I-hope1.Remold:core:version.
- Also publishes raw unshaded artifact under classifier
aw\ (\core-0.1.3-raw.jar).
- Multi-Tier Resolution in Script Plugins:
- Both
emold.gradle\ and
emold.gradle.kts\ now support multi-tier agent and plugin resolution:- Standard Maven coordinate: \com.github.I-hope1.Remold:core:\
- Fallback classifier coordinate: \com.github.I-hope1.Remold:core::all\
- Plugin coordinates: \com.github.I-hope1.Remold:plugin-:\
- Direct GitHub Releases download fallback
- Both
- Fabric Loom Auto-Integration:
- Automatically hooks into \abric-loom\ projects (
uns.client.vmArg) so
unClient\ runs with Remold hot reload out-of-the-box.
- Automatically hooks into \abric-loom\ projects (
📦 Release Assets
emold-agent.jar\ /
emold-agent-0.1.3.jar: Universal Remold JavaAgent (JDK 11+ / JBR 17/21/25).
emold-plugin-arc.jar\ /
emold-plugin-arc-0.1.3.jar: Arc / Mindustry plugin extension.- \mindustry-mod-template.zip: Complete out-of-the-box modern Mindustry mod template.
- \minecraft-fabric-template.zip: Complete modern Minecraft Fabric 1.21.1 mod template with Loom auto-injection.