Skip to content

Releases

Remold v0.2.8

Choose a tag to compare

@github-actions github-actions released this 12 Nov 15:58

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/0 to 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 @Unique field read by an @Inject handler 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 configured remold.version and replaces a stale local jar. A new cleanRemoldCache task
    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.requiresTwoPhase and 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 generated runtimeBytes (no second transformer
    bridge pass, no second InitFix.transform). Method-body-only edits keep the single-redefine fast
    path. Covered by DynamicTwoPhaseRedefineTest (2/2, recorded Instrumentation asserts the call order
    and bytes; mutation twoPhase=false turns it red) and by the real-DCEVM
    redefineDynamicNoBaselineProbe (13/0, log two-phase: A -> patch -> C, existing instance b=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. void orphans stay no-op by default (-Dremold.agent.orphan_retain_void=true opts in). Retention
    logs once on first real execution and survives multiple rounds. Spec:
    docs/topology/11-orphan-lambda-body-retention.md; tests OrphanBodyRetentionTest 6/6.
  • Gradle plugin: agent version reconciliation + cleanRemoldCache. remold-agent.jar now writes
    Remold-Version/Implementation-Version into 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 explicit agentJar is only warned about. cleanRemoldCache clears the
    project's resolved agent/plugin copies and generated launch scripts; the cross-project shared download
    cache is kept unless --global is passed. It is deliberately not wired into clean.
  • JDK-internal packages opened by the agent. ModuleOpener uses Instrumentation.redefineModule in
    premain/agentmain to export/open nine JDK-internal packages to every unnamed module, so users no
    longer need --add-exports/--add-opens. An EVERYONE_MODULE fallback 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=allow escape 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 with ENUM_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, existing EnumMap/EnumSet instances that cannot be resized).
    The Mixin member aligner no longer swallows enum reorders by treating $VALUES and 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.md and D-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 a LocalVariableTable, captured parameter
    names are unavailable and reorder detection silently degrades. The aligner now warns explicitly and
    records missingLvtLambdas; -Dremold.agent.lambda_strict=true throws
    LambdaAlignmentStrictException ([HOTSWAP-REJECT], transaction rolled back so the class keeps its old
    version). Covered by LambdaLvtStrictGateTest 6/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 by ParameterAnnotationDriftTest and
    :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 throw IncompatibleClassChangeError);
    deleted constructors are checked against out-of-batch readers; -D environment-flag overrides are
    detected by the same gate. Covered by MemberStaticnessAndConstructorScanTest (7/7, 8/8).
  • Record components and @RemoldReinit extractability. Adding a Record component with live instances
    is refused; @RemoldReinit authorization 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
    RecordAndReinitExtractabilityGateTest 5/5.
  • @RemoldReinit contract tightened. -Dremold.agent.force_reinit is now a real escape hatch
    (default false), a per-field [REINIT] @RemoldReinit trigger on <class> line explains why a field was
    replayed, an inherited-scope gate refuses INCLUDE_SUBCLASSES when a registered subclass writes the
    same inherited field (scope = SELF is not blocked), and the Javadoc documents the "does not do" list
    and the exact CONDITIONAL semantics.
  • 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:verifyLogLanguage guard (wired into check) fails on CJK/full-width punctuation in
    main-source string literals.
  • Release-coordinate self-check is now case-sensitive. verifyReleaseCoordinates no 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 (matching git remote I-hope1). Verified: 16/16 coordinates resolve from
    mavenLocal, and both templates resolve gradle-plugin:v0.2.8 via help --offline.

Fixes

  • Linux aarch64 native library (UnsatisfiedLinkError). The platform matrix mapped Linux aarch64 to
    the Android NDK artifact (bionic ABI), which cannot load und...
Read more

v0.2.7 · Mixin 热更与对齐子系统收口

Choose a tag to compare

