QuinnAddon · module handoff

Speedmine for QuinnAddon

THM-Addons' packet miner, ported to Yarn mappings and QuinnAddon's conventions, with nine defects of the original fixed.

Source
THM 26.2
Lines in
1,507
Lines out
1,413
Fixes
9
Mixins
3
Compiled
clean

What was done

What you get

FileWhat it is
port-only/
Speedmine.java
The original behaviour, only reshaped so it builds inside QuinnAddon. Keep it as the reference point.
fixed/
Speedmine.java
The same module with nine defects fixed. This is the one to ship.
changes.diff249 lines. Exactly what differs between the two, so the fixes can be reviewed on their own.
mixin/ClientPlayerInteraction
ManagerAccessor.java
Required. Puts the client's prediction sequence number on the dig packets.
mixin/ClientWorld
Accessor.java
Required. Lets a packet-triggered rebreak record what the server really has, so a rejected rebreak rolls back to the block and not to air.
mixin/PlayerMove
C2SPacketAccessor.java
Needed only by the Air Miner group.

The code itself

Both versions, the diff between them and the two mixins are on their own page, in full, with a raw link beside each for copying.

Open the source →

Credit and licence

The THM file ships with this header, and it is a condition of use, not a courtesy:

Copyright (c) THM Addons contributors. Credit the devs, keep the link.
By using this code you agree to the license terms and to keep your repo public.

So the port keeps a header pointing back at Magnus-Krill/THM-Addons, and the module description credits them. QuinnAddon is already public, so the rest is satisfied. Please don't strip that header. Credit also to mushek, named in the original, and to BlackOut, whose AutoMine the target picker follows.

Install

Four files go into the repo, two existing files get one line each, then you build. Download them from the source page — the raw link beside each file is the plain text.

QuinnAddon/
  src/main/java/com/example/addon/
    modules/Speedmine.java  ← from fixed/
    mixin/ClientPlayerInteractionManagerAccessor.java
    mixin/ClientWorldAccessor.java
    mixin/PlayerMoveC2SPacketAccessor.java
    QuinnAddon.java  ← edit
  src/main/resources/addon-template.mixins.json  ← edit

  1. Drop the four files in

    Speedmine.java from fixed/ goes in modules/, the three accessors go in mixin/. Take the one from fixed/, not port-only/ — they have the same class name, so only one of them can be in the repo at a time.

  2. Register the mixins

    In src/main/resources/addon-template.mixins.json, add the three names to the client array. Mind the commas.

    "client": [
        "BetterTabPlusMixin",
        "ClientPlayerInteractionManagerAccessor",
        "ClientWorldAccessor",
        "PlayerMoveC2SPacketAccessor"
    ],
  3. Register the module

    In QuinnAddon.java, one import at the top and one line in onInitialize, next to the others.

    import com.example.addon.modules.Speedmine;
    ...
    modules.add(new Speedmine());
  4. Build

    From the repo root. The jar lands in build/libs/; the one you want is the plain QuinnAddon-<version>.jar, not the -sources one.

    ./gradlew build
  5. Run it

    Put that jar in your mods folder beside Meteor, and delete the older QuinnAddon jar first or you will load two copies. In game the module is under the QuinnAddon category as speedmine.

Two of the three mixins are required: vanilla's sendSequencedPacket is private and ClientWorld.getPendingUpdateManager() is package-private, and the miner needs both to put a sequence number on a dig packet and to keep rebreak rollbacks honest. PlayerMoveC2SPacketAccessor is only for the Air Miner — drop that settings group and you can leave that one out.

If the build fails on a mixin, the usual cause is a name missing from the client array or a comma in the wrong place, not the Java.

Why it isn't a copy-paste

THM builds against Mojang mappings; QuinnAddon uses Yarn. Every Minecraft type and method differs, so not one line compiles as-is. On top of that, THM leans on its own support classes, none of which exist here. Each one had to be replaced — this is true even of port-only.

