TickMender
TickMender is the fix and performance mod of the Nytheria modpack (Fabric, Minecraft 1.21.1, about 415 mods). It holds 37 small repairs for crashes, lost data, memory leaks, lag and slow starts in the game and in other mods, for cases where upstream has no fix.
Every repair has its own switch, stays idle when its target mod (or the exact version it was written for) is not installed, and returns what the original code returns: the same world, the same gameplay, the same picture.
What it is, and what it is not
- It was built for Nytheria's mod set. Most fixes target one named mod, often one exact version, and do nothing anywhere else.
- A few fixes are for the game itself, for Fabric API or for PuzzlesLib. Those are active in any pack that has the target (see Compatibility).
- It is not a general optimiser like Sodium or Lithium, and it does not replace them.
- It changes no gameplay rule: no spawn rates, no mob caps, no tick rules, no view or simulation distance. It does not change world generation or visuals.
- The numbers below were measured with Nytheria on one PC. They are not a promise for another pack.
What it fixes
The name in the first column is the fix's switch in config/tickmender.json.
Crashes and lost data
| Switch |
Target |
What changes |
aetherAercloudSave |
The Aether 1.5.11 |
No lost chunk save (ConcurrentModificationException) while a large aercloud is placed and saved at the same time. |
merchantShutdownSave |
Minecraft (seen with Repurposed Structures 7.5.22) |
Wandering traders and villagers survive a server stop. The shutdown save no longer rolls their trades. |
almostUnifiedTagReloadLock |
AlmostUnified 1.4.2 |
The server no longer fails to start now and then with Failed to load datapacks. The tag reload runs once per data pack load and on one thread. |
betterCavesCarvedChunkSet |
YUNG's Better Caves 3.1.6 |
The set of carved chunks, which all worldgen threads write without a lock, is replaced by one that is safe to use that way. |
Memory leaks at world close
A closed singleplayer world, or a stopped server, used to stay in memory for the rest of the game run: about 235 MB per world opened and closed. Five fixes let it go.
| Switch |
Target |
What changes |
stoppedServerCriteria |
Minecraft |
Advancement criteria listeners of player objects that never leave (Fabric API's fake players) are cleared when the server has stopped. |
stoppedServerFakePlayers |
Fabric API 0.116.17 |
The fake player map no longer holds the worlds of a stopped server. |
stoppedServerModStatics |
Open Parties and Claims, Eternal Starlight, Porting Lib, Polytone, What Are They Up To, Bad Packets, Connectivity, BCLib, Xaero's library, WorldWeaver, Twilight Forest, Tide |
Static fields and collections that keep a stopped server, its worlds, its players or its registries are released. |
closedWorldRenderContext |
Fabric API 0.116.17, and Sophisticated Building, Obscura's Simple Storage, Euphoria Patcher, Eternal Starlight, Simply Swords (client) |
The client world that was left is let go on the title screen. |
stoppedServerBiomeSearchCache |
Minecraft |
The per-thread cache of the biome search no longer keeps the registries of every closed world. |
Server tick
| Switch |
Target |
What changes |
puzzlesLibDispatchCache |
PuzzlesLib 21.1.x |
Event dispatch is about 100 times cheaper per listener call. It was 20 to 30 % of a busy server tick. |
astralTickProcedures |
Astral Dimension 3.0 |
Three per-entity tick procedures run without their lookups and chunk loads. |
betterEndIslandArenaWait |
YUNG's Better End Island 3.1.2 |
The dragon arena is no longer generated on the server thread at the first visit of the End. |
twilightForestSpawnLookup |
Twilight Forest 4.8.734 |
The spawn-list hook runs without the ticketed structure lookup. |
chunkSendingExpiryScan |
Chunk Sending 3.9 |
The packet cache is scanned for expired entries every five seconds, not every tick. |
goblinTradersKeyCache |
Goblin Traders 1.11.2 |
The storage key is built without a regular expression per tick. |
artifactsDoubleJumpRead |
Artifacts Remastered 1.0.0 |
No chunk load on the server thread from a block read in a chunk that is not there yet. |
tideInfoBlockScan |
Tide 2.1.1 |
The same, for Tide's scan for information blocks. |
fuelRegistryApplyOnce |
Fabric API 0.116.17 |
The fuel registry is applied once per fuel map, not on every fuel query. EMI's fuel index at a world join took 10 to 44 s before. |
structurePlaceFilter |
Minecraft |
A large structure template is processed only for the blocks of the chunk that is generated, not in full for every chunk it overlaps. |
World generation (flying over new terrain)
| Switch |
Target |
What changes |
structureTemplateCacheHit |
Minecraft (matters with ModernFix 5.25.1) |
A structure template that is already loaded is answered without the cache's lock. |
puzzlesLibConfigHolderLookup |
PuzzlesLib 21.1.62 |
A config lookup no longer builds a message text for every call. |
terraBlenderRuleMemo |
TerraBlender 4.1.0.8 |
The surface rule of a biome's namespace is looked up once per rule instead of twice per block. |
betterCavesNoiseCubes |
YUNG's Better Caves 3.1.6 |
The noise cubes of a chunk's carving are written into arrays a worldgen thread keeps. They were 11.5 % of all garbage of a flight. |
betterCavesThresholds |
YUNG's Better Caves 3.1.6 |
A column's thresholds no longer need a new map per column. That was 3.5 % of all garbage of a flight. |
Client frame time
| Switch |
Target |
What changes |
betterGrassifySnowNeighbour |
BetterGrassify 1.8.7 |
The "better snow" check of a chunk mesh runs without its garbage (47 % of the client's garbage while flying) and reads the four neighbours once per call. |
irisSameDimensionReload |
Iris 1.8.14 |
Off by default. No rebuild of the shader pipeline for a respawn in the same dimension or for entering a world again. It stays off until its effect on the picture has been checked by eye. |
Start-up, world load and resource reload
| Switch |
Target |
What changes |
ringPlacementExecutor |
Minecraft |
No 30 s stall while the worlds are created. World load on a warm boot: 6 to 8 s instead of 34 to 47 s in most boots. |
zipPackListingIndex |
Minecraft |
A zip resource pack or data pack is walked once per open file, not once per listing. |
craftTweakerScanCache |
CraftTweaker 21.0.38 |
The scan of every jar on the class path (4 to 6 s) runs once per set of jars instead of at every start. The answer is kept in .cache/tickmender/ and checked against the jars before it is used. |
athenaModelDataMemo |
Athena 4.0.6 (client) |
Athena's lookup per baked model is answered once per block. |
betterEndFlowerPotModels |
BetterEnd 30.5.0 only (client) |
Each flower pot model is built once per reload instead of 4 352 times. Idle with BetterEnd 30.5.1, which repairs it at the source. |
betterGrassifyModelHook |
BetterGrassify 1.8.7 (client) |
The configured block ids are parsed and looked up once per model loader instead of once per id and block state model. |
twilightForestModelEvents |
Twilight Forest 4.8.734 (client) |
No identifier is built per jar lid for a baked model that cannot be a lid. |
blockStateModelIdMemo |
Minecraft (client) |
The model id of a block state is built once per reload instead of three times. |
moreCullingSpriteScanMemo |
More Culling 1.0.10 (client) |
Each part of a texture is scanned for translucent pixels once per run of the mod's reload listener. |
Log filter
logFilter keeps known, harmless WARN and ERROR lines of other mods out of the log, so real problems are easier to see. It only looks at the message text of WARN and ERROR lines. The 57 patterns are in config/tickmender-logfilter.json, you can edit them, and every pattern has a hit counter. A summary of the hits is written when a server has stopped, when a world was left and when the client exits.
The pattern list was written for Nytheria. In another pack, read the list before you rely on the log, or switch logFilter off.
JVM arguments hint
jvmArgumentsHint (client) is not a code fix. A player who starts the game without the recommended JVM arguments is shown them once on the title screen, with the place to paste them, a Copy button, Done and Do not show again. The mod only shows the line. It never changes launcher settings and never writes into the launcher's files, and the line never contains -Xmx or -Xms.
Measured results
Measured on the pack author's PC (Intel i5-14600KF, 64 GB RAM, RX 9070 XT) with Nytheria, Java 21 and a 10 GB heap. Most cells are one run, on a PC that was not idle, so treat differences below about 10 % as noise.
Dedicated server, with and without TickMender
Same server, same conditions: four simulated players, simulation and view distance 10, 10 GB heap. The only difference is the TickMender jar. These runs used an earlier build with 15 of the 37 fixes (all ten server tick fixes among them).
| Scenario |
Without |
With |
| 4 players standing: tick time, median / 95th percentile |
27.6 / 56.6 ms |
19.5 / 46.7 ms |
| 4 players exploring new terrain: TPS |
17.5 |
19.5 |
| 4 players exploring new terrain: ticks over 50 ms |
40 % |
12 % |
| Travel through the dimensions: longest tick |
5 280 ms |
2 954 ms |
| Travel through the dimensions: heap after garbage collection, highest |
10.2 GB |
5.0 GB |
| Travel through the dimensions: longest garbage collection pause |
647 ms |
239 ms |
| 20 minute soak: tick time, 95th percentile |
48.3 ms |
25.7 ms |
| 20 minute soak: heap growth |
19.3 MB/min |
1.2 MB/min |
| Server boot |
79 s |
51 s |
| World load on a warm boot |
34 to 47 s |
6 to 8 s |
The largest single effect is PuzzlesLib's event dispatch: 7.6 ms per tick before and 0.2 ms after with players standing, 13.1 ms and 0.7 ms while they explore. Part of the gain with standing players is the state of the run (mob caps, a data pack clock), not the fixes.
Client
Shaders on, render distance 12, 10 GB heap, singleplayer.
| What |
Before |
Now |
Measured with |
| Game start to the title screen |
163 s |
85 to 106 s (five starts) |
the whole pass (see below) |
| Resource reload (F3+T) in a world |
75 to 81 s |
29 to 34 s |
the whole pass |
| Flight through new terrain: longest frame |
2 067 ms |
209 ms |
the whole pass |
| Flight through new terrain: 1 % low |
31 fps |
40 fps |
the whole pass |
| EMI reload after joining a world: its built-in plugin step (fuel index) |
10 to 44 s |
2 to 4 s |
TickMender alone |
| EMI reload after joining a world: in total |
16 to 58 s |
11 to 15 s |
TickMender alone |
| Memory left behind per closed singleplayer world |
about 235 MB |
about 40 MB |
TickMender alone |
"The whole pass" means: the pack before the performance pass against the pack after it. That is TickMender together with fixes in two other mods of the same author (BetterX Remastered 30.5.1 and Sporran 1.0.4) and a few config changes in the pack. Those numbers are not TickMender's alone.
The JVM arguments hint
The same flight, the same average frame rate, 10 GB heap. Only the JVM arguments differ.
| JVM arguments |
Frames over 50 ms |
Longest frame |
| Launcher default (no garbage collector flags) |
100 |
1.16 s |
| The recommended G1 line |
42 |
189 ms |
How you can trust it
- One switch per fix. Set it to
false and restart: the fix's mixins are not applied at all and the target runs its own unmodified code.
- Idle when there is nothing to repair. A fix does nothing when its target mod is missing. Ten fixes are written for one exact version and do nothing with any other.
- It says what it does. One log line at start-up lists the state of every fix:
active, idle (...), off (config) or inactive (...). Every injector is checked after it was applied, a missing one is named in a warning, and the original code runs unchanged at that point. The first use of every fix is logged once.
- Verify mode. With
-Dtickmender.verifyCaches=true, every fix that answers from something it remembered, or with code of its own in place of a mod's, runs the original code as well, uses the original's answer and reports any difference (VERIFY FAILED) and a count (VERIFY RESULT).
- 372 unit tests in the build of 1.0.0.
In the last verify runs before the release, eleven fast paths were compared with the original code in the running game. No mismatch.
| Fast path |
Answers compared |
Mismatches |
| TerraBlender rule lookups |
683 to 757 million |
0 |
| BetterGrassify snow neighbour calls |
137 to 226 million |
0 |
| PuzzlesLib config lookups |
150 to 178 million |
0 |
| Better Caves thresholds |
3.4 million |
0 |
| Structure template lookups |
1.3 million |
0 |
| Better Caves noise cubes |
858 000 |
0 |
| Five model memos at start-up (BetterGrassify, Athena, Twilight Forest, Minecraft, More Culling) |
3.1 to 21.1 million each |
0 |
Candidates that would have changed what the game does, or could not be shown to keep it, were left out on purpose.
Configuration
config/tickmender.json is created with defaults the first time the mod starts. It holds one line per fix:
{
"puzzlesLibDispatchCache": true,
"ringPlacementExecutor": true,
"irisSameDimensionReload": false,
"logFilter": true,
"jvmArgumentsHint": true
}
(Shortened. The real file has all 37 switches.)
true is active, false is off. The file is read once at game start, so restart after a change.
- After an update that brings new fixes, their switches are added with their defaults. Everything already in the file is kept.
- A value that is not
true or false, or a file that is not valid JSON, costs one warning in the log and falls back to the default. The mod leaves your file untouched.
config/tickmender-logfilter.json holds the patterns of the log filter.
- A file
tickmender-jvm-presets.json in config/ replaces the built-in line of the JVM arguments hint. -Dtickmender.jvmHint=off disables the hint for one start.
Compatibility
- Minecraft 1.21.1, Fabric Loader 0.16.0 or newer, Java 21.
- No other dependency. Fabric API is not required. Nothing is shaded.
- Works on a client, on a dedicated server, or on both. Neither side needs the other to have it. It adds no packets and no registries.
- The client-only fixes are idle on a dedicated server.
Where the fixes apply:
- The game itself (any pack):
ringPlacementExecutor, merchantShutdownSave, structurePlaceFilter, structureTemplateCacheHit, stoppedServerCriteria, stoppedServerBiomeSearchCache, zipPackListingIndex, blockStateModelIdMemo. How much they do in another pack was not measured.
- Fabric API 0.116.17:
fuelRegistryApplyOnce, stoppedServerFakePlayers, closedWorldRenderContext.
- PuzzlesLib 21.1.x:
puzzlesLibDispatchCache, for every pack with mods that use PuzzlesLib.
- Not tied to a mod, on by default:
logFilter and jvmArgumentsHint. Both were written for Nytheria. In another pack, check the pattern list and the recommended line, or switch them off.
- Everything else: one named mod each, as listed above. A fix whose target looks different from the version it was written for is not applied, and the log says so.
FAQ
Does it work outside Nytheria?
Yes, it starts in any Fabric 1.21.1 pack. What it does there depends on which target mods and versions you have. The start-up log line shows which fixes are active. Expect a fraction of the 37, and do not expect Nytheria's numbers.
Can I add it to, or remove it from, an existing world?
Yes. It adds no blocks, items, registries or packets, so a world does not depend on it. What stays behind after removal are its files: config/tickmender.json, config/tickmender-logfilter.json, the folder .cache/tickmender/ and tickmender-jvm-arguments.txt.
Does it change gameplay?
No. No spawn rate, cap, distance or tick rule is touched, and the performance fixes return what the original code returns. The four fixes for crashes and lost data change one thing on purpose: the data is no longer lost. One example: a wandering trader that nobody has opened is saved without trades at a server stop and rolls them at its first use after the restart, with the game's own roll.
Do players need it to join a server that has it?
No. And a client that has it can join a server that does not.
What does the JVM arguments hint do?
It checks which arguments the game's Java was started with. If the recommended garbage collector settings are missing, it shows them once on the title screen, with the steps (those of the CurseForge app when the game was started by it) and a Copy button. You paste the line yourself. If the game runs worse with it, remove it again. There is no hint when the arguments are already set, when you chose another garbage collector, with less than 7 000 MB of heap, or after "Do not show again". Switch: jvmArgumentsHint.
Why does the log say a fix is idle?
Because there is nothing for it to repair: the target mod is not installed, the installed version is not the one the fix was written for, or it is a client fix on a dedicated server. inactive is different: the target is there but looks different, a warning names what is wrong, and the original code runs unchanged.
Licence and credits
MIT. Free to include in any modpack, no permission needed.
Written by Navrelis for the Nytheria modpack. TickMender is not affiliated with the mods it repairs and contains none of their code. If a mod you use is repaired here, the right place for the real fix is still that mod.