See also:
Micro Refined Wood Light Setups
Micro Improved Wood Light Setup (outdated, listed for comparison)
Wood Light ‘Visualize’ Map
A New Wood Light Standardization Idea with No Vanila Disparity (hey i’m noticing nobody is clicking through this link but if you have the patience to read through this very long analysis i’m sure you will be able to understand the beauty behind this idea so mind if you check it please?)
In this thread, I will go through:
- The basic behavior of lava and fire
- Detailed walkthrough on certain setups:
- Analysis data from simulations
- How certain blocks affect the wood light
- What you should do differently on certain situations[wip]
- Brief instructions on how to create & test a new setup[wip]
¹ The process that flammable blocks catch fire from lava is called Fire Spread in the wiki, but it is both unintuitive and easily confused with the process air adjacent to flammable blocks catches fire from another fire, which works fundamentally different. I stand by my term from previous posts and in this post I am going to continue calling it Fire Generation.
1. The Basics
1.1 Lava
Chunks consist of one subchunk per 16 blocks of height, each one being a 16×16×16=4096 block cube. Subchunks are distributed vertically starting at the lowest y level. Every chunk tick, some blocks are chosen at random from each subchunk in the chunk. The blocks at those positions are given a “random tick”. Subchunks that do not contain at least one block that can react to random ticks are skipped. All blocks are a possible target of a random tick, including air.
In Java Edition, the number of blocks chosen from each subchunk is specified by/gamerule random_tick_speed(defaults to 3), and one block can be chosen multiple times in one chunk tick.
https://minecraft.wiki/w/Tick#Random_tick
In net.minecraft.fluid.LavaFluid.randomTick():
public void randomTick(World p_207186_1_, BlockPos pos, FluidState state, Random random) {
if (p_207186_1_.getGameRules().getBoolean(GameRules.DO_FIRE_TICK)) {
int i = random.nextInt(3);
if (i > 0) { // 'Popping'
...
} else { // 'Heating'
...
}
}
You can see there are actually two ways that fire generates¹ from lava when it get random ticked. The first (I didn’t find any documentations on this so I had to come up with a name, which I chose ‘Popping’) happens \frac{2}{3} of the time; The second(I call ‘Heating’) happens the rest \frac{1}{3} of the time.
// 'Popping'
BlockPos blockpos = pos;
for(int j = 0; j < i; ++j) {
blockpos = blockpos.add(random.nextInt(3) - 1, 1, random.nextInt(3) - 1);
if (!p_207186_1_.isBlockPresent(blockpos)) { // checks if block is within loaded chunks, world border, etc. , unimportant
return;
}
BlockState blockstate = p_207186_1_.getBlockState(blockpos);
if (blockstate.isAir()) {
if (this.isSurroundingBlockFlammable(p_207186_1_, blockpos)) {
p_207186_1_.setBlockState(blockpos, AbstractFireBlock.func_235326_a_(p_207186_1_, blockpos));
return;
}
} else if (blockstate.getMaterial().blocksMovement()) {
return;
}
}
When it chooses to pop, the spark either pops for up to 1 or 2 blocks high with equal probability. Starting from the lava position, for each y layer it pops up, it moves to a uniformly random block in the 3x3 region above it. Then it checks if the spark is within an air block, if yes it checks if the air block is adjacent to a flammable(catches fire from lava) block. If yes, then the air block is lit on fire. Note that in this process what the flammable blocks are does not matter. In other words, lava lights blocks on fire per air, not per flammable block. Finally, if the block the spark is in meets blocksMovement()(which is to say it has any solid hitbox, including slabs, glass; excluding water, lava, cobweb), the spark immediately dies.
This means that sparks popped have to first go through one of the blocks in the 3x3, not light anything on fire, not get blocked by a block that has a solid hitbox, win a 50% chance, and then get to choose a block in the 3x3 above that, which is a 5x5 region with inequal probability.
\color{Red}{Red} = \frac{1}{9}_{\text{(chance of chosen)}}/\text{pop} \space or
\space \frac{1}{9} \times \frac{2}{3}_{\text{(chance of popping)}} = \frac{2}{27} /\text{random ticked} or
\frac{2}{27} \times \frac{3}{4096}_{\text{(random tick speed)}} = \frac{1}{18342}/\text{tick} = 18 \times \color{White}{White}.
Assuming that no blocks with a solid hitbox or is air adjacent to a flammable block are present in the first layer:
\color{Pink}{Pink} = \frac{1}{2}_{\text{(popping to second layer)}} \times \frac{1}{9}_{\text{(chance of being chosen in the second layer)}}
\times \frac{9}{9}_{\text{(chance of a block being chosen in the first layer can choose the final block)}} = \frac{1}{18} /\text{pop} = 9 \times \color{White}{White}.
\color{Yellow}{Yellow} = \frac{1}{2}_{\text{(popping to second layer)}} \times \frac{1}{9}_{\text{(chance of being chosen in the second layer)}}
\times \frac{6}{9}_{\text{(chance of a block being chosen in the first layer can choose the final block)}} = \frac{1}{27} /\text{pop} = 6 \times \color{White}{White}.
\color{Lime}{Lime} = \frac{1}{2}_{\text{(popping to second layer)}} \times \frac{1}{9}_{\text{(chance of being chosen in the second layer)}}
\times \frac{4}{9}_{\text{(chance of a block being chosen in the first layer can choose the final block)}} = \frac{2}{81} /\text{pop} = 4 \times \color{White}{White}.
\color{Cyan}{Cyan} = \frac{1}{2}_{\text{(popping to second layer)}} \times \frac{1}{9}_{\text{(chance of being chosen in the second layer)}}
\times \frac{3}{9}_{\text{(chance of a block being chosen in the first layer can choose the final block)}} = \frac{1}{54} /\text{pop} = 3 \times \color{White}{White}.
\color{Blue}{Blue} = \frac{1}{2}_{\text{(popping to second layer)}} \times \frac{1}{9}_{\text{(chance of being chosen in the second layer)}}
\times \frac{2}{9}_{\text{(chance of a block being chosen in the first layer can choose the final block)}} = \frac{1}{81} /\text{pop} = 2 \times \color{White}{White}.
\color{White}{White} = \frac{1}{2}_{\text{(popping to second layer)}} \times \frac{1}{9}_{\text{(chance of being chosen in the second layer)}}
\times \frac{1}{9}_{\text{(chance of a block being chosen in the first layer can choose the final block)}} = \frac{1}{162} /\text{pop} = 1 \times \color{White}{White}.
This exact color palette will be reused later as well, I also define
\color{Green}{Green} = 5 \times \color{White}{White} and
\color{Magenta}{Magenta} = 2 \times \color{Red}{Red} = 36 \times \color{White}{White}.
This works the other way around as well, where you can calculate the probability of every air block adjacent to a flammable block catch fire from any certain lava block this way:
Now let’s see the case that lava generates fire through ‘heating’:
// 'Heating'
for(int k = 0; k < 3; ++k) {
BlockPos blockpos1 = pos.add(random.nextInt(3) - 1, 0, random.nextInt(3) - 1);
if (!p_207186_1_.isBlockPresent(blockpos1)) { // checks if block is within loaded chunks, world border, etc. , unimportant
return;
}
if (p_207186_1_.isAirBlock(blockpos1.up()) && this.getCanBlockBurn(p_207186_1_, blockpos1)) {
p_207186_1_.setBlockState(blockpos1.up(), AbstractFireBlock.func_235326_a_(p_207186_1_, blockpos1));
}
}
You can see despite only happening \frac{1}{3} of the time, it tries to generate fire 3 times, making this actually a slightly more effective way of fire generation. In each attempt, the game chooses a uniformly random block in the 3x3 region at the same level of the lava block(the block chosen can be the lava itself, in which case the attempt is wasted). Then it checks if the block chosen is flammable(can catch fire from lava), and has air on top. If yes, then the air on top of the chosen block is lit on fire.
This drastically increases the chance of an air block above flammable blocks neighbour of lava, like above the two blocks at lava level outside a wood light portal, Which is why they are extra important. Note that this doesn’t generate fire on the side of a block, so it cannot directly light the portal.
Note that the air blocks that can be lit on fire here is actually the same blocks that can be lit on fire in the first iteration in popping. This means that for both of these two ways that fire generates from lava, the air block that gets lit on fire are either in the 3x3 1 block above or the 5x5 2 blocks above the lava random ticked, and not at any other level, including the same level as the lava block or anything below. This is partially why pouring lava from the top of the portal turns out to be so inefficient, the lava on top is not really doing anything.
1.2 Fire
1.2.1 Fire ticks
In net.minecraft.block.FireBlock.tick():
public void tick(BlockState state, ServerWorld worldIn, BlockPos pos, Random rand) {
worldIn.getPendingBlockTicks().scheduleTick(pos, this, func_235495_a_(worldIn.rand));
...
}
...
private static int func_235495_a_(Random p_235495_0_) {
return 30 + p_235495_0_.nextInt(10);
}
Fire runs on schedule ticks. Instead of updating every tick, it registers another scheduled update in uniformly 30~39 ticks every time it gets ticked.
1.2.2 Aging & Extinguish
BlockState blockstate = worldIn.getBlockState(pos.down());
boolean flag = blockstate.func_235714_a_(worldIn.func_230315_m_().func_241515_q_());
int i = state.get(AGE);
if (!flag && worldIn.isRaining() && this.canDie(worldIn, pos) && rand.nextFloat() < 0.2F + (float)i * 0.03F) {
worldIn.removeBlock(pos, false); // raining extinguish, unimportant
} else {
int j = Math.min(15, i + rand.nextInt(3) / 2);
if (i != j) {
state = state.with(AGE, Integer.valueOf(j));
worldIn.setBlockState(pos, state, 4);
}
...
\frac{1}{3} of the time, the fire ticked increases its age by 1, with max age of 15. Note that a newly generated fire starts with age 0.
if (!flag) { // block below is not infiniburn
if (!this.areNeighborsFlammable(worldIn, pos)) {
BlockPos blockpos = pos.down();
if (!worldIn.getBlockState(blockpos).isSolidSide(worldIn, blockpos, Direction.UP) || i > 3) {
worldIn.removeBlock(pos, false);
}
return;
}
if (i == 15 && rand.nextInt(4) == 0 && !this.canBurn(worldIn.getBlockState(pos.down()))) {
worldIn.removeBlock(pos, false);
return;
}
}
...
}
There are two ways that fire can naturally extinguish. The first is when no flammable blocks are adjacent to the fire block, and the block below the fire does not have a solid upside surface, then the fire is considered floating and extinguishes immediately. The second is when the fire reaches the age of 15, the block below the fire cannot burn, then there is a \frac{1}{4} chance every fire tick it gets removed.
Note that the minimum time a fire block can reach age 15 is 15_{\text{(times that it has to age)}} \times 1_{\text{(times of fire ticks for it to age in the worst case)}} \times 30_{\text{(minimum time of fire tick)}}
= 450 \space \text{ticks} or 22.5 \space \text{seconds}; the average time it reaches that is
15_{\text{(times that it has to age)}} \times 3_{\text{(times of fire ticks for it to age in the average case)}} \times 34.5_{\text{(average time of fire tick)}}
= 1552.5 \space \text{ticks} or 77.63 \space \text{seconds}, so this has very minimal effect to the process of wood lighting.
Also note that so long as a fire block is not at max age(which basically doesn’t happen in the context of wood lighting portal), has any flammable block adjacent, it will not extinguish in any other way, even if it doesn’t look like it visually. for example, a fire has an oak plank both above it and below it, visually it looks like the block below it is burning, but if that block burns out, the fire immediately switches to be attached to the bottom of the top block. In other words, fire burns per fire, and it extinguishes only if the fire has nothing more to burn. This makes preserving early fire possible like this:
1.2.3 Burn Out Blocks
boolean flag1 = worldIn.isBlockinHighHumidity(pos);
int k = flag1 ? -50 : 0;
this.catchOnFire(worldIn, pos.east(), 300 + k, rand, i);
this.catchOnFire(worldIn, pos.west(), 300 + k, rand, i);
this.catchOnFire(worldIn, pos.down(), 250 + k, rand, i);
this.catchOnFire(worldIn, pos.up(), 250 + k, rand, i);
this.catchOnFire(worldIn, pos.north(), 300 + k, rand, i);
this.catchOnFire(worldIn, pos.south(), 300 + k, rand, i);
BlockPos.Mutable blockpos$mutable = new BlockPos.Mutable();
......
private void catchOnFire(World worldIn, BlockPos pos, int chance, Random random, int age) {
int i = this.func_220274_q(worldIn.getBlockState(pos));
if (random.nextInt(chance) < i) {
...
}
}
Every fire tick the fire tries to burn out all six blocks adjacent to it, with probability \frac{F}{B+k}. B is the basis factor of the side it tries to burn, with east, west, north and south being 300 and up, down being 250. k is the humidity factor, for some reason mojang decided that in humid biomes (including jungle, bamboo jungle, swamp, frozen peaks, and mushroom fields) blocks burn out even faster, presumably to control the fire. And finally, F is the flammablility of the block being burnt(the ‘burn out odds’ shown on wiki), which you can see that they really are odds, blocks with twice the burn odds really do burn out 2 times faster.
Here’s a table for the burn probability per fire tick in a non-humid biome:
| Side | Vertical | |
|---|---|---|
| Oak Log(F = 5) | \frac{1}{60} | \frac{1}{50} |
| Oak Planks, Carpets(F = 20) | \frac{1}{15} | \frac{2}{25} |
| Leaves, Wool(F = 60) | \frac{1}{5} | \frac{6}{25} |
| Grass, TNT(F = 100) | \frac{1}{3} | \frac{2}{5} |
Note that a block that can burn out can have multiple blocks of fire adjacent to it, in which case it burns out faster because every fire will try to burn it. This is why even overworld planks have the same burn odds as carpets, carpets seems to burn out faster simply because it spreads fire to more faces of itself.
BlockState blockstate = worldIn.getBlockState(pos);
if (random.nextInt(age + 10) < 5 && !worldIn.isRainingAt(pos)) {
int j = Math.min(age + random.nextInt(5) / 4, 15);
worldIn.setBlockState(pos, this.func_235494_a_(worldIn, pos, j), 3);
} else {
worldIn.removeBlock(pos, false);
}
Block block = blockstate.getBlock();
if (block instanceof TNTBlock) {
TNTBlock tntblock = (TNTBlock)block;
TNTBlock.explode(worldIn, pos);
}
If a block is decided to burn out from this process, it then decides whether it spawns another fire in its position with probability \frac{5}{A+10}, where A is the age of the fire that burnt it. If it does, then the fire spawned is chosen to be with age A with \frac{4}{5} chance or A+1 with \frac{1}{5} chance, capped at 15. Finally, if the block burnt is a tnt block, it explodes.
1.2.4 Spreading
BlockPos.Mutable blockpos$mutable = new BlockPos.Mutable();
for(int l = -1; l <= 1; ++l) {
for(int i1 = -1; i1 <= 1; ++i1) {
for(int j1 = -1; j1 <= 4; ++j1) {
if (l != 0 || j1 != 0 || i1 != 0) {
int k1 = 100;
if (j1 > 1) {
k1 += (j1 - 1) * 100;
}
blockpos$mutable.func_239621_a_(pos, l, j1, i1);
int l1 = this.getNeighborEncouragement(worldIn, blockpos$mutable);
if (l1 > 0) {
int i2 = (l1 + 40 + worldIn.getDifficulty().getId() * 7) / (i + 30);
if (flag1) {
i2 /= 2;
}
if (i2 > 0 && rand.nextInt(k1) <= i2 && (!worldIn.isRaining() || !this.canDie(worldIn, blockpos$mutable))) {
int j2 = Math.min(15, i + rand.nextInt(5) / 4);
worldIn.setBlockState(blockpos$mutable, this.func_235494_a_(worldIn, blockpos$mutable, j2), 3);
}
}
}
}
}
}
......
private int getNeighborEncouragement(IWorldReader worldIn, BlockPos pos) {
if (!worldIn.isAirBlock(pos)) {
return 0;
} else {
int i = 0;
for(Direction direction : Direction.values()) {
BlockState blockstate = worldIn.getBlockState(pos.offset(direction));
i = Math.max(this.func_220275_r(blockstate), i);
}
return i;
}
}
Every fire block scans the entire 3x6x3 region to try light air blocks on fire, with up to 4 blocks above and 1 block below. Then it calculates l1 = getNeighborEncouragement() from the block scanned, which returns if the block is air and if yes the maximum value of the encouragement level of its adjacent blocks. So, having more flammable blocks around the air block does not speed up the spreading to it. If the block scanned has l1 > 0, which is to say it is air and has any block that spreads fire, then it calculates
i_2 = \lfloor\frac{l_1 + 40 + D \times 7}{A + 30}\rfloor, with D being the difficulty
(Peaceful = 0, Easy = 1, Normal = 2, Hard = 3), and A being the age of the ticked fire. If the fire block is in a humid biome, this factor i_2' = \lfloor \frac{i_2}{2} \rfloor gets further cut in half and rounded down.
If i2 > 0(which always happens in non humid biomes, minimum l_1 is 5 and maximum A is 15), then the scanned block has a p = \frac{i_2 + 1}{K_1} chance of lighting on fire, where K_1 is the space resistance factor that starts with 100 and adds another 100 for each layer higher above 2. Finally, the fire lit is chosen to be with age A with \frac{4}{5} chance or A+1 with \frac{1}{5} chance, capped at 15.
From here you can see that the correlation between the highest encouragement level of an air blocks’ adjacent flammable block l_1 and the probability that it ends up lighting on fire p is not at all linear, it has to pass through a heavy bias term (for difficulty hard that we use for wood lighting, that is 40 + 3 \times 7 = 61) a floor function that truncates decimal values, and another bias term (which affectively is equivillant to a bias of A + 30 in the first bias). In other words, the true odds is equivillant to be adding A + 91 to the encouragement value, which you can see pretty clearly why a block having encouragement of 60(like that of carpets) does not mean it spread 12 times faster than a block with encouragement of 5(like that of wood planks).
\color{Red}i_2 and Probability (\%) of lighting a block in the bottom 3 levels \color{Gray}p for l_1 = 5, x axis being A:
\color{Red}i_2 and Probability (\%) of lighting a block in the bottom 3 levels \color{Gray}p for l_1 = 60, x axis being A:
Now, fire does spread exponentially, so it is also wrong to say that carpets only spread fire max 1.6 times faster than wood planks, but certainly nowhere near 12 times faster.`





























