QuinnAddon · module handoff
THM-Addons' packet miner, ported to Yarn mappings and QuinnAddon's conventions, with nine defects of the original fixed.
compileJava, jar, remapJar — and the jar carries the module and both accessors.| File | What 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.diff | 249 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. |
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.
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.
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
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.
In src/main/resources/addon-template.mixins.json, add the three names to the
client array. Mind the commas.
"client": [
"BetterTabPlusMixin",
"ClientPlayerInteractionManagerAccessor",
"ClientWorldAccessor",
"PlayerMoveC2SPacketAccessor"
],
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());
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
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.
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.
| THM | QuinnAddon |
|---|---|
| Mojang mappings | Yarn — mc.level → mc.world, ServerboundPlayerActionPacket → PlayerActionC2SPacket |
| InventoryManager | a local server-slot tracker over UpdateSelectedSlotC2SPacket |
| RangeUtils | isInRange() / closestFace(), in the file |
| RenderUtilsTHM | Render3DEvent.renderer.box() |
| hand-written mining formula | BlockUtils.getBreakDelta(slot, state) — Meteor already had it |
| ClientLevel mixin | Meteor's BlockUpdateEvent |
| sequenced-packet accessor | same idea, our own invoker on ClientPlayerInteractionManager |
| PaketLimiter, ThmMembers, THMSystem | dropped — THM-only |
| fromTag() migration | dropped — 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.
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.
isUsingItem().
If AutoTotemPlus and Speedmine end up fighting over the hotbar slot, that is where a shared
lock belongs.sendSequencedPacket and assumes success. Nothing in QuinnAddon cancels those
packets today, but a module that did would make the miner believe it had started.getBreakDelta does everything THM's
hand-written formula did except the BLOCK_BREAK_SPEED attribute, which THM
multiplied in. That attribute is 1.0 unless something changes it, so it costs nothing on a
normal server.fromTag() rewrites its own older
configs. There is nothing here to migrate.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.
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.
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.
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.
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.
Dead players were still targeted
isSpectator was checked, isAlive was not, so the picker kept digging around a corpse for the whole death animation.
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.
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.
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.
tps-sync still defaults off. With it off, progress is timed as wall clock × 20 ticks, so STOP goes out too early on a lagging server. Deriving break-threshold from measured ping, or syncing automatically below ~19 TPS, is the real answer — but that's a feature, not a fix.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.
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.
Strict waits for server confirmation between
rebreaks. Strong skips the confirmation wait and the TPS start delay entirely.
Bypass runs Strict until one rebreak is confirmed and then switches to Strong,
which is an anticheat-aware ramp rather than a flat setting.rebreak-trigger on
OnPacket it reacts to the server's block update as it arrives instead of waiting
for the next client tick. That is where the speed comes from.debug-rebreak counts
server-confirmed rebreaks per second and shows the figure beside the module name. Whoever
wrote it was tuning against a number, not a feeling.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.
Three things I ran into while porting. None of them block this module; the first will bite eventually.
SurroundPlus and
LogStripper each swap slots on their own, and Speedmine now tracks its own server
slot too, because there is no shared place to ask. In a fight those run at the same time —
placing while mining is the whole point of the module — so sooner or later one swaps out from
under another, and in cpvp that is a death rather than a glitch. THM handles it with a single
InventoryManager that every module asks before swapping. It is also the one difference in this
port that cannot be fixed from inside the module.fabric.mod.json carries
"id": "addon-template", so any other addon built from the unmodified Meteor
template collides with it. sources still points at
MeteorDevelopment/meteor-addon-template as well.build.gradle.kts and
libs.versions.toml both say 0.2.3 while the current tag is
v0.4.0, so a jar built from main reports the wrong version.