THMQuinnAddon
Mojang mappingsYarn — mc.level → mc.world, ServerboundPlayerActionPacket → PlayerActionC2SPacket
InventoryManagera local server-slot tracker over UpdateSelectedSlotC2SPacket
RangeUtilsisInRange() / closestFace(), in the file
RenderUtilsTHMRender3DEvent.renderer.box()
hand-written
mining formula
BlockUtils.getBreakDelta(slot, state) — Meteor already had it
ClientLevel mixinMeteor's BlockUpdateEvent
sequenced-packet
accessor
same idea, our own invoker on ClientPlayerInteractionManager
PaketLimiter,
ThmMembers, THMSystem
dropped — THM-only
fromTag() migrationdropped — no legacy Speedmine settings to migrate

One of those is worth a second look. THM wrote the vanilla mining formula out by hand, in two places that had drifted apart; Meteor ships the same calculation, so the port is shorter and can't disagree with itself. The world-update mixin also disappeared, since Meteor fires BlockUpdateEvent for exactly that.

One behavioural difference to watch. THM pauses mining through its InventoryManager priority system, so eating and other modules can hold the inventory. QuinnAddon has no shared arbiter, so this port falls back to mc.player.isUsingItem(). If AutoTotemPlus and Speedmine start fighting over the hotbar slot, that's where a shared lock belongs.

Where this differs from THM

All 35 settings are here; none dropped, none added. These are the behavioural differences, and each one exists because QuinnAddon does not have the system THM leans on. Worth knowing before you compare the two side by side.

The nine fixes

  1. A declined click did nothing at all

    The break event was cancelled before deciding whether to mine the block. With multitask off and an item in use, left-clicking a block neither packet-mined it nor fell back to vanilla mining. handleBlockClick now reports whether it took the block, and the event is cancelled only then.

  2. requestBreak could stall the whole module

    It only checked for air. An unbreakable block passed in by another module produced a context with zero progress that the removal check never cleared, so the primary slot stayed occupied forever. It now validates the block, and any context making no progress for 3s is dropped. The original's unused startMs field looks like this timeout was planned and forgotten — it is used now.

  3. One queue insert stored a mutable BlockPos

    Three of the four inserts called immutable(); one didn't. Harmless with today's callers, and silent corruption the day a MutableBlockPos reaches it.

  4. A replaced block kept being mined as the old one

    The context holds the BlockState it started on while the removal check reads the live world, so swapping the target block left it mining with the old hardness and firing the old block's break particles.

  5. Surround ring sat one block too high

    Math.round on the enemy's Y, so standing on a slab, snow layer or soul sand shifted the whole ring up. Floored now.

  6. Dead players were still targeted

    isSpectator was checked, isAlive was not, so the picker kept digging around a corpse for the whole death animation.

  7. The tool search could never return "no tool"

    It started at -1, which an empty hand already beats, making every no-tool branch dead code. Ties also went to the lowest hotbar index, so it swapped away from an equally good tool already in hand. It now starts from the selected slot, which also means fewer slot packets.

  8. One timeout, written twice

    The rebreak deadline existed as 2000 ms in one place and 2_000_000_000 ns in another. One constant now, the other derived.

  9. Outline ignored rebreak-mode Off

    Every other rebreak check tested the mode; the "last broken block" outline didn't, so it drew with rebreaks disabled.

Left alone on purpose

Build check

Both files compile against Minecraft 1.21.11, Yarn 1.21.11+build.3 and meteor-client:1.21.11-SNAPSHOT. gradlew build runs clean through compileJava, jar and remapJar, and the produced jar carries Speedmine.class and both accessors.

One real problem surfaced on the way, and it is the reason the second mixin exists: PendingUpdateManager lives in net.minecraft.client.network, not client.world, and ClientWorld.getPendingUpdateManager() is package-private. The first attempt reached for it directly and could not. Everything else that looked mapping-risky — PlayerEntity.isGliding(), PlayerMoveC2SPacket.Full(...), PlayerInventory.getSelectedSlot() — was right first time.

What this does not cover: nothing has been run in-game. The mixins compile and are listed in the config, but whether they apply at runtime, and how the miner behaves against a live anticheat, is still for you to test.

On the insta rebreak question

The idea post asked whether this is on FutureClient / Mio speedmine level, specifically at insta rebreak. Having read the whole file: the rebreak path is the most worked-on part of it.

Whether it actually matches those clients is measured, not read, and none of this has been run in-game. But the answer is one setting away: turn on debug-rebreak, run the others the same way, and compare.

Worth doing in QuinnAddon

Three things I ran into while porting. None of them block this module; the first will bite eventually.