@I-hope1 I-hope1 released this 11 Nov 12:54

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: redefineClasses makes the VM re-lay-out instances and rewrites unrelated heaps.
    Measured on a real client ([观测]): one such redefine wiped
    LootContextPredicate.combinedCondition on 230 of 244 instances
    ([HEAPWATCH-DIFF] null{before=0 after=230 newlyNull=230}, both channels agree=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 including isStatic/isSynthetic) and reports it as gateRejected. 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 throws NoSuchMethodError. A new BytecodeTransformerBridge.Abort signal carries
    "do not redefine this class in this round" through the bridge chain: applyTransformerBridges
    rethrows it, redefineDynamic returns failure without calling redefineClasses, 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. BLOCKED is
    cleared per round, so a session recovers without a restart.
  • Reload structure contracts broken open. @Mixin(priority) (a final field inside a TreeSet),
    @Mixin(targets) (in-place metadata migration across four indexes with snapshot rollback instead of
    detach + re-register, which now trips MixinTargetAlreadyLoadedException) and interfaces (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 a MixinBytecodeOverlay
    that also swaps each MixinConfig's start-up-captured service, plus a pendingUnresolvedMixins relay
    (TTL + retry budget) for "mixins.json saved first, .class compiled seconds later". A pure-IDEA
    out/production/classes layout now hot-swaps without a second output directory and without a restart.
  • Access levels only ever move up. AccessAlign aligns a re-woven product against the JVM's loaded
    bytes (public/protected must not narrow, stripped ACC_FINAL must stay stripped) behind a
    fail-closed [ACCESS-GATE] self-check, and readOriginalBytes replays the loader's pre-Mixin
    transforms (PackageAccessFixer, AccessWidener, AccessTransformer). Before this, one hot-swap of
    MobEntity.isInAttackRange sent 112 members back to protected and the VM answered
    VerifyError: Bad access to protected data → InternalError.
  • A Mixin subsystem documentation set. docs/mixin/01..08 now mirrors docs/topology/: invariants
    and classloader authority, admission and the bytecode overlay, the re-weave pipeline and member
    alignment (including why baseline synthetic methods and @Unique fields 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 NullPointerException between redefineClasses and InitFix: 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=false restores the single-phase behaviour.
  • Mixin handler's first anonymous class: Error loading class: Outer$1 (ClassNotFoundException) followed
    by innerClass is null — the new bytes are offered to Mixin's own provider before reloadMixin, and
    the upstream negative ClassInfo cache entries are evicted.
  • Body-only edit of an already-loaded generated inner class: used to end in a silent
    [DYNAMIC-REDEFINE] no-op while the old body kept running; the synthetic class is now redefined in
    place and the trailing target no-op line 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, and ClassInfo.cache is re-anchored to
    MixinInfo.getClassInfo() after every reload/registration so transformSingleDescriptor keeps
    remapping this$0.
  • NoClassDefFoundError: LoadedTargetSuppressor under a plugin/core version skew: the suppressor now
    crosses the core boundary as plain JDK Function/Consumer method 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 @RemoldReinit escape 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 in BytecodeSources, and the diagnostic names the weaving-generated class.
  • A mixin name removed from *.mixins.json could be silently re-registered when its class file was
    rewritten; the removal now holds until the name is re-added (Forge/NeoForge included, via
    ModLauncherClassTrackerSpoof inside the prepare() window).
  • Batch stall watchdog: previously covered only the debounce batch; now arms through a scoped
    enter/exit API covering the manual triggerHotswap() entry and retransformLoaded as well.
  • Gradle plugin: agent JVM arguments were injected only into the loader client container, so
    ./gradlew runServer and the gametest runs started without -javaagent; all run containers now get
    the same arguments, and auto-deploy wires runServer.
  • Release tooling: JitPack publications published the bare version while the packaged shims and both
    templates request …:v<version>; publications now use the v-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, ReflectionCacheManager SPIs.
  • plugin-mixin: MixinBytecodeOverlay, MixinNewMixinBytes, MixinClassByteSources,
    MixinClassBaseline (premain (loader, dotName) recorder), MixinInnerClassHotSwap single exit,
    SyntheticAbortLedger, MixinSyntheticSchema schema views, MixinRetarget, MixinReloadPolicy.
  • docs/mixin/01..08 plus docs/topology/04/07/08 additions (Tier 3.5 body-token disambiguation,
    Tier 4.5 whole-group zero-scan pairing, findHoldingFields holder naming).
  • :tests:verifyTestCount baseline 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 CI Test Matrix leg reports
    the same JUnit 测试数 = 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: MixinSyntheticGateTest 8, MixinSyntheticLivenessTest 4,
    MixinSyntheticRetryAndFlagsTest 4, MixinSyntheticSchemaTest 4, MixinHotSwapWiringTest 3,
    BridgeAbortTransactionTest 2.
  • 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.
  • MixinSyntheticGateProbe on 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, P4a live{raw=1} refused, P...
Read more

Remold v0.2.6

Choose a tag to compare

@github-actions github-actions released this 09 Nov 11:44

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. redefineClasses returns before
    InitFix has written the newly added fields, so a reader in that window can observe null / 0 (a
    real NullPointerException on 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=false restores 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 Kotlin object /
    top-level val holder whose <clinit> runs Turret("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 new HotswapBridge.KIND_CONDITIONAL_EXPECTED protocol: 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 writes FieldLedger or changes patchGenerated.
  • Mixin anonymous-class / inner-class hot-swap. The first anonymous class introduced inside an
    @Inject handler used to kill the reload (Error loading class: Outer$1 (ClassNotFoundException),
    then innerClass is null); editing only the body of an already-loaded generated inner class used to
    end in a silent [DYNAMIC-REDEFINE] no-op while the old body kept running. Remold now feeds the new
    bytes into Mixin's own bytecode provider (MixinBytecodeOverlay, with the upstream negative
    ClassInfo cache entries evicted) and redefines the generated Target$Anonymous$<uuid> class in place
    (MixinInnerClassHotSwap, MixinInnerClassSupport). Weaving-generated classes can also register
    their bytes in core BytecodeSources, so Nest scanning and InitFix's constructor-visibility check
    resolve them instead of failing closed; refeedForTarget pre-generates a freshly woven target's
    synthetic inner classes after weaving and before the InitFix scan, then restores loadCounter so the
    platform still performs the real defineClass.
  • 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. api gained HotswapPlugin.isHostReady()
    (default true); StartupGate holds batches until the host says it is ready, with
    -Dremold.agent.ready_timeout_ms (default 0 = never releases on time). BatchWatchdog dumps all
    thread stacks once per batch with [HOTSWAP-STALL] when a batch exceeds
    -Dremold.agent.batch_stall_ms (default 15000, floor 5000); the funnel now covers the debounce
    batch, the manual triggerHotswap() entry and retransformLoaded.
  • Structured batch report, client HotSwap toast, template back on 1.21.1. api gained
    HotswapBatchReport and HotswapBatchListener.afterInternalBatchReport() — batch elapsed time, Mixin
    member deltas (with an explicit "delta unknown" state) and InitFix patch counts — populated by the new
    core BatchReportScope. 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 / .jar event 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 pure DebounceWindow state machine (unit-tested without sleeping)
    decides which stream is due; the manual triggerHotswap() entry still cancels the pending timer,
    clears both deadlines and force-drains all queues. The Gradle DSL gained assetShakeMs.

Fixes

  • Read-through NullPointerException: a reader running between redefineClasses and InitFix observed
    null / 0 for a newly added field. Closed on the internal batch path by the two-phase redefine
    above; the external IDE/JDWP and redefineDynamic channels still run single-phase and are listed
    under Known limits.
  • Mixin handler's first anonymous class: ClassNotFoundException for Outer$1 followed by
    Cannot invoke "ClassInfo.isProbablyStatic()" because "innerClass" is null no longer happens — the new
    bytes are offered to Mixin's bytecode provider before reloadMixin, and the negative ClassInfo
    cache entries are evicted.
  • Body-only edit of an already-loaded Mixin-generated inner class: used to end in
    [DYNAMIC-REDEFINE] no-op while 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: LoadedTargetSuppressor disabled 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 JDK Function / Consumer method 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 @RemoldReinit escape 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 in BytecodeSources, and the diagnostics name
    the weaving-generated synthetic class as the cause instead of failing unattributed.
  • A Mixin name removed from *.mixins.json could 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,
    ModLauncherClassTrackerSpoof removes target names from ModLauncherClassTracker.loadedClasses
    inside the prepare() window, restores them in finally, and fails closed when the tracker shape is
    unknown; ForgeMixinConfigDiff wires onAssetsChanged to the shared engine.
  • The batch stall watchdog previously covered only the debounce Class batch (manual triggerHotswap()
    and retransformLoaded were uncovered). It now arms through a scoped enter / exit API with at
    most one armed watchdog per batch.
  • Gradle plugin: the agent JVM arguments were injected only into the loader client run 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 wires runServer, and the IDEA Loom sync covers
    Minecraft_Server.xml as well as Minecraft_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 / mavenLocal resolution of the
    plugin dangled. Publications now use the v-prefixed version, panama gained 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; MixinChange with an explicit
    deltaKnown state, InitFixStats that 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 by HierarchyTreeOracle.readClassBytes), BatchWatchdog, StartupGate.
  • `tests/fabric-mixin...
Read more

Remold v0.2.5

Choose a tag to compare

@I-hope1 I-hope1 released this 07 Nov 16:12

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 @Mixin members hot-swap. Adding an @Inject handler method
    or a @Unique field to an existing @Mixin used to be rejected at reload
    (Conform error ... please restart client, then a ClassInfo$Method.isUnique() NPE) because
    MixinInfo.reloadMixin reuses the pre-reload ClassInfo. plugin-forge now adds the new
    members to that same ClassInfo before IMixinTransformer.reload and rolls them back if the
    reload is rejected. Every member lookup is resolved against the host ClassLoader's own
    FieldNode/MethodNode types, so a stale agent-side ASM copy cannot be matched.
  • One ClassInfo member-sync engine. core/src/remold/reflect/ClassInfoMemberSync.java is now the
    only implementation; plugin-mixin (Fabric) and plugin-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-UNNAMED argument
    instead of a bare reflective failure.

Fixes

  • plugin-mixin MixinClassInfoSync: 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 @Inject handler / @Unique field 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 raw IllegalAccessException / InaccessibleObjectException.

Added

  • tests/fabric-mixin-probe/forge-classinfo/: real Forge 1.21.1 dedicated-server E2E (stage 1 adds
    an @Inject handler, stage 2 adds a @Unique field) with a witness-based Verdict whose
    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 through META-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, including verifyDocStatusMarkers and verifyTestCount.
  • E2E, same probe sequence for red and green:
    • Forge 1.21.1: red VERDICT RED (stage 1 reload rejected with Conform error ... ClassInfo$Method.isUnique() ... "method" is null, stage 2 re-weave failed) -> green
      VERDICT GREEN (new handler +1 per call, new @Unique reads its initializer 456, existing
      members preserved). Re-run after the SSOT refactor: VERDICT GREEN again.
    • 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 GREEN with Mixin class-info sync: ... addedMethods=1 (stage 1) and addedFields=1
      (stage 2). No InaccessibleObjectException, no --add-opens needed 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 @Inject self-evolution probe re-run unchanged after the refactor -> RESULT PASS
      (failures=[]), with class-info sync ... addedMethods=1 for both handler additions and the
      added handler removed again in the final stage.
  • Mutation checks: reverse-applying the engine/adapter diff turns the unit cases red; reverting the
    NeoForge Verdict turns 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 point net.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-opens requirement must be re-tested there; the agent currently prints a precise
    diagnostic instead of attempting runtime unsealing (runtime redefineModule is recorded as the
    preferred follow-up in docs/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

Choose a tag to compare

@github-actions github-actions released this 07 Nov 07:18

v0.2.4 Release Notes (draft for commander review)

Status: released 2026-11-07 (v0.2.4).
Previous release: v0.2.3 (0036550).

Highlights

  • @RemoldReinit scope: new scope element with SELF / INCLUDE_SUBCLASSES (default preserves
    existing behaviour). SELF reinitializes only exact declaring-class instances; host Class is
    canonicalized so JBR's old-version "shadow" Class cannot 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-forge now drives IMixinTransformer.reload
    reflectively and cascades a re-weave + redefineDynamic to loaded targets (ForgeMixinHotSwap).
    Previously a changed @Mixin class was defined into the target loader while MixinInfo stayed
    stale, so @Inject bodies in targets never updated.
  • Mixin @Inject self-evolution: added/renamed @Inject handler methods now hot-swap.
    MixinClassInfoSync runs before reloadMixin (the conform check reads that metadata) and
    rolls back members when reload is rejected.
  • Profiler / entity probe robustness: ProfilerTransformer.transform uses per-call local state
    (reentrant, no shared-field clobbering); stale static-field Class refs are normalized via
    ClassIdentity.canonicalize; toggleEntityProbe catches Throwable and rolls back from a
    pre-call snapshot (failed enable restores the registration, failed disable re-registers).
  • Verification tooling: probeAll aggregate task (not in check) with zero exclusions; new
    seconds-scale probes for multi-mixin targets, @Inject evolution, sourceBytecodeCache real-path
    matrix, LayoutGate reject→recover, @RemoldReinit scope, shadow-Class identity.

Fixes

  • plugin-mixin: MixinInfo.reloadMixin validated new @Inject handlers against the previous
    ClassInfo, rejecting added/renamed handlers with Conform error ... please restart client;
    sync now happens before reload, with SyncToken rollback.
  • plugin-forge: Mixin template changes were not reloaded (targets kept old handlers); new
    ForgeMixinHotSwap reloads the template and cascades target re-weaving.
  • core profiler: shared mutable transformer state caused NullPointerException: this.classReader is null and intermittent ClassFormatError when ClassWriter.getCommonSuperClass re-entered the
    transformer; transform is now stateless per call.
  • core profiler/LibTool: static-field Class references rewritten by JBR redefinition (shadow
    Class with old fields, getInstances=0, silent no-op retransform) are canonicalized at API
    boundaries.
  • core layout: type-change reject summaries no longer claim the reader scan failed; the note is
    limited to REMOVED_FIELD.
  • plugin-arc: Injector used the package-private RemoldAgent.inst field across the
    app/bootstrap loader boundary (IllegalAccessError); it now uses public RemoldAgent.getInst().

Verification

  • JUnit: 461 tests, 0 skipped, 0 failures.
  • :tests:initFixOracle: all assertions pass.
  • ./gradlew probeAll (explicit, not wired into check): all Gradle-hosted JVM probes pass,
    including alignBaselineProbe and offsetStabilityProbe (47/47). Aggregate probes include
    scopeReinitProbe 35/0, cacheSourceMatrixProbe 23/0, layoutRejectRecoverProbe 41/0,
    doubleBraceProbe 80/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
    probeAll but run separately with their own evidence files.
  • Mutation checks (seconds-scale, recorded in evidence): scope exact-class filter removal → red;
    cache first-load write / putIfAbsent removal → red; layout live-instance reject removal → red;
    cancellable revert 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 @Unique fields are merged
    into a single field (Fabric sponge-mixin disambiguates to <sessionId>$<modId>$<name>$0).
    Remold's re-weave keeps the merged field stable; this is upstream behaviour, not a Remold defect.
  • Forge ClassInfo member sync for newly added Mixin members is not implemented on the Forge
    side (Fabric covers it via MixinClassInfoSync); structural contract changes still require a
    restart.
  • NeoForge Mixin compatibility is not verified end-to-end. NeoForgeHotswapPlugin and the
    shared ModLauncherMixinBridge/ForgeMixinHotSwap code path exist, but there is no NeoForge
    MDK probe and no real runServer evidence: NeoForge mixin re-weaving, template hot update,
    @Unique/@Inject retention and mixin-config registration are all unverified on NeoForge
    (tracked as 🔶 in docs/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/offsetStabilityProbe are JBR-sensitive and require
    -XX:+AllowEnhancedClassRedefinition. offsetStabilityProbe Probe 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 adjacent s2=200L slot, which is an
    environment difference, not offset corruption. probeAll must be run with JBR 25.0.2
    (-Pp05.jbr=<jbr-25.0.2>); Probe D/D2 now poll compilation.log for 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 only remold/ + libs/.
  • plugin-{fabric,forge,mixin,arc}/build/libs/remold-plugin-*-0.2.4.jar.

Remold v0.2.3

Choose a tag to compare

@github-actions github-actions released this 06 Nov 13:37

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

  1. 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 @Unique fields.
    • Those duplicates were written into the baseline cache; the next real hot-swap was then rejected by
      LayoutGate as field(s) removed, and the edit silently stopped taking effect.
    • Baseline capture now uses the JVM input bytes directly and never re-weaves them.
  2. Entity-probe toggle no longer pollutes the hot-swap baseline

    • DynamicProfilerAPI.toggleEntityProbe retransforms loaded subclasses; for Mixin targets that went
      through the same external channel and double-wove them.
    • The retransform now runs inside RemoldAgent.enterInternalRetransform (exitInternalRedefine in
      finally): the profiler still instruments and un-instruments, but AnnotationTransformer leaves
      the class and caches untouched.
  3. 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 / @Inject survive the cross-ClassLoader re-weave and InitFix patches the live instance.
    • Evidence and probe sources live under tests/fabric-mixin-probe/.

Fixed

  • Mixin baseline double-weave (duplicate @Unique fields -> LAYOUT-REJECT on the next edit).
  • Entity-probe retransform corrupting the baseline cache.
  • redefineDynamic without bytecodeCache: baseline now falls back
    bytecodeCache -> fetchOriginalBytecode -> sourceBytecodeCache, and fails closed when all are missing.
  • No-op redefineDynamic is now visible: the "runtime bytes equal baseline, return true but nothing
    was redefined" branch logs an explicit [DYNAMIC-REDEFINE] no-op line instead of being DEBUG-gated.
  • Retry bookkeeping / inject-conflict false positives: per-class retry bookkeeping moved out of the
    redefine catch; injectNewClass verifies already-loaded bytes before treating a LinkageError as success.
  • Source baseline rollback and raw-byte fingerprints: classes that did not actually apply roll back
    sourceBytecodeCache; disk fingerprints now hash the raw Files.readAllBytes output, not aligned/woven bytes.
  • Offset drift: inherited fields of descendants are detected; drift bindings dedup by
    declaringClass#field; LayoutPredictor normalizes 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: InitializerDrift compares <init> slices per existing field and
    reports INIT-DRIFT when a field is still present but its initializer changed. Includes an instance gate,
    inherited-field wording, aggregated warnings, and INSTANCE_ENUMERATIONS diagnostics.
  • 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, redefineDynamic no-op and fail-closed cases.

Verification

  • JUnit: 446 tests, 0 skipped, 0 failures, 0 errors.
  • Probes: initFixOracle 350/0, doubleBraceProbe 80/0, retryBookkeepingProbe 15/0,
    injectConflictProbe 8/0, fingerprintDiskHashProbe 18/0, redefineDynamicNoBaselineProbe 13/0,
    pendingAlignedMultiLoaderProbe 5/0, retransformBaselineProbe 10/0.
  • MixinProbe exit-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

Choose a tag to compare

@github-actions github-actions released this 05 Nov 11:09

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

  1. Binary Gradle plugin + Plugin Portal

    • New :gradle-plugin binary plugin (io.github.i-hope1.remold) with a type-safe RemoldExtension
      DSL and configuration-cache support.
    • The root remold.gradle / remold.gradle.kts scripts 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.
  2. Fabric / Mixin hot-swap correctness

    • @Unique field 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 @OnReload never fired.
    • Stale same-mod jars in run/mods are quarantined (renamed to .jar.remold-disabled) instead of
      only warning, preventing the loader from silently running old bytes.
  3. Failures are surfaced

    • Post-processing failures (InitFix patches, @OnReload, cache eviction) are no longer swallowed by
      the scheduler's Future; the entry point catches Throwable and reports it.
    • Redundant dynamic redefines are skipped when the runtime bytecode is unchanged.

Fixed

  • Layout-prediction copy stealing the hot-swap target ("field added but never applied").
  • Silent post-processing failures after redefineClasses.
  • @Unique field add/remove during Fabric hot-swap.
  • Mixin target bytecode resolution from source cache / watch dirs; duplicated Loom VM args.
  • out/production vs build/classes conflict via buildMode and timestamp arbitration.
  • Loom run-config generation (Make-based configs, IDEA build auto-detection from gradle.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-fabric module (remold-plugin-fabric.jar): loader integration (class path groups, stale-jar
    inspection, mods watch exclusion) moved out of core into the generic HotswapPlugin windows
    (onPremain, onWatchPathsChanged, isWatchPathIgnored).
  • RemoldAgent split: layout decision, removed-member scanning, watch-path registry, watcher thread,
    jar extraction and environment probing extracted into dedicated modules; public API unchanged.
  • HotswapPlugin gained three default methods; PluginWatchPathTest locks 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

Choose a tag to compare

@github-actions github-actions released this 03 Nov 08:47

🚀 Remold v0.2.1 Release Notes

Remold v0.2.1 进一步拓展了模组开发生态,正式带来 Minecraft Forge & NeoForge 统一自适应热重载插件,并深度优化了 Fabric 开发工作流(解除 Gradle Daemon 独占锁定、自动化部署)与 IDE 原生单文件编译实时监听,带来极致流畅的原生热重载体验!


🌟 核心亮点 (Highlights)

  1. Minecraft Forge & NeoForge 统一自适应插件 (:plugin-forge):

    • 新增扩展插件 :plugin-forge(分发产物 remold-plugin-forge.jar),提供 remold.HotswapPlugin SPI 驱动;
    • 支持 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)注入探针参数。
  2. 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 符号转义损坏。
  3. IDE 原生编译目录实时监控 (out/production):

    • 脚本层自动探测并创建工程根目录下的 out/production,纳入 -javaagent 监控目录表;
    • RemoldAgent 增强:显式指定未建的 out/production 时自动创建,当监控包含 build/classes/... 时自动向上查找到项目根目录探查并挂载 out/production;
    • 在 IDEA 中通过单文件编译快捷键(Ctrl + Shift + F9)编译的 class 改动能直接在 200ms 内触发热重载,无需等待 Gradle 完整构建流程。
  4. 测试套件与规范守卫:

    • 新增 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.jar
    • remold-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.zip
    • mindustry-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

Choose a tag to compare

@github-actions github-actions released this 02 Nov 15:45

🚀 Remold v0.2.0 Release Notes

Remold v0.2.0 是一个里程碑式的重要更新版本!本次发布不仅将热重载能力延伸至 Minecraft Fabric (Mixin) 与 Mindustry (Arc 全套资源) 两大游戏模组生态,还在底层架构上补齐了字段类型动态变更、物理内存布局漂移安全门、第三方反射缓存驱逐 SPI、Lambda 单向捕获桥等核心安全防线,提供更加极致、零侵入、零重启的 Java 运行时开发体验。


🌟 核心亮点 (Highlights)

  1. Minecraft Fabric 生态全面就绪 (:plugin-mixin):

    • 深度集成 SpongePowered Mixin 字节码重织入桥;
    • 支持在热重载时动态修改 @Mixin 切入点与注入逻辑,无需重启 Minecraft 客户端;
    • 支持在 Mixin 类中使用 @Unique 配合 @RemoldReinit 注入全新实例字段;
    • 随版本分发开箱即用的 minecraft-fabric-template。
  2. Arc 全套多媒体与界面资源原位直刷 (:plugin-arc):

    • PNG 贴图:Tier 1 原位直刷显存(Gl.texSubImage2D),零重启修改界面与图集像素;支持运行期新增贴图动态注册;
    • 字体 / 语言包 / 音频 / 着色器:字体精确路由 UI 槽位,国际化 Bundle 安全回写内容缓存,音效音乐原位复用重载,内置着色器原位重编;
    • 随版本分发开箱即用的 mindustry-mod-template。
  3. 字段类型变更(Field Type Changed)安全热重载:

    • 当普通字段描述符类型变更(oldDesc != newDesc)且批次外无悬空读者时:无活体实例或标有 @RemoldReinit 时安全放行;
    • 自动清除旧类型指纹缓存,重定义后由 InitFix 引擎安全写入新类型的初始值。
  4. 物理内存布局预测器与漂移安全门 (LayoutPredictor / SuperclassDriftGate):

    • 事前使用抛弃式 ClassLoader 干净预测物理布局(实测与 DCEVM 布局 100% 吻合);
    • 结合堆存活探测,精准阻断父类增删字段引起的子类物理偏移漂移;
    • 协同预注入整条非 JDK 祖先链,彻底防御 package-private 运行时包隔离访问异常。
  5. 第三方实例级反射与序列化缓存驱逐 SPI (ReflectionCacheManager):

    • 内置 Jackson ObjectMapper、Gson Gson 与 Arc Json 实例级驱逐器;
    • 类重定义完成后自动清空解析缓存,防止反序列化错读陈旧字段元数据。
  6. 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.jar
    • remold-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.zip
    • mindustry-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

Choose a tag to compare

@I-hope1 I-hope1 released this 24 Oct 09:54

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:
      1. Standard Maven coordinate: \com.github.I-hope1.Remold:core:\
      2. Fallback classifier coordinate: \com.github.I-hope1.Remold:core::all\
      3. Plugin coordinates: \com.github.I-hope1.Remold:plugin-:\
      4. Direct GitHub Releases download fallback
  • Fabric Loom Auto-Integration:
    • Automatically hooks into \ abric-loom\ projects (
      uns.client.vmArg) so
      unClient\ runs with Remold hot reload out-of-the-box.

📦 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.