Repository navigation
Minecraft
Minecraft 1.20.1 / Forge: this page documents that build and its measurements. For the separate 170-control Minecraft 1.21.1 port, see Minecraft 1.21.1 NeoForge.
Fifty-one controls patch vanilla Minecraft 1.20.1 classes (tested on Forge 47.4.16). They are grouped below: world generation and biomes, the client, and the server with its entities. All but two give the same results as the original code with less repeated work: frame_pacing deliberately changes when the client waits for its FPS limiter, and vanilla_entity_section_x_overflow fixes a crash far from the world origin. vanilla_background_level_dat ships switched off.
Jump to: World generation and biomes (16) · Client (6) · Server, entities and general (29)
The three terrain controls compose: the memo removes repeated transformation of shared nodes within one traversal, the pass-2 reuse removes the second traversal's rebuilding, and the surface-estimate table removes repeated ground-height scans between neighbouring work areas. Each was measured with the others on.
| Key | Target | Side | Since | Kind |
|---|---|---|---|---|
terrain_density_memo |
Minecraft 1.20.1 world generation | BOTH | 1.0.2 | opt |
terrain_final_density_reuse |
Minecraft 1.20.1 world generation | BOTH | 1.0.16 | opt |
terrain_surface_estimate_share |
Minecraft 1.20.1 world generation | BOTH | 1.0.18 | opt |
vanilla_aquifer_candidate_cache |
Minecraft 1.20.1 (Forge 47.4.16; with YUNG's Cave Biomes 2.0.5) | BOTH | 1.0.30 | opt |
vanilla_aquifer_high_air |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_beardifier_influence_bounds |
Minecraft 1.20.1 (Forge 47.4.16; with Integrated API, YUNG's API, Moog's Structure Lib, fdbosses, Lithostitched, Valhelsia) | BOTH | 1.0.30 | opt |
vanilla_biome_fiddle_mask |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_climate_rtree_flat_bounds |
Minecraft 1.20.1 world generation | BOTH | 1.0.23 | opt |
vanilla_climate_sample_repeat |
Minecraft 1.20.1 world generation | BOTH | 1.0.23 | opt |
vanilla_climate_sample_xz_parts |
Minecraft (world generation) 1.20.1 | BOTH | 1.0.32 | opt |
vanilla_climate_search_repeat |
Minecraft 1.20.1 world generation | BOTH | 1.0.23 | opt |
vanilla_climate_tree_sort_keys |
Minecraft 1.20.1 | BOTH | 1.0.29 | opt |
vanilla_climate_tree_span_bounds |
Minecraft 1.20.1 | BOTH | 1.0.29 | opt |
vanilla_noise_column_cache |
Minecraft 1.20.1 world generation | BOTH | 1.0.23 | opt |
vanilla_noise_wrap_presize |
Minecraft 1.20.1 (with or without ModernFix) | BOTH | 1.0.26 | opt |
worldgen_empty_beardifier_marker |
Minecraft 1.20.1 world generation (Forge 47.4.16) | BOTH | 1.0.28 | opt |
Target: Minecraft 1.20.1 world generation · Side: BOTH · Since: 1.0.2 · Kind: optimization
Patched: net.minecraft.world.level.levelgen.NoiseChunk (router mapping) and DensityFunctions$HolderHolder.mapAll via the Mixins NoiseChunkMixin and HolderHolderMixin in terrain_efficiency.mixins.json; helper agentcraft.terrain.MemoizingVisitor
What upstream did. Every NoiseChunk (one per chunk column being prepared) re-maps the world's density-function graph through a visitor so that noise functions are wired to that chunk's coordinates. The graph is a DAG in which HolderHolder nodes (references to registry-held functions) are shared by many parents. Vanilla's visitor transforms each shared node again every time it is reached, so one traversal transforms the same sub-graph many times and allocates a fresh copy each time.
What the patch does. The visitor used during one NoiseChunk preparation is wrapped in an identity-keyed memo: the first time a shared node is reached it is transformed and remembered; every later reference within the same traversal reuses that result. The memo belongs to exactly one traversal and is discarded with it. It is never shared between chunks, worlds, seeds or worker threads, so there is no cross-chunk state and nothing to invalidate.
What stays the same. The noise math, the coordinates, the terrain settings, random-number consumption, structure and feature decisions, and ModernFix's own wrapper cache (which sits at a different layer and is left intact). Every block state generated was compared with the unpatched generator.
Measured. Per-thread CPU −56.3 % and allocation −59.6 % (1.474 GB → 0.595 GB) for 64 Overworld base-column queries in three warmed ABBA trials. In one host-confounded trial, 144 requested FULL chunks generated in 70.7 s versus 96.0 s. Equivalence: 2,496,115 native assertions equal, including 2,457,600 block states. This is a measurement of terrain preparation; it is not a claim about total world-generation speed.
Switching it off. Also honours the legacy property -Dac.terrain.enabled=false, which the loader sets when the key is false.
Since 1.0.33. When another mod also changes the call inside the NoiseChunk constructor that this control prepares, the control steps aside with one log line that names that mod's mixin, and the constructor runs as with the switch off. With 1.0.30 the game crashed while a world was being created next to the c2meforge port of C2ME (tested 0.2.0-forge.9.8). Where no other mod touches that call, nothing changes.
Target: Minecraft 1.20.1 world generation · Side: BOTH · Since: 1.0.16 · Kind: optimization
Patched: net.minecraft.world.level.levelgen.NoiseChunk.<init> plus the mapAll methods of DensityFunctions$Ap2, RangeChoice, BlendDensity, WeirdScaledSampler, ShiftedNoise, Spline and the MarkerOrMarked default, through the mixins NoiseChunkFinalDensityMixin, MarkerOrMarkedReuseMixin and DensityRecordReuseMixin (1.0.19 and earlier: the eight coremod scripts furious16-terrain-*.js; every replaced method is fingerprinted); helper bons.pure.terrain.FinalDensityReuse
Mixins (since 1.0.20): DensityRecordReuseMixin, MarkerOrMarkedReuseMixin, NoiseChunkFinalDensityMixin
What upstream did. Minecraft works out terrain shape from a large graph of density functions. Before a NoiseChunk can use that graph for its area it prepares it in two passes: the constructor maps the noise router with its wrap visitor (pass 1), then maps cacheAllInCell(add(finalDensity, beardifier)) with a fresh wrap visitor (pass 2). Pass 2 walks the already-mapped final density again: every record and spline is rebuilt, hashed down its whole subtree and looked up in the wrap table, and the lookup hands back the very object pass 1 produced. All that rebuilding and hashing happens once per NoiseChunk, that is once per generated chunk and once per single-column height query.
What the patch does. The pass-2 visitor is wrapped, and each mapAll that goes through the wrap table asks the helper first. A node is returned unchanged exactly when vanilla would return that same object: it is the value of a table entry that maps it to itself (or, for a NoiseChunk cache, of the marker entry that created it) and every child maps to an equal result, so vanilla's rebuilt copy would equal that key and the lookup would return the node. Mapped, Clamp and MulOrAdd never go through the table in vanilla (it returns a fresh equal copy), so they keep running their own code. Everything else runs vanilla code: other mods' density functions, nodes that are new in pass 2, and per-chunk objects such as Bumblezone's biome noise.
What stays the same. The resulting graph, its caches and the wrap table come out the same object for object; only the hashing, equality checks and rebuilding of unchanged nodes are skipped. Noise math, coordinates, terrain settings, random draws, and structure and feature decisions are untouched. The oracle checked every node marked reusable against vanilla re-mapping the same object in the same NoiseChunk (Overworld 4,656 nodes, Nether 2,028, End 264, plus 19 modded dimensions): always the identical object, wrap table unchanged. Base columns, all six heightmaps and complete noise volumes (1,081,344 Overworld blocks, 393,216 each in the Nether and the End, more in modded dimensions) were identical with the switch on and off, including 24 parallel column queries; 28,285 assertions per fixture run. The helper reads the protected records through method handles and switches itself off if they do not match the expected shapes.
Measured (Overworld, ABBA in one JVM, thread CPU and allocation per item, with terrain_density_memo on in both arms):
| Operation | Switch off | Switch on | Change |
|---|---|---|---|
NoiseChunk construction |
10.35 ms, 8.4 MB | 0.89 ms, 0.40 MB | −91 % CPU, −95 % allocation |
| One-column height query (structure placement) | 17.7 ms, 9.3 MB | 8.3 ms, 1.3 MB | −53 % CPU, −86 % allocation |
| Full-chunk noise fill | 77 ms, 19.9 MB | 69 ms, 11.9 MB | −10 to −13 % CPU, −40 % allocation |
In a generation-worker recording of the reference pack, pass 2 was 22.7 % of worker CPU, so roughly a fifth less CPU can be expected in chunk generation wherever structures are placed, exploration and Distant Horizons LOD generation included. This is not a measured whole-world speed or TPS figure: chunk generation in that pack was found latency-bound (C2ME workers idle 56–74 %), so wall-clock gains can be smaller than CPU gains.
Switching it off. terrain_final_density_reuse=false, or -Dbons_and_furious.finalDensityReuse=false. -Dbons_and_furious.finalDensityReuse.metrics=true logs reuse counters (fixture use).
Since 1.0.33. When another mod also changes the call inside the NoiseChunk constructor that this control prepares, the control steps aside with one log line that names that mod's mixin, and the constructor runs as with the switch off. With 1.0.30 the game crashed while a world was being created next to the c2meforge port of C2ME (tested 0.2.0-forge.9.8). Where no other mod touches that call, nothing changes. Next to Bye Pregen 1.1.2.4 it keeps working: that mod changes the same call but hands this control's part on, and one log line says so.
Target: Minecraft 1.20.1 world generation · Side: BOTH · Since: 1.0.18 · Kind: optimization
Patched: net.minecraft.world.level.levelgen.NoiseChunk.computePreliminarySurfaceLevel(long) (SRG m_198249_) through furious18-terrain-SurfaceEstimate.js (exact fingerprint); helper bons.pure.terrain.SurfaceEstimateShare
Mixin (since 1.0.20): NoiseChunkSurfaceEstimateMixin
What upstream did. When the aquifer decides whether an underground cavity holds air, water or lava, and when surface rules run, they first ask the NoiseChunk for the preliminary surface level of a column. The NoiseChunk answers by evaluating the initial density from the top of the world downwards in cell-height steps until it exceeds 0.390625, often more than 100 evaluations, and remembers the answer only in its own per-instance table, which dies with it. One NoiseChunk exists per generated chunk and one per single-column height query, which structure placement issues constantly, so neighbouring work areas scanned the same columns again and again: in the reference pack, 349,843 estimates for only 13,692 distinct columns while generating 144 chunks, 25.6 per column.
What the patch does. On a miss in the NoiseChunk's own table (which is kept), the helper consults one shared table per world generator and noise settings: a bounded, lock-free, direct-mapped table of 2¹⁶ immutable entries (about 2 MB when full), keyed by the aquifer's PositionalRandomFactory by identity (that factory is a value record whose equality depends only on the seed, so an equality key would merge dimensions that share a seed), held behind weak references so unloaded worlds are collected. Sharing happens only where the answer provably cannot depend on which work area asks, that is when all of these hold: the NoiseChunk has no terrain blending (Blender.empty()); it uses the noise-based aquifer; and an audit of the initial density graph, run once per world generator, finds only vanilla density-function types and no flat_cache / cache_2d subtree that reads y (such caches are read at y = 0 inside a NoiseChunk and at the real y outside it, and the two agree only for x/z-only functions). Everywhere else the original code runs. In the reference pack sharing is active in 3 of 22 noise dimensions (the Overworld, The Afterdark and The Graveyard's past); the Nether is refused because one cache_2d in its router reads y, and the remaining dimensions have aquifers off and never reach the table.
What stays the same. In every dimension, a value stored by one work area and served to a differently placed one equalled a fresh vanilla scan with sharing off. Complete noise volumes including aquifer fluids (1,081,344 Overworld blocks, 393,216 each in the Nether and the End, more in modded dimensions), base columns and all six heightmaps were identical with sharing on and off, and 64 parallel column queries on 8 threads (racing on an empty table, then reading it) came out identical; 1,404 assertions per fixture run.
Measured. Same rig, seed and 144-chunk Overworld generation, terrain_final_density_reuse on in every run:
| Run | Switch | Wall time | Estimates computed |
|---|---|---|---|
| two runs before the switch existed, and one control with it off | off | 151.1 s, 174.5 s, 161.8 s (mean 162 s) | 349,594 to 349,843 |
| five runs with the switch on | on | 74, 88.5, 81.8, 53.9, 76.0 s (mean 75 s) | 16,440 to 16,494 (95.3 % served from the table) |
Other sessions' test servers shared the machine during every run, so single wall times are noisy; every run with the switch off landed at 151–175 s and every run with it on at 54–89 s, about half the wall time for this workload. Focused ABBA measurements inside one JVM (thread CPU): clustered height queries in the structure-placement pattern −44 to −45 % on top of 1.0.16. Full-chunk noise fills measured in isolation show no gain (a chunk's own table already deduplicates estimates within the chunk; the shared-table check adds well under a microsecond to each roughly 0.4 ms scan). This is not a TPS figure. Distant Horizons' LOD generation runs the same structure code, so it benefits too.
Switching it off. terrain_surface_estimate_share=false, or -Dbons_and_furious.surfaceEstimateShare=false. -Dbons_and_furious.surfaceEstimateShare.metrics=true logs hit, miss and bypass counters (fixture use).
Since 1.0.34. The check of a world generator also looks inside a weird_scaled_sampler's input for function types of other mods, and refuses a terrain with a beardifier (each chunk's own structure terrain adaptation). Either keeps the original code for that world generator.
Since: 1.0.23 · Side: BOTH · Mixin: ColumnHeightShareMixin · Helper: bons.furious.patch.terrain.ColumnHeightShare
Patched: NoiseBasedChunkGenerator, the two column height query methods (getBaseHeight and the column iteration behind it)
What upstream did. Structure placement asks the noise generator for ground heights constantly, and each query builds a NoiseChunk for one column and fills its corner densities over the full world height. While this pack generated, 21 % of the queries asked again for a column already answered with the same heightmap type, three quarters of them from another worker thread.
What the patch does. A query reads no world state (no terrain blending, no structures), so its height is a function of the world generator, the build height, the column and the heightmap type. Answered heights are kept in a bounded table per world generator (2^16 entries) and reused. A generator keeps the original code when its noise router contains another mod's density function type (Tectonic's Invert is reviewed and bound to its class hash by DensityAudit), when it is not the vanilla noise generator class, or when BetterEnd fills its slices. The first 256 reuses of every world, then one in 4,096, are recomputed and compared instead of served; a difference switches the table off for that generator and logs it.
What stays the same. Every height: in game 400 queries in each of 13 noise dimensions, at two places, were identical to the original, and the self-check never fired. Lookups served from the table skip the whole NoiseChunk build; the first query of a column still does it.
Measured. Pregenerating 512 chunks on the dedicated server, the table served 968 of 16,220 overworld queries (6 %, less than review 8's 21 % repeat estimate, which counted all threads over a longer session). An overworld column costs 3.5-4.7 ms here (Tectonic terrain), which a served query skips.
Since: 1.0.23 · Side: BOTH · Mixin: ClimateSampleRepeatMixin · Helper: bons.furious.patch.terrain.ClimateRepeat
Patched: Climate$Sampler.sample
What upstream did. A biome lookup in this pack samples the climate at the same position several times in a row: Alex's Caves, ElysiumAPI, TerraBlender and vanilla each evaluate the six climate density functions there. 32.8 % of all samples repeated the previous sample of the same thread while generating.
What the patch does. Each thread remembers its last sampler, position and result and returns that immutable result for the same sampler and position. A sampler whose density functions include another mod's type is never served from memory (the same audit as the column table).
What stays the same. The sample values: 3,000 samples and 9,000 repeats identical in the offline mixed-class test, and in game every dimension's sampler (28 levels, 3,000 samples each) matched the original, overworld included.
Measured. A repeated sample 6.8 → 2.3 µs. Samples were 13.3 % of world-generation worker time in review 8; pregenerating 512 chunks served 3.5 million repeats.
Since 1.0.34. The values this control keeps on a record class are skipped by Gson. Up to 1.0.33 a mod that saves such a record with Gson failed with a NoSuchMethodException, because Gson asks a record for an accessor for every field it holds (reported with Dragon Mounts Remastered on Minecraft 1.21.1; no case is confirmed on Forge 1.20.1). Nothing else changes.
Since: 1.0.23 · Side: BOTH · Mixin: ClimateSearchRepeatMixin · Helper: bons.furious.patch.terrain.ClimateRepeat
Patched: Climate$RTree.search and the SubTree / Leaf search steps
What upstream did. After a climate sample the biome lookup chain searches a climate tree, and 30.7 % of the searches repeated the previous search of the same thread with an equal target. A search starts from the leaf the thread found last and stores the leaf it finds.
What the patch does. Each thread remembers its last tree, metric, target and result and returns the result for an identical next search, leaving the thread's remembered leaf exactly as the search would (right after a search for a target that leaf is the target's answer, and searching again from it finds and stores the same leaf).
What stays the same. The result and the per-thread leaf state: 9,000 searches identical in the mixed class, including the thread's last-result state; in game 2,000 searches per biome tree matched the original.
Measured. Searches were 5.9 % of worker time; pregenerating 512 chunks served 3.2 million repeats.
Since: 1.0.23 · Side: BOTH · Mixin: ClimateNodeBoundsMixin
Patched: Climate$RTree$Node.distance and the node constructor (an @Overwrite written from the behaviour; the body is our own flat-array computation)
What upstream did. Every node a biome search visits computes the squared distance from the target to its seven climate ranges, reading each range from its own Parameter object.
What the patch does. Each node also keeps the fourteen bounds in one long array, filled from the same parameters when the node is built, and the distance is the same integer sum read from that array. TerraBlender's and ElysiumAPI's access transformers make these node methods public, so the overwrite conforms its visibility to the installed class.
What stays the same. Every node distance equals Parameter.distance: 9,112 nodes × 1,500 targets offline (a third with random 64-bit values) and every dimension's tree in game.
Measured. 9.6-10.3 → 7.6-8.5 ns per node; node distances were 5.0 % of worker time.
Since 1.0.30. Nodes with equal bounds share one array: the region trees of a modded overworld repeat the same climate ranges. In the reference pack's dedicated server 228,371 arrays (29.2 MB) became 19,154 (2.5 MB), with 1,080,600 node distances and 115,500 searches identical with sharing as without it.
Since 1.0.33. The replaced method is declared public, which is what Biolith's access transformer asks for. Up to 1.0.30 it was declared protected, as in Minecraft itself, and that took back Biolith's change: with Biolith installed the first biome lookup of a new world failed with an IllegalAccessError and the game crashed. The computation is unchanged, and terrain is the same with the control on and off.
Since: 1.0.26 · Target: Minecraft 1.20.1 (with or without ModernFix) · Side: BOTH · Kind: opt
Mixins: NoiseChunkWrapPresizeMixin, RandomStateWrapSizeMixin
Every NoiseChunk (one per generated chunk and one per terrain-height query) wraps the noise router through a private table of the density functions it has already wrapped. ModernFix's perf.worldgen_allocation makes it a default-size fastutil map, which re-hashes every key each time it grows, and the keys are density records whose hash codes walk their whole subtree. The still-empty table is now replaced by one of the same class sized for the entry count the previous NoiseChunk of the same world reached. Same keys, same values, same objects; the table is never iterated by the game (Bons and Furious' own terrain_final_density_reuse reads it order-independently).
Measured: offline on vanilla's overworld with ModernFix's table, 300 table pairs (class, size, keys) and 1,200 heights identical with the switch off and on; median of 20 rounds 543 -> 482 us per NoiseChunk and 1,314 -> 1,214 us per height query. The growth re-hashing was 2.0-2.3% of world-generation worker time.
Since 1.0.33. When another mod also changes the call inside the NoiseChunk constructor that this control prepares, the control steps aside with one log line that names that mod's mixin, and the constructor runs as with the switch off. With 1.0.30 the game crashed while a world was being created next to the c2meforge port of C2ME (tested 0.2.0-forge.9.8). Where no other mod touches that call, nothing changes.
Since: 1.0.28 · Target: Minecraft 1.20.1 world generation (Forge 47.4.16) · Side: BOTH · Kind: opt
Mixins: BeardifierEmptyFillMixin, NoiseChunkFillStateAccessor, CreateNoiseChunkMixin
Empty beardifier filled without a per-block loop. Every generated chunk asks its beardifier (structure terrain adaptation) for a value at every block: 98,304 calls in a vanilla-height chunk, 196,608 in a 768-high one, each through a megamorphic call plus the handlers mods such as Integrated API, YUNG's API and Moog's Structure Lib inject into it. Most chunks have no structure piece near them and every one of those calls returns +0.0. The switch flags such a beardifier once per chunk, right after it is built, and only when that is provable: vanilla's piece and junction lists and every adaptation list those mods keep are empty, and every mod that changes the beardifier is a build whose code was read (checked once per run by the SHA-256 of its class files; unknown code anywhere and the switch stands down with one log line). A flagged beardifier fills its cache cells with +0.0 directly and leaves the noise chunk in the state the loop leaves it in. Same object, same bounds, same values for every block; chunks with structures run the original code unchanged.
Measured: 7.2 ns -> 0.06 ns per block on real classes (0.7 ms saved per vanilla-height chunk, 1.4 ms per 768-high chunk; in-game JFR before: 1.9-2.6% of world generation worker time); identical blocks, heightmaps and post-processing lists for 150 whole chunks with and without structures, 63,562 checks.
Since: 1.0.29 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: opt
Mixins: ClimateTreeSortMixin, ClimateNodeSpaceAccessor
Minecraft: building a biome search tree sorts its nodes once per sort by keys worked out in advance. Every biome source builds search trees over its climate points (TerraBlender one per region at each server start, Blueprint one when the world settings are read). Building one sorts the points eight times per tree level, and every comparison worked the sort key out again from the point's climate ranges. The keys are now worked out once per sort and the points sorted by them; equal keys keep their order, as before, so every tree comes out node for node the same.
Measured: offline on Minecraft's own class, 96 trees (vanilla's 7,593 overworld points and synthetic sets of up to 20,000 with ties and overflowing values) and 1,680 direct sorts were identical; the overworld tree built in 29.9 -> 19.1 ms, a 10,000-point tree in 95 -> 47 ms. In this pack's profiles these sorts were 1.5% of all server startup samples (about 0.8 s) and 0.5% of the client's world load (about 1.4 s).
Since: 1.0.29 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: opt
Mixins: ClimateTreeSpanMixin, ClimateNodeSpanAccessor
Minecraft: the climate ranges of biome search tree branches are worked out without creating an object per step. Each branch of a biome search tree records the climate ranges its points cover. Working them out created a new range object for every point and every one of the seven climate values, about two million short-lived objects for the overworld tree alone. The smallest and largest values are now collected directly and one range object made per value, with the same results (a branch with a single point still shares that point's own range objects, as before).
Measured: offline on Minecraft's own class, 400 direct calls and the same 96 trees were identical; together with the sort keys above the overworld tree built in 29.9 -> 15.1 ms and a 10,000-point tree in 95 -> 40 ms (span bounds alone: 29.9 -> 25.1 ms and 95 -> 86 ms).
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16; with YUNG's Cave Biomes 2.0.5) · Side: BOTH (world generation: dedicated and integrated server) · Kind: opt
Mixin: AquiferCandidateMixin
Minecraft: aquifers find the nearest water sources from a small table instead of recomputing them for every block. Every block of new terrain that is not solid (the whole air column above the ground, water and caves) asks the aquifer what it holds. Each time the aquifer worked out the twelve candidate water sources around the block again (grid index, stored position, unpacking, distance) and looked the three nearest up again by position. It now keeps the twelve candidates of each grid cell in a small table, ranks their distances without branches (equal distances in the same order as before), and reads the nearest ones by index. Water levels are computed by the game's own code, in the same order and number as before (YUNG's Cave Biomes looks up a biome whenever one is computed, so that order matters), and a cell whose positions are not computed yet runs the original code. Only aquifers with no unknown mod code use it.
Idea: C2ME (unmerged pull requests 557/558), idea text only
Measured: offline on Minecraft's own class with YUNG's mixins applied: 60 whole chunks (384 and 768 high), 1,125 column queries and 520,000 direct calls gave identical blocks, flags, caches and the same sequence of water-level computations. At the pack's 768 height computeSubstance took 99-126 -> 73-86 ns of thread CPU per call (per-round ratio 0.67-0.71 over three runs; 0.63-0.83 at 384 high) and a chunk's noise fill 52-57 -> 44-47 ms CPU (ratio 0.78-0.85; busy machine, medians of 13 interleaved rounds). In this pack's pregeneration profile the method was 8.2% (self) of all samples.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (world generation: dedicated and integrated server) · Kind: opt
Mixins: AquiferHighAirMixin, FluidStatusLevelAccessor
Minecraft: blocks high above all nearby water levels skip the aquifer's pressure checks (needs the switch above). Close to the border between two water sources the aquifer weighs their "pressure" before it decides a block is air. When the block is at least 5 above all three nearest water levels and the second and third sources are already known, that check can only end in air (every weight is zero or negative there), so the answer is given at once. It never computes a water level the game would not have computed at that moment, and never skips one it would have.
Idea: C2ME (unmerged pull request 559), idea text only
Measured: same proof as above (and 2.55 million shadow comparisons, 401,000 of them decided by this shortcut, no difference); 64-72% fewer bytes per call (9.6 -> 3.5 B at 768 high, 6.9 -> 1.9 B at 384: about 1.2 MB less garbage per 768-high chunk); thread CPU per call a further 0-6% at 768 high (inside the busy machine's noise) and 2-20% at 384.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16; with Integrated API, YUNG's API, Moog's Structure Lib, fdbosses, Lithostitched, Valhelsia) · Side: BOTH (world generation) · Kind: opt
Mixin: BeardifierBoundsMixin
Minecraft: the terrain blending around structures only looks at the pieces that can reach each block. In a chunk near villages, ruins and other structures that blend into the terrain, every block (196,608 in a 768-high chunk) adds up a term for every structure piece and every jigsaw junction nearby, although a piece only reaches 5-12 blocks around its box. Now each chunk's pieces are looked at once, and each block only adds the terms of the pieces whose reach holds it, in the same order. Every term it leaves out is exactly zero, so the sum is the same to the last bit. The other mods' terrain-adaptation code still runs as before; pieces of fdbosses' boss arenas keep the full list. It only acts where the Beardifier check of worldgen_empty_beardifier_marker trusts every mod's code in this class.
Idea: C2ME (pull request 552; Mojang did the same in 26.1.2), idea text only
Measured: offline on Minecraft's own class with the pack's ten Beardifier mixins: 300 random structure sets (7.1 million positions, subclassed and fdbosses boxes included) and 40 whole chunks gave bit-identical results; a shadow run checked 113.7 million left-out terms (all exactly zero). Per block: 24 pieces + 80 junctions 1,209 -> 58 ns (768 high, about 238 -> 11 ms per chunk), 6 pieces + 12 junctions 297 -> 38 ns, 1 piece 42 -> 31 ns. In this pack's pregeneration profile the blending was 3.0% of all samples (1.0.26, before the empty-chunk marker).
Since 1.0.33. Steps aside, with one log line, while Generator Accelerator is installed. That mod replaces the method this control adjusts, and with 1.0.30 a dedicated server stopped at the first world load. Tested with Generator Accelerator 1.4.10-3.1: the server starts, generates, saves and stops, and the terrain equals a run with Generator Accelerator alone.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH · Kind: opt
Mixin: BiomeFiddleMaskMixin
Minecraft: biome lookups work out their border blur offsets with a bit mask instead of a signed remainder. Every biome lookup at a block position (mob spawning, particles, snow and ice, feature placement, height queries of other mods, ...) blurs biome borders by giving each of the 8 surrounding biome cells three pseudo-random offsets. Each offset takes the remainder of a 64-bit number divided by 1024, rounded towards minus infinity, and the sign correction that needs is a branch the processor cannot predict, 24 times per lookup. For a divisor of 1024 that remainder is exactly the lowest 10 bits of the number, for negative numbers too, so it is now read with a bit mask. Every offset, distance and chosen biome is the same.
Idea: Tpsum (its BiomeLookup notes), idea text only
Measured: offline on Minecraft's own class: 200 million random numbers and 4 million lookups (64 seeds, positions up to 30 million blocks out) gave identical results; a lookup took 293 -> 172 ns of thread CPU (ratio 0.59, 15 interleaved rounds of 4 million lookups, busy machine). In this pack's profiles biome lookups were 0.17-0.69% of the client's samples and 0.92% of the server's during pregeneration.
Since: 1.0.32 · Target: Minecraft (world generation) 1.20.1 · Side: BOTH · Kind: opt
Mixins: ClimateSamplerPartsMixin, RandomStatePartsMixin
Vanilla terrain: climate samples outside chunk generation compute each two-dimensional part once per column. Biome lookups made outside chunk generation (structure biome checks, Distant Horizons' distant terrain, surface biome lookups, /locate, the spawn search) use a climate sampler whose six density functions keep no cache at all, so one sample evaluates the same shift noises up to eight times and continentalness, erosion and ridges twice, and a sample one block higher in the same column repeats everything. Such a sampler now evaluates copies of its functions in which each part the world generator marks as two-dimensional (flat_cache / cache_2d) and that reads only x and z is remembered per thread for its column and shared by every function that reads it (since 1.0.34 a part that is a bare constant is left as it is, as the original sees it). A sampler is used this way only when all of its density functions are known vanilla (or reviewed) types and the copies return the same bits as the original at 256 test positions; its first 64 real samples are also compared with the original.
Measured: on the transformed classes the samplers of all seven vanilla world types returned the same bits as the original in 1,294,477 checks (scattered positions, whole columns, extreme coordinates, two samplers interleaved, eight threads). Per sample of the vanilla overworld sampler: 5.0-5.6 -> 1.7-1.9 us at scattered positions, 5.0-5.5 -> 0.25-0.34 us down a column.
Since 1.0.34. The values this control keeps on a record class are skipped by Gson. Up to 1.0.33 a mod that saves such a record with Gson failed with a NoSuchMethodException, because Gson asks a record for an accessor for every field it holds (reported with Dragon Mounts Remastered on Minecraft 1.21.1; no case is confirmed on Forge 1.20.1). Nothing else changes. It also no longer stops the game when another mod changes the same climate calls first.
These six only load on a client. frame_pacing is the one deliberate change among the Minecraft controls; its section explains the tradeoff.
| Key | Target | Side | Since | Kind |
|---|---|---|---|---|
frame_pacing |
Minecraft 1.20.1 client | CLIENT | 1.0.5 | change |
vanilla_animate_tick_uniform_biome |
Minecraft 1.20.1 client | CLIENT | 1.0.23 | opt |
vanilla_camera_fluid_memo |
Minecraft 1.20.1 | CLIENT | 1.0.26 | opt |
vanilla_fog_color_sample_memo |
Minecraft 1.20.1 client | CLIENT | 1.0.23 | opt |
vanilla_layer_bake_streamless |
Minecraft 1.20.1 | CLIENT | 1.0.30 | opt |
vanilla_model_bone_lookup |
Minecraft 1.20.1 client models | CLIENT | 1.0.21 | opt |
Target: Minecraft 1.20.1 client · Side: CLIENT · Since: 1.0.5 · Kind: timing change (the one deliberate change among the Minecraft controls)
Patched: net.minecraft.client.Minecraft.runTick (placement of the limitDisplayFPS call) via MinecraftPacingMixin in bons_and_furious_pacing.mixins.json; helper bons.pure.pacing.FramePacer
What upstream did. With an FPS cap set, vanilla renders the frame, presents it (updateDisplay), and then sleeps in the FPS limiter. Because the sleep comes after presentation, any variable tick or render work in the next frame lands on top of a fixed sleep, so the interval between presented frames wobbles by the amount of that variable work.
What the patch does. The Mixin runs the same limiter call once immediately before the display update and skips the original call afterwards. Spare frame budget is then spent before presenting, so variable work is absorbed by the wait instead of shifting the next presentation. It only acts when an FPS cap is set; unlimited FPS bypasses limiting exactly as before.
What stays the same. Exactly one limiter call per frame, the effective FPS option, the entity/block-entity/texture/ambient tick counts and every visual setting. Achieved FPS did not change.
With Collections Of Optimizations (tested 4.4, since 1.0.27). Its option vanilla.paceFramesBeforeSwap makes the same change. While that option is on, this control steps aside; while it is off, this control applies and leaves COO's copy out, so only one copy runs. See Compatibility and target versions. With both copies running (1.0.26 and earlier, COO option on), every frame waited twice and a 30 FPS cap gave about 14 FPS.
Measured. At an equal 120 FPS cap: consecutive-interval difference p95 7.36 → 1.47 ms (standalone CTM scene) and 7.63 → 1.25 ms (Fusion scene); swap-interval p99 17.96 → 15.01 ms and 17.46 → 13.69 ms; intervals above 16.67 ms fell from 125 → 69 and 106 → 59 per ~80 s window; achieved FPS unchanged (116.2 / 116.7). These are buffer-swap return intervals measured inside the JVM, not OS scan-out timestamps, so they show smoother submission rather than a guaranteed change on screen.
Switching it off. Set frame_pacing=false, or use the legacy -Dbons_and_furious.framePacing=false (1.0.15 and earlier: -Dbons.pure.framePacing=false, which newer builds still accept). Because this is a behaviour change, it has always had its own switch, even before every other patch got one.
Target: Minecraft 1.20.1 client models · Side: CLIENT · Since: 1.0.21 · Kind: optimization
Patched: net.minecraft.client.model.HierarchicalModel.getAnyDescendantWithName through BoneLookupMixin and ModelPartChildrenAccessor; helper bons.furious.patch.models.BoneSearch
What upstream did. Keyframe animations (vanilla sniffers, frogs, camels and wardens, and modded mobs on the vanilla animation system) look up every animated bone every frame through getAnyDescendantWithName, which streams the whole part tree (nested Stream.concat and flatMap) to find the part that has a child with that name.
What the patch does. Redirects the getAllParts() stream inside that method to a plain depth-first walk of the children maps in the stream's own source order, and hands the first part that has a child with the name to the rest of the method unchanged. A part class that overrides getAllParts or hasChild sends the lookup back to the original stream. Minecraft's code is not copied; the mixin adds only the redirect and the walk.
What stays the same. The part returned, or the exception thrown: 30,000 random trees over five children-map types (including null children) in the review, and in the 1.0.21 client run every one of the 5,800 bone names of all 439 loaded entity models gave the same part object as the stream expression.
Measured. 120 → 10 µs per frame for a 47-part model with 18 animated bones.
Since 1.0.30. When Embeddium's own stream-free lookup is active (Embeddium without Oculus), this control steps aside with one log line. Before, that combination crashed the game at start.
Since: 1.0.23 · Side: CLIENT · Mixin: AnimateTickBiomeMixin · Helper: bons.furious.patch.vanilla.AnimateTickBiome
Patched: the biome lookup inside ClientLevel.doAnimateTick (BiomeManager.getBiome and the section read it reaches)
What upstream did. The client picks 1,334 random blocks per tick for ambient particles and asks each one's biome, which compares eight candidate biome cells with a hash-based distance and returns the nearest.
What the patch does. When all eight cells lie in one chunk section that holds a single biome, every candidate has that biome, so it is returned directly. In every other case (cells across a section border, a chunk not loaded, a mixed section) the original lookup runs. No random numbers are involved, so the particle sequence is unchanged. Embeddium replaces doAnimateTick with its own copy that keeps the same biome lookup; the shortcut applies there too.
What stays the same. The biome: 300,000 positions around the player identical in game.
Measured. 39 % of the lookups took the shortcut in the client run.
Since: 1.0.23 · Side: CLIENT · Mixins: FogColorSampleMixin, GameRendererFrameMixin · Helper: bons.furious.patch.vanilla.FogSample
Patched: FogRenderer.setupColor's biome sample (CubicSampler.gaussianSampleVec3), with a per-frame marker from GameRenderer.render
What upstream did. The fog colour blends 216 biome fog colours around the camera, and in this pack it is computed up to three times per frame for the same camera and level: vanilla, Distant Horizons' fog and Ars Nouveau's sky.
What the patch does. Within one frame the blended sample is reused when the level, biome manager, brightness and sample position are exactly the same; everything else in the fog colour computation still runs every time. Only dimensions with vanilla special effects are reused.
What stays the same. The colour: in game bit-identical with the memo on and off, and the frame's later calls hit the memo (over Embeddium's fast sampler).
Measured. The sample was 1.2 % of the render thread.
Since: 1.0.26 · Target: Minecraft 1.20.1 · Side: CLIENT · Kind: opt
Mixins: CameraFluidMemoMixin, CameraFluidFrameMixin
The fog type (water, lava, powder snow or none) comes from Camera.getFluidInCamera, which vanilla and about twenty mods ask for every frame. Each call recomputed the camera's near plane, up to eleven block and fluid lookups and the work of the six hooks other mods put on it (Valkyrien Skies twice, Clockwork, Alex's Caves, Blast from the Past, Snow! Real Magic). Within one render pass the answer is now reused while the camera, its level, position, block position and rotation, the window size and the FOV setting are exactly the same; the blocks, effects and ships the answer depends on only change between render passes. The switch stands down by itself when a mod it was not tested with hooks the camera. -Dbons_and_furious.cameraFluidMemoShadow=true computes both answers on every call and logs any difference (for testing).
Measured: 1.2 million offline calls through the camera with all six hooks applied, every reused answer equal to the original; 405 -> 22 ns and 1.5 KB -> 16 bytes per call. 2.9% of the render thread and 9.6 MB/s of its allocation before.
Since: 1.0.30 · Target: Minecraft 1.20.1 · Side: CLIENT · Kind: opt
Mixin: LayerBakeStreamlessMixin
Vanilla: model layers are baked with plain loops. Turning a model layer definition into model parts built two stream pipelines for every part. Many armour mods (Clothing of the Lowlands, Terramity, Aquamirae and others built from the same template) bake their armour model again on every render call, three times per chestplate, for every entity wearing it. The same parts are now built with plain loops: children in the same order, the same map and list types, the same cubes, the same poses, and other mods' hooks on the bake still run. Runtime flag: none (it replaces a Minecraft method; switch it off here, which takes effect at the next start).
Measured: offline, all 204 vanilla layers and the 53 armour layers that installed armour mods bake per render call give trees identical to vanilla's own bake; against the unchanged game code with ModernFix in the same run, 6.9 -> 6.2 us and 11.5 -> 7.6 KB per layer bake (median of 7 rounds). A chestplate of these mods bakes 3 layers per render.
Block-entity and chunk ticking, mob goals and searches, entity lookups, item merging, commands, saving and packet sending. In singleplayer the same code runs on the integrated server. Several are marked "public value": the work they remove hardly showed up in the reference pack and matters in other packs (large item piles, many item frames with maps, long pipe or goat farms).
| Key | Target | Side | Since | Kind |
|---|---|---|---|---|
vanilla_background_level_dat |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_background_saves |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_block_entity_tick_state |
Minecraft 1.20.1 / Forge 47.4.16 | BOTH | 1.0.25 | opt |
vanilla_block_state_air_flag |
Minecraft 1.20.1 with Forge 47.4.16 | BOTH | 1.0.29 | opt |
vanilla_block_ticking_range_memo |
Minecraft 1.20.1 | BOTH | 1.0.23 | opt |
vanilla_chunk_status_name_memo |
Minecraft 1.20.1 | BOTH | 1.0.26 | opt |
vanilla_connection_flush_batching |
Minecraft 1.20.1 (Forge 47.4.16, Netty 4.1.82) | BOTH | 1.0.30 | opt |
vanilla_cursor_counters |
Minecraft 1.20.1 | BOTH | 1.0.30 | opt |
vanilla_data_merge_unchanged |
Minecraft 1.20.1 data commands | BOTH | 1.0.0 | opt |
vanilla_entity_class_count_layer |
Minecraft 1.20.1 | BOTH | 1.0.30 | opt |
vanilla_entity_section_x_overflow |
Minecraft 1.20.1 | BOTH | 1.0.30 | fix |
vanilla_fire_scan_loop |
Minecraft 1.20.1 | BOTH | 1.0.28 | opt |
vanilla_framed_map_holder_scan |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_game_event_registry_lookup |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_goal_flags_none_disabled |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_item_merge_candidates |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_long_jump_weighted_pick |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_poi_chunk_sections |
Minecraft 1.20.1 | BOTH | 1.0.25 | opt |
vanilla_raycast_fluid_none |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_repellent_search_sections |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_selector_type_index |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.29 | opt |
vanilla_spawn_gate_visibility_memo |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_strip_formatting_fast_path |
Minecraft 1.20.1 | BOTH | 1.0.30 | opt |
vanilla_suffocation_scan_loop |
Minecraft 1.20.1 | BOTH | 1.0.28 | opt |
vanilla_sun_burn_deferred_wet_check |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_tag_membership_ids |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_ticker_range_memo |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
vanilla_ticking_chunk_memo |
Minecraft 1.20.1 (Forge 47.4.16; with or without ModernFix) | BOTH | 1.0.29 | opt |
vanilla_turtle_egg_search_sections |
Minecraft 1.20.1 (Forge 47.4.16) | BOTH | 1.0.30 | opt |
Target: Minecraft 1.20.1 data commands · Side: BOTH · Since: 1.0.0 · Kind: optimization
Patched: net.minecraft.server.commands.data.DataCommands (the merge sub-command) via DataCommandsMixin in bons_and_furious.mixins.json; helper agentcraft.pure.UnchangedMerge
What upstream did. /data merge <target> <nbt> serialises the target, copies its whole NBT tree, merges the given compound into the copy, compares the result with the original, and either writes it back or throws the "nothing changed" error. Data-pack mods run this command from tick functions to pin a single scalar every tick (Scorched merges Invulnerable:1b into every buried sandcrab; Incendium has similar loops), so the full deep copy of an entity's NBT happened dozens or hundreds of times per tick for no change.
What the patch does. When the merge payload consists of small scalar values, the patch first compares each of them with the value already present in the serialised tree. If every one already matches, it throws the original "nothing changed" exception without copying the tree. Any merge that would change data, or that carries nested or non-scalar payloads, takes the untouched vanilla path.
What stays the same. Entity serialisation still happens (the check needs it), depth validation, the exact error type and message, the result of every merge that changes something, selector resolution, function order and cadence. Data-pack files are not touched.
Measured. The unchanged-merge phase after serialisation fell from 18,040 → 0 bytes and 17.287 → 0.085 µs per command; 20,000 randomised NBT cases and real command outcomes were equal. The Scorched sandcrab gate builds on this: with both on, an idle buried sandcrab's script cost fell from 29.8 to 1.4 µs per tick.
Since: 1.0.23 · Side: BOTH · Mixins: TickingTrackerVersionMixin, DistanceManagerTickingMixin · Helper: bons.furious.patch.vanilla.TickingLevels
Patched: TickingTracker (level writes and the level lookup) and DistanceManager's block-ticking and entity-ticking checks
What upstream did. Every ticking block entity asks whether its chunk ticks, which looks the chunk up in the ticket tracker's map of ticking levels; entity ticking checks do the same lookup. Block entities of one chunk come one after another, so the same chunk was looked up again and again.
What the patch does. The tracker remembers its last lookup together with a version that its only writing method advances before and after every change. A repeat of the same chunk with an unchanged version returns the remembered level; anything else reads the map.
What stays the same. Every answer: in game 45,387 chunks × 4 lookups were identical with the memo on and off.
Measured. 84 % of lookups answered from the memo in review 8, where the lookup was 2.8-3.0 % of the server thread.
Since 1.0.30. Stands down for subclasses of the ticking tracker (for example the one of C2ME's notickvd module), which are then asked every time.
Since: 1.0.25 · Target: Minecraft 1.20.1 / Forge 47.4.16 · Side: BOTH · Kind: opt
Mixins: BlockEntityTickStateMixin, BlockEntityStateAccessor, LevelChunkBlockEntityStateMixin
Every tick of every ticking block entity (furnaces, hoppers, machines, chests on the client...) looked its block state up in the chunk and then asked BlockEntityType.isValid, which five mods in this pack hook. Radium's mixin.world.block_entity_ticking.support_cache removes both steps, but Radium Re-Reforged ships it off: its LevelChunk part cannot apply on Forge. This switch does the same with Forge-safe hooks: the tick takes the state from the block entity's own field, which LevelChunk.setBlockState keeps equal to the block in the world (also in the two cases where vanilla leaves it behind, when a block's placement code changes its own state), and remembers the isValid answer per state until tags are reloaded. Radium's three support_cache mixins are cancelled while this applies.
Measured: in every rig run all ticking block entities (about 2,460) had the same state as the block in the world, and a hopper powered at placement kept its item as in vanilla (with the switch off, its block entity kept the unpowered state). Timed in one game run, alternating so machine load hits both alike: the two calls this switch takes out of every tick (the chunk lookup and isValid with its five hooks) cost 26.3 ns per block entity, their replacement 7.3 ns. Block entity ticking was 2.9-3.3% of the render thread in the 2026-10-01 client profiles.
Since: 1.0.26 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: opt
Mixins: ChunkStatusNameMixin
ChunkStatus.toString() looks the status up in the chunk-status registry (Forge's wrapper, a reverse lookup) and builds a new string on every call. C2ME's profiling hook calls it for every chunk request that is not finished yet, before its profiler decides whether to record anything. The first string is now kept and handed back again: a registered status keeps its key forever, so the text is the same. A status asked before it has a key runs the original.
Measured: offline on the transformed class all 12 statuses gave the same text as the original and the registry key; 26.3 -> 3.9 ns per call with vanilla's registry (Forge's registry wrapper costs more). The call was 0.8-1.2% of the integrated server thread while terrain loaded, 0.55% of the dedicated server's with C2ME.
Since: 1.0.25 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: opt
Mixins: PoiChunkSectionsMixin, SectionStorageAccess
Radium's mixin.ai.poi is forced off in this pack by Valkyrien Skies, so every search for beds, job sites, bee nests, portals or modded points of interest (Quark's feeding troughs search 216 sections each) runs vanilla's getInChunk for each chunk in range: a stream pipeline and a new SectionPos for every section of the column. The same sections are now looked up in the same order and just as lazily, without them; the records and their order are unchanged. Applied only while Radium's own ai.poi is not.
Measured: timed in one game run, alternating so machine load hits both alike, getInChunk took 788 ns per chunk against 2022 ns for a compiled copy of vanilla's pipeline over the same 121 chunks. The records and their order equalled vanilla's pipeline in every run, and findFirst looked up the same number of sections.
Since: 1.0.29 · Target: Minecraft 1.20.1 with Forge 47.4.16 · Side: BOTH · Kind: opt
Mixins: BlockBehaviourAirInvoker, BlockStateAirFlagMixin
Minecraft (Forge): each block state remembers whether it is air. Forge turns the "is this block air" check into a call on the block that reads the answer back from the state. Because two blocks in this pack answer that call their own way, every check has to look up the block and its type first. Each state now keeps its answer after the first check when its block uses Forge's standard air check (a fixed property of the state); blocks with their own air check (Ars Nouveau's intangible air, Immersive Engineering's fake light) are asked every time.
Measured: offline on the game's own block-state classes with the changes of all 23 other mods that patch them applied: the same answer in every case (192 kinds of state, a block whose answer changes, 8 threads racing); one check 3.2 -> 1.5 ns with the states cached, no consistent change when they come from memory. In the test flight this check was the busiest single spot of the chunk-mesh builders (18% of their time).
Since: 1.0.28 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: opt
Mixin: EntityInWallScanMixin
Suffocation check without a stream. Every living entity asks Entity.isInWall every tick, and vanilla answers by building a Java stream over the one to four blocks around its eyes (a spliterator, a pipeline and the match operation's objects) before testing a single block. The check now walks the same block positions in the same order with the game's own test and stops at the same block. Any other use of that stream still gets the vanilla stream.
Measured: 96 -> 49 ns and 200 -> 80 B per isInWall call on real classes; 840,000 checks identical (with the fire check below).
Since: 1.0.28 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: opt
Mixin: EntityFireScanMixin
Fire check of moving entities without a stream. At the end of every movement, every moving entity checks whether it touches fire or lava through a Java stream over the blocks in its box (a spliterator, a pipeline, a map stage and the match operation's objects). The check now asks the same chunk-loaded question first and reads the same blocks in the same order with the game's own test. A level class that streams its blocks differently keeps its own stream. In the profile this was twice the cost of the suffocation check above. Since 1.0.34 the check steps aside while Radium applies its experimental fire and lava cache (Radium's mixin.experimental switched on in its own lithium.properties): Radium changes the same check, and its version runs, with one log line. Before, the two together skipped the "not touching fire" step, so burning entities got no fire grace time back and no extinguish sound.
Measured: 555 -> 301 ns and 680 -> 440 B per moving entity per tick on real classes; 840,000 checks identical (with the suffocation check above).
Since: 1.0.29 · Target: Minecraft 1.20.1 (Forge 47.4.16; with or without ModernFix) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixin: ChunkHolderTickingMemoMixin
Chunk holders remember their ticking chunk. Every server tick asks each loaded chunk's holder for its ticking chunk: the holder's chunk future, its result and the chunk inside it, two extra objects per chunk that are usually cache misses. Each holder now keeps the future it last answered for and that answer in two fields of its own, recorded only once that future has completed normally (a completed future's result never changes), and answers from them while the holder still has that same future. A new, unfinished or failed future runs the original code. Only the server thread uses the record; other threads, and every call while C2ME's no-tick view distance is on, run the original code.
Measured: offline on the real class (ModernFix's version of the method, as installed) with the caches emptied before each pass: 20.9 -> 7.8 ns per chunk (laid out in order) and 18.2 -> 8.2 ns (scattered), warm 12.8 -> 4.3 and 10.2 -> 3.9 ns, no allocation; 327,815 calls identical over 72,185 future changes, plus other-thread and concurrent runs (1.5 million checks). In-game JFR before: the method and the future read under it were 3.1-4.4% and 4.2% of the integrated server thread (Bons and Furious 1.0.26).
Since: 1.0.29 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixins: EntityLookupMixin, EntitySelectorMixin, EntitySelectorParserMixin, EntitySelectorTypeOptionMixin
Entity selectors with a type tag only visit entities of those types. A data-pack selector such as @e[type=#incendium:mobs_no_player,tag=!in.checked] walks every loaded entity of the level and runs isAlive and the selector's checks on each one, because vanilla only narrows a scan by type for type=<one id>. Data-pack clock functions run such scans several times a tick (Incendium's main clock: seven type-tag scans every tick). When the selector's first option is a type tag or a negated type, the scan now walks a per-level index of the loaded entities grouped by type and visits only the types the option accepts (plus entities of classes with their own isAlive, health or type methods), in the level's own entity order. Same entities, same order, same limit and sorting; every other selector and every other entity query runs vanilla's code.
Measured: offline on the real classes (with Radium's entity data tracker, as installed), seven Incendium-style scans over 1,800 entities 1.64x faster with half of them in the tag and 2.39x with a modded-like mix (up to 7x per scan), no extra allocation; 82,480 selector runs and 2.7 million selected entities identical to vanilla, 6,000 entity adds/removals with the index in step. In-game JFR before: these scans were 3.7-4.9% of the integrated server thread (Bons and Furious 1.0.26).
Since 1.0.34. A mod's entity class whose methods name classes that exist only on the client now takes the original code path. Up to 1.0.33 such a class stopped a dedicated server: this control looks at the methods of those classes, and on a server Java cannot load the client classes they name. In our test pack, a Small Ships Drakkar was enough while a command searched entities by type, which some data packs do every few seconds.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixin: MobSunBurnOrderMixin
Sun-burn checks ask "wet?" only when it can matter. Zombies, skeletons, phantoms and modded undead ask every daytime tick whether they are in water, rain or a bubble column before the light test and the random roll that decide whether they may burn at all (the roll passes at most 4% of the time). That question reads blocks at the mob's feet and, with Slice & Dice, three more above its feet and head. The switch asks it after the light test and the roll pass, in the same "and" of the same conditions: same answer, the same random numbers. Mob classes with their own water checks or water update keep the original order (18 of the pack's 1,579 mob classes); Spawn's moonstone and sunstone checks in the same method run unchanged.
Idea: Gale and Leaf ("Optimize sun burn tick"), idea text only.
Measured: offline on the real class (with Spawn's real hooks), mob and chunk data in cache as in the mob's own tick: 130 -> 100 ns and 138 -> 116 B per call without Slice & Dice's reads, 336 -> 128 ns and 271 -> 120 B with them (as in this pack; a busier rerun 255 -> 197 and 583 -> 257 ns); 400,000 calls identical (1.6 million checks). In-game JFR before: the method was 0.5% of the integrated server thread with undead mobs in daylight (Bons and Furious 1.0.26).
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16, Netty 4.1.82) · Side: BOTH (acts on the server thread) · Kind: opt
Mixins: ConnectionFlushBatchMixin, TickChunksBatchMixin
Packet bursts are flushed once instead of once per packet. Every packet the server sends is handed to the network thread as its own task, which wakes that thread up and then writes and flushes the packet on its own, one socket write per packet. The entity tracker and the block/light change broadcast send hundreds of small packets per tick in one burst, and a player's own tick (chunks, inventory) another. Inside those bursts the same tasks are queued in the same order without waking the network thread, each writes its packet without flushing, and every connection is flushed once when the burst ends, still within the tick. The packets, their order and every byte on the wire stay the same (compression and encryption see the same sequence). Packets with a send listener, keep-alives, protocol switches and packets from other threads take Minecraft's usual path. A connection that is closed during a burst is flushed first, so a message sent just before the close still arrives (1.0.34; in 1.0.30-1.0.33 a player refused at login for a ban, the whitelist, a full server or a slow login saw a generic disconnect without the reason).
Idea: Krypton (flush consolidation), VMP (avoid network wakeups) and Paper's connection patch title, idea text only
Measured: 40 randomized runs over a real encrypted socket and 4 over the integrated server's local channel: identical byte streams and send-listener order. 300-packet bursts: 300 socket flushes -> 1, network thread CPU 8.7 -> 2.6 us per packet, server thread 235 -> 161 ns per send when other work runs between sends.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server thread of a running server) · Kind: opt
Mixins: SavedDataWriteMixin, PlayerDataStorageWriteMixin, PlayerAdvancementsWriteMixin, ServerStatsCounterWriteMixin, DimensionDataStorageWaitMixin, NbtIoReadWaitMixin, SaveFlushMixin, LevelSaveFlushMixin, WorldAccessWaitMixin
Saved data and player files are written by a background writer. An autosave makes the server thread gzip and write every changed saved-data file (data/*.dat of every dimension) and every player's .dat, advancements and stats file before the tick can go on. The server thread still builds each file's content at the same moment as before (the same NBT and text); one background writer then compresses and writes it with the same streams, the same temporary file and the same replace, strictly in order, so the files end up byte for byte the same. Bytes reach disk a moment later; a hard crash in that window loses the newest copy that vanilla would have written. Reads of these files through Minecraft's loaders and file readers, /save-all flush, world close, server stop and world backups wait for the writer first; a mod that opens such a file with its own stream can still see the previous copy in that moment. With /save-all flush, Forge's level-save event runs after the files are written (1.0.34); after an ordinary autosave it can run while they are still being written. Mods whose saved data writes its own file format (Mekanism, Create, Refined Storage, Structure Gel here) stay synchronous, so does saved data with its own text form (its own toString or hashCode, none here; until 1.0.33 that text was built on every save), and so do player .dat files while any mod listens to Forge's player-save event (none in this pack).
Idea: BetterAutoSave, Fast Async World Save and Paper's incremental saving, idea text only
Measured: 289 saved-data files from 0 bytes to 3 MB, 150 player saves, 40 advancement and 40 stats files came out byte for byte identical, also for unwritable files and broken tags (761 checks). All 500 data files of a reference server at once: the server thread 4.9 s -> 0.9 s. A real autosave of the reference server spent about 175 of its 588 ms in this compression and writing.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server thread of a running server) · Kind: opt
Mixins: LevelDatWriteMixin, LevelDatFlushMixin
level.dat is written by the same background writer. Every autosave also rewrites level.dat (world settings plus Forge's registry snapshot, about 1.2 MB compressed on the reference server) on the server thread. The server thread still builds its content as before; the background writer of the switch above compresses it, writes the temporary file and replaces level.dat (keeping level.dat_old), in order with the other files. Bytes reach disk a moment later; a hard crash in that window loses the newest copy that vanilla would have written. /save-all flush, world close, server stop, backups and every read of level.dat wait for the writer first.
Idea: BetterAutoSave and Paper's incremental saving, idea text only
Measured: the same writer and proof as above; on the reference server the compression and writing of level.dat took about 80 ms of every autosave.
Ships switched off. Set the key to true to use it.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixins: ServerLevelEntityManagerAccessor, EntityManagerEpochsMixin, ServerLevelSaveEpochsMixin, LevelChunkVisibilityMixin, ServerChunkCacheSpawnGateMixin
Ticking chunks remember their spawning gate. Every server tick asks, for each ticking chunk, whether natural spawning is allowed there: a lookup of the chunk in the entity manager's map of every loaded chunk, usually two cache misses per chunk. Each chunk now remembers its last answer. The map changes in one place only (a chunk's status change), and that change marks its 8x8-chunk region as changed, so every chunk of the region asks the map again; saves mark everything as changed. While nothing changes around a chunk, the answer comes from the chunk itself and is always the map's answer. Steps aside when ServerCore is installed, because ServerCore changes the same call.
Idea: our own (found in the 1.0.26 profile while measuring the natural-spawning census).
Measured: offline on the real classes, 1,000 ticking chunks among 6,000 loaded ones: 30.0 -> 10.5 ns per chunk warm, 251.7 -> 119.6 ns with the caches emptied (2.1x); 1.3 million answers identical over 250,000 status changes, saves and a moving player (80% of answers remembered while 24 edge chunks per tick change). In-game JFR before: the lookup was 1.8-2.8% of the integrated server thread (Bons and Furious 1.0.26).
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH · Kind: opt
Mixins: HolderReferenceTagIdsMixin, TagKeyIdMixin
Tag tests compare tag numbers. Every tag test of a block, item, entity type, fluid or biome ("is this block climbable", "is this item a sword") asks the holder's tag set, which hashes the tag key and compares it with the record's generated equals against colliding entries, touching each compared key, its name and both strings. Each tag key now carries a number for its content (equal keys always share a number), and each holder keeps the sorted numbers of its current tag set; a test compares numbers. The holder rebuilds its numbers whenever its tag set is another object (every tag load and reload), so the answer is always the set's own. Sets that are not the JDK's immutable sets keep the original call.
Idea: Leaf ("Cache block state tags"), idea text only; our design at the holder level covers every registry.
Measured: offline on the real classes, 3,000 holders: 41-55 -> 17-21 ns per test warm (2.1-2.6x), 106-185 -> 66-107 ns with the caches emptied (1.5-1.7x), no allocation; 561,200 tests identical over 29,900 tag rebinds, mutable and polluted sets and racing readers (577,000 checks). In-game JFR before: the tag set test was 2.5-2.9% of the integrated server thread (Bons and Furious 1.0.26), 90% of it in the key's equals.
Since 1.0.34. The values this control keeps on a record class are skipped by Gson. Up to 1.0.33 a mod that saves such a record with Gson failed with a NoSuchMethodException, because Gson asks a record for an accessor for every field it holds (reported with Dragon Mounts Remastered on Minecraft 1.21.1; no case is confirmed on Forge 1.20.1). Nothing else changes.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixins: DistanceManagerTrackerAccessor, TickingTrackerEpochsMixin, TickerWrapperMemoMixin, LevelTickerContextMixin, ServerLevelTickerGateMixin
Block-entity tickers remember whether their chunk ticks. Every tick, each block entity in the level's ticking list asks whether its chunk is in block-ticking range: a lookup of the chunk in the ticket tracker's map. vanilla_block_ticking_range_memo answers a repeat of the same chunk, but most chunks hold one or two tickers and sculk sensors ask about other chunks in between, so the map lookups it leaves were still 3.6% of the integrated server thread. Each ticker now remembers its own last answer. The map changes in one place only, and every change advances the tracker's version and marks the 8x8-chunk region it touched, so a ticker asks the map again whenever its region may have changed. While nothing changes around it, the answer comes from the ticker itself and is always the map's answer. Other callers, and tickers of other mods, ask the map as before.
Idea: our own (found in the 1.0.26 profile: the cost vanilla_block_ticking_range_memo leaves).
Measured: offline on the real classes with vanilla_block_ticking_range_memo on: 30,000 tickers of 20 players 17.8 -> 10.0 ns per ticker (1.8x), 2,050 tickers of one player 10.5 -> 9.3 ns, the old memo's 11 bytes per ticker of garbage gone; 3.4 million loop answers identical with the switch on and off, over ticket changes during the loop, chunk unloads, other threads and other levels (93% remembered while a player moves). In-game JFR before: the lookup was 3.6-3.9% of the integrated server thread (Bons and Furious 1.0.26).
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixin: MapItemSavedDataFramedMixin
Maps in item frames stop scanning every player's inventory for every player. Every half second each map in an item frame updates itself once for every player in the dimension, and each of those updates checks, for every player the map has seen, whether that player carries the map, by scanning the whole inventory (with Curios, the curio slots too). For a map in a frame the answer cannot change what happens next (the map counts as carried because it is framed), so that inner check now skips the scan. The first check of each update, whose answer matters, maps held in hands, and other mods' map additions (Moonlight, Better Nether Map, Vivecraft) are unchanged; markers, frames and the data sent to players stay the same.
Idea: Lithium (framed map updates), Paper ("Improve Maps in item frames performance"), titles and idea text only
Measured: public value; not visible in this pack (0.000% in our own recordings). Offline on the real classes, one framed map's update: 1 player 996 -> 765 ns (1.30x), 10 players 7.2 -> 5.1 us (1.40x), 30 players 21.8 -> 15.9 us (1.38x), without Curios' extra slot scan; 600 scenarios (50,400 updates with held maps, removed players and players carrying copies) left identical markers, frames and player states.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixins: GameEventDispatcherMixin, LevelChunkRegistriesAccessor
Game events skip chunk sections without listeners instead of creating empty registries. Every game event (a step, a landing, a splash, eating, a block change, a door or chest...) is offered to the listener registry of every chunk section within 16 blocks, 27 sections. Asking a section for its registry creates an empty one when there is none, and the chunk keeps it for as long as it is loaded, so the next event visits it again. Events now read the registry a section already has and pass over sections that have none, without creating anything. An empty registry has no listener to tell, so the same listeners hear the same events in the same order; block entities and mods that register or remove listeners still use the original lookup, and Valkyrien Skies' handling of events on ships is unchanged.
Idea: Lithium (game event dispatch without empty section dispatchers), idea text only
Measured: public value; not visible in this pack (game event dispatch was 0-0.26% of the integrated server thread in Bons and Furious 1.0.26). Offline on the real classes: an event with no listener nearby 627 -> 213 ns and 888 -> 24 B (2.9x), with a listener in 4% / 30% of the sections 2.8x / 1.6x, a busy sculk area where every section has listeners unchanged; 300 scenarios with 129,000 events, listeners added and removed during events: identical deliveries, while vanilla kept 346,753 empty registries and the switch 1,202.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH · Kind: opt
Mixin: GoalSelectorFlagsMixin
Goal selectors skip the disabled-flag loop when nothing is disabled. Every other tick each mob's goal selectors ask, for every goal, whether one of the goal's control flags (move, look, jump, target) is disabled: a loop over the goal's flags. Flags are only disabled while a rider steers the mob or it sits in a boat, so for nearly every mob the answer is no and the loop decides nothing. The goal's flags are still read once, as before; while nothing is disabled the loop gets an empty set and answers no straight away. Any disabled flag, or a goal without a flag set, runs the original loop.
Idea: Paper (goal selector flag sets); idea text only
Measured: GoalSelector's flag test is 0.08-0.35% of the integrated server thread (Bons and Furious 1.0.26). Offline on the real classes, 4-12 goals per selector, three runs: 1.06-1.22x per selector tick with Radium's goal set (e.g. 260 -> 229 ns), 1.05-1.26x with vanilla's; unchanged (0.94-1.06x) when a flag is disabled; 400,000 operations identical to vanilla incl. every goal call.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixin: ItemEntityMergeMixin
Item merges leave out neighbours that cannot merge before the merge loop. Every item entity looks for neighbours to merge with every 2 to 40 ticks: it collects every item entity in a small box around it and then tries each one, so in a pile it checks every entity of every other item twice and runs the merge test on it as well. The collection now leaves out the neighbours that the merge test would reject on its first two checks (another item, or both counts together above the stack size), using the same comparisons; everything else goes through vanilla's own checks in the same order. The count check is used only while every stack involved holds at most 64, where the merges themselves cannot change its answer, and items with their own stack-size code are never left out. Same merges in the same order, same capability and stack-size calls.
Idea: Lithium (item entity merging grouped by item), idea text only
Measured: public value; not visible in this pack (item merging was 0-0.06% of the integrated server thread in Bons and Furious 1.0.26). Offline on the real classes: a 500-entity pile of 50 item types 33.9 -> 17.8 us and 4,161 -> 566 B per merge attempt (1.9x), one item type over half full 1.8x, a 500-entity mob-farm pile with 60% full stacks 1.3x; with Tactical Fishing's merge check (as in this pack) 15.5 kB -> 0.6 kB per attempt. 400 random piles (56,384 merge attempts, 4,215 merges, capability stacks, stacks above 64, items with their own stack size) identical to vanilla.
Since 1.0.34. A mod's item class whose methods name classes that exist only on the client now takes the original code path. Up to 1.0.33 such a class stopped a dedicated server: this control looks at the methods of those classes, and on a server Java cannot load the client classes they name. In our test pack, Create's potato cannon dropped next to another item stack was enough.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixin: LongJumpToRandomPosMixin
Goats and frogs pick long-jump targets without walking the whole candidate list per pick. Before a long jump, a goat lists every block in an 11 x 11 x 11 box around it (1,330 candidates; a frog 404) and then draws them one at a time, weighted by distance, until one is a usable landing spot. Every draw adds up all remaining weights, walks the list to the drawn one and searches the list again to remove it, and on a steep slope a goat can go through hundreds of draws in one tick (a frog that wants lily pads empties its whole list in one go). The switch keeps running sums of the weights beside the list, so a draw finds its candidate directly. It makes the same random draw, picks the same candidate and leaves the same list, and checks the list against its sums before every pick (any other change to the list falls back to vanilla's own selection with the same draw). Only vanilla's goat and frog behaviours are handled; other mods' jumping mobs keep the original code.
Idea: Lithium (long-jump weighted choice), idea text only
Measured: public value; not visible in this pack (no goat jumped during our own recordings). Offline on the real classes: a goat's 1,330 candidates drawn to the end 1.88 ms -> 0.22 ms (8.6x, 1,412 -> 163 ns per draw), a frog's 404 3.5x, a frog draining its list for preferred blocks 2.4x; 600 scenarios (491,000 draws incl. replaced lists and removals behind its back) gave the same candidates, the same lists and the same random numbers.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH · Kind: opt
Mixins: LevelClipMixin, ClipContextAccessor
Raycasts that ignore fluids skip the fluid reads. Every step of a raycast reads the fluid in the block it crosses, even when the ray ignores fluids (mobs' line of sight, projectile flight, explosion exposure): for those rays the fluid never matters, and the read is a second chunk lookup of the same position plus a section read. Level now runs such rays with Minecraft's own ray walk and makes every call of the original step except that read. Every other ray, and every level whose class has its own fluid lookup, runs the original code. Stands down whenever another mod gives Level its own raycast (Valkyrien Skies' ship raycasts, or any other mod's): that mod's raycast is kept.
Idea: Lithium (raycast), Paper ("Don't lookup fluid state when raytracing"); idea text only
Measured: public value; not visible in this pack (0.00% of the integrated server thread, Bons and Furious 1.0.26; and it stands down here because Valkyrien Skies is installed). Offline on the real classes, line-of-sight rays of 8-32 blocks: 1,194 -> 919 ns per ray with vanilla's chunk cache, 1,078 -> 734 ns with Radium's (1.3-1.5x); other rays unchanged; 40,000 rays identical to vanilla incl. chunk lookups, chunk cache and load tickets.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixins: MoveToBlockGoalSearchMixin, RemoveBlockGoalAccessor
Turtle-egg searches skip sections that cannot hold the target. Zombies, husks, drowned, zombie villagers and zombified piglins (and mod goals built on the same RemoveBlockGoal, such as Alex's Mobs' and Naturalist's egg goals) look for their target block every 5-10 seconds: about 15,500 positions, each a chunk lookup and two block reads. The search now visits the same positions in the same order and makes the same chunk lookups, but skips the block reads in chunk sections whose palette holds neither the target block nor a block with its own canEntityDestroy: there the test is a pure "no". Every other position runs the goal's own test. Applies only to goal and mob classes that keep vanilla's search, test and restriction methods (checked once per class).
Idea: Lithium (non-POI block searches), Leaf, Mobtimizations; idea text only
Measured: public value; not visible in this pack (0.00% of the integrated server thread, Bons and Furious 1.0.26). Offline on the real classes, natural terrain: 1,221 -> 673 us per search with vanilla's chunk cache, 925 -> 502 us with Radium's (1.8x; +332 bytes per search); 6,000 searches identical to vanilla incl. chunk lookups, chunk cache and every canEntityDestroy call.
Since: 1.0.30 · Target: Minecraft 1.20.1 (Forge 47.4.16) · Side: BOTH (acts on the server, incl. the integrated server) · Kind: opt
Mixins: PiglinRepellentSearchMixin, HoglinRepellentSearchMixin
Piglin and hoglin repellent searches skip sections without a repellent. Every piglin and hoglin sensor run (about once a second per mob) looks for the nearest repellent block (soul fire, soul lanterns, warped fungus, ...): 2,601 positions, each a block read and a tag test. The search now visits the same positions in the same order with the same chunk lookups, but skips the block reads in chunk sections whose palette holds no repellent; the first hit is the same position, returned the same way. Only when the whole search box is inside the build height and outside debug worlds; otherwise the original runs.
Idea: Lithium (non-POI block searches), Leaf; idea text only
Measured: public value; not visible in this pack (0.00% of the integrated server thread, Bons and Furious 1.0.26). Offline on the real classes, nether terrain: 144 -> 80 us per search with vanilla's chunk cache, 105 -> 68 us with Radium's (1.5-1.8x; +32 bytes per search); 6,000 searches identical to vanilla incl. chunk lookups, chunk cache and load tickets.
Since: 1.0.30 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: opt
Mixin: StripFormattingMixin
Formatting-code strip without a regex for plain text. Minecraft removes colour and style codes from a text by running a pattern search over it, even when the text has none. JEI does this for every tooltip line of every item while it builds its search index at world join. A formatting code always starts with the section sign, so a text without one comes back exactly as it went in: the very same text object. Such texts are now returned directly; texts with a section sign still go through the pattern search.
Idea: JEIOptimizer (cheaper single-threaded JEI index build), idea text only; the fast path itself is ours
Measured: offline on Minecraft's own class, 2,036,620 strings (159,459 item names and tooltip lines from this pack, every single character, random texts with real codes) gave the same result, the same object where the original returns its input; 235 -> 26 ns and 208 -> 0 bytes per call.
Since: 1.0.30 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: opt
Mixins: EntityClassCountStorageMixin, EntityClassCountSectionMixin, LevelClassCountMixin, EntityGetterClassCountMixin
Vanilla: a few entity types are counted where each level keeps its entities. Needed by crittersandcompanions_red_panda_gate and fdbosses_spawner_presence_gate; it does nothing by itself. Each level keeps a count of red pandas (Critters and Companions) and of boss spawners, Malkuth spawners and Chesed kinetic fields (FD Bosses), updated in the two places where an entity enters or leaves one of the level's entity sections. Searches are not changed by this switch. If something makes a count doubtful (two entities with the same id in one section, or an entity added from another thread), that level's gates run their original searches from then on, with one warning. -Dbons_and_furious.entityClassCountLayer.shadow=true checks every "none" answer by walking the level (for testing).
Measured: offline on the game's own entity managers, 24,000 random adds, moves, removals, visibility changes and unloads: every count equal to a full walk of the sections (4.35 million checks), and every duplicate-id or wrong-thread case was caught. Cost: about 5 ns per entity entering or leaving a section, about 50 ns for a counted entity.
Since: 1.0.30 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: fix
Mixin: EntitySectionXOverflowMixin
Vanilla: entity searches that reach block X 33,554,416 no longer throw. Minecraft finds entities by reading its entity sections one x column at a time. For the column at block X 33,554,416..33,554,431 (and every 67,108,864 blocks from there, for example -33,554,448..-33,554,433) the end of that read overflows and the game throws "Start element is larger than end element", which crashes the thread that searched. Any entity search whose box touches that column does it, for example a runaway Relics essence or a mod's very large search box. That column is now read up to its last section; every other read is the original one, so every search that worked before gives the same result. Loading and unloading the entities of the chunk at x 2,097,151, z -1 had the same overflow and is fixed the same way.
Measured: offline on the transformed class against the original, 90,000 random searches and 40,448 chunk reads: 29,013 searches crashed the original and now return the right sections, all others gave identical results. Ordinary searches cost the same as before (4.7 us, no allocation).
Since: 1.0.30 · Target: Minecraft 1.20.1 · Side: BOTH · Kind: opt
Mixin: Cursor3DCounterMixin
Vanilla: the cell walker of block collision checks counts instead of dividing. Collision checks (for example the check for the block an entity stands on, which runs for moving entities every tick) walk the blocks of a small box with a cursor that worked out the x, y and z of every cell from a running number with two integer divisions. For every box it is used with (all three sizes positive, fewer than 2.1 billion cells) the cursor now steps x and carries into y and z: the same cells in the same order with the same values. Any other cursor runs the original code.
Measured: offline on the transformed class against the original, 9,000 cursors (including zero, negative, huge and overflowing sizes) gave identical cells, 69.7 million checks. Per cell 4.7 -> 2.8 ns (two runs: 5.7 -> 4.3 ns), no allocation; a collision check of an entity at ground level 907 -> 826 ns.
Bons and Furious is licensed GPL-3.0-only with upstream notices retained. Target mod names identify compatibility targets and do not imply endorsement; their code, assets and licenses remain their authors' property. All measurements on this wiki are focused method, phase or reproduction measurements from the pack the mod was built for, taken on the historical build named in each entry; they are not whole-game FPS or TPS promises and are not additive.
Bons and Furious
Minecraft 1.20.1 / Forge 1.0.34
- Home
- Installation and configuration
- How the patches are applied
- All controls
- Measurements and caveats
- Whole-modpack benchmark
- Troubleshooting and log messages
- Generation context fixes
- Version history
Compatibility
- Compatibility
- Known conflicts and workarounds
- Tested mod list
- Redundant mods
- Compatibility and target versions
Controls by mod
- Minecraft (51)
- Forge (6)
- Ad Astra (1)
- Alex's Caves (4)
- Alex's Mobs (2)
- Almost Unified (1)
- AmbientSounds (3)
- Architectury API (1)
- Ars Nouveau (2)
- Artifacts (1)
- Better Combat (1)
- Bosses of Mass Destruction (1)
- Butterflies (1)
- ChunkSending (1)
- CIT Reforged (1)
- Citadel (1)
- CoFH Core (1)
- Colorwheel (1)
- Cooking for Blockheads (1)
- Create (4)
- Critters and Companions (1)
- Cryptic Foes (2)
- Cucumber (1)
- Curios API (6)
- Distant Horizons (17)
- Dungeons Delight (1)
- Dynamic Trees (2)
- Embeddium (13)
- Entity Model Features (4)
- Entity Texture Features (1)
- EntityCulling (1)
- FancyMenu (1)
- Farmer's Delight (2)
- FD Bosses (2)
- Fowl Play (2)
- Fusion (2)
- GeckoLib (5)
- Goblins Tyranny (2)
- Hostile Villages (2)
- Ice and Fire (6)
- ImmediatelyFast (3)
- Immersive Engineering (1)
- Jaden's Nether Expansion (1)
- JEI (7)
- Kiwi (1)
- L2 Library (2)
- Legendary Monsters (1)
- Lionfish API (2)
- ModernFix (4)
- Mowzie's Mobs (1)
- Mutant Monsters (1)
- Nether Depths Upgrade (1)
- Occult (1)
- Oculus (12)
- Oh The Biomes We've Gone (1)
- Particular (1)
- Pehkui (1)
- Pipez (2)
- Placebo (1)
- Presence Footsteps (1)
- Radium Re-Reforged (8)
- Regions Unexplored (1)
- Relics (2)
- Ribbits (1)
- Ryoamic Lights (2)
- Sake's Structures (1)
- Scorched (1)
- Scuba Gear (1)
- Simply Swords (1)
- SlashBlade: Resharped (1)
- Slice & Dice (1)
- Spawn (4)
- Storage Drawers (2)
- Structure Gel API (1)
- Structurify (3)
- TaCZ (2)
- TerraBlender (2)
- Terramity (1)
- Under the Moon (1)
- Valkyrien Skies (15)
- Trackwork model repair
- Living Engineering (moved out in 1.0.20)
Links