Chapter One · 23 February 2026

The World That Doesn't Wait

Frozen Dawn takes the sun away. This chapter is what that does to the world around you, stage by stage, and how little of it you get to see coming.

Left is the world you stand in. Right adds the code, the commits and the six months it took.
What changed

The sun shrinks, the sky goes black and the temperature falls to minus two hundred and seventy three, across seven stages.

What you do with it

Drag the slider and watch a world die in ten seconds, then find the warm spike at every boundary that tells you it is over.

Why it is odd

Nothing is running while you are away. A world you have not loaded in a month is already exactly as dead as it should be.

7phases, zero through six
273degrees below zero at the end
2%the sun's final size
24files held one threshold
1,111lines the loader arrived in
41days of silence it cost
What it actually is

Frozen Dawn does one thing to your world: it takes the sun away, slowly, and never gives it back.

Not as a cutscene. Not as a scripted event on day forty. Everything you can see is on the same slide. The size of the sun. The colour of the sky. How cold it is where you are standing. How long the day lasts. Whether water can still stay liquid.

All of it is read off one number: the fraction of the world's lifespan that has already burned, between 0 and 1. Every visible symptom is a function of that.

Load a world you have not touched in a month and the sky is already black. Nothing had to be running while you were gone.

That decision is the whole mod. No state to desync, no timer to drift, nothing to save except the day counter. Black is simply what the function returns at 0.61.

It also means the whole thing fits on a slider.

Drag the collapse

Drag the handle, click a marker, or use the arrow keys. This is the real thing, not a drawing of it.

The numbers are the real arrays out of PhaseManager.java, run through a line-for-line port of the mod's own interpolate().

NormalPhase 0
Vacuum · no sound
Day 0 of 100progress 0.000
Sun scale1.00×
Temperature0.0°C
Sun brightness1.00
Sky light1.00
Day length1.00
Snowfallnone

Drag · click a marker · arrow keys

The world does not fall apart at a steady rate.

Six arrays and a list of boundaries. That is the whole collapse.

Every value the slider showed you is one row of this table, read at one position. There is no simulation state behind it, no accumulator, no drift. Given the same day, it returns the same world.

The six arrays, PhaseManager.javaJava
// PhaseManager.java private static final float[] PHASE_BOUNDS = {0.0f, 0.05f, 0.12f, 0.22f, 0.34f, 0.46f, 0.60f, 1.00f}; private static final float[] SUN_DISTANCE = {1.0f, 0.35f, 0.12f, 0.06f, 0.04f, 0.03f, 0.02f, 0.0f}; private static final float[] TEMP_OFFSET = { 0f, 0f, -10f, -25f, -45f, -70f, -120f, -273f}; private static final float[] SUN_BRIGHTNESS = {1.0f, 1.0f, 0.9f, 0.7f, 0.45f, 0.2f, 0.05f, 0.0f}; private static final float[] SKY_LIGHT = {1.0f, 1.0f, 1.0f, 0.85f, 0.6f, 0.3f, 0.05f, 0.01f}; private static final float[] DAY_LENGTH = {1.0f, 1.0f, 0.95f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f};

The stages are deliberately uneven. The early ones are short, because they are meant to feel like bad weather. The last one runs for forty percent of the world's life, because it is meant to feel like it will never end.

The sun does most of its shrinking in the first five percent, which is before most people have a bed. By the time anyone notices it is smaller, the fastest part is already behind them.

SUN_DISTANCE goes 1.0 down to 0.35 between the first two stops. That is the steepest change in the entire mod.

How a value is found
interpolate(), the fourteen lines everything callsJava
private static float interpolate(float[] values, int currentDay, int totalDays) { float progress = getProgress(currentDay, totalDays); for (int i = 1; i < PHASE_BOUNDS.length; i++) { if (progress <= PHASE_BOUNDS[i]) { float segStart = PHASE_BOUNDS[i - 1]; float segEnd = PHASE_BOUNDS[i]; float segProgress = (progress - segStart) / (segEnd - segStart); return Mth.lerp(segProgress, values[i - 1], values[i]); } } return values[values.length - 1]; }

Fourteen lines, called by everything. The sky renderer calls it. The freezer calls it. The heads-up display calls it. Nothing owns the collapse, which is why nothing can get out of step with it.

The false calm

This is the one place I broke my own rule and added something that is not strictly derived from the curve, because the curve alone read as a machine.

getFalseCalmRebound(), the javadocJava
/** * Returns a small warmth spike (+3C max) near phase boundaries. * Creates "false calm" moments where the temperature briefly * seems to stabilize. */ public static float getFalseCalmRebound(int currentDay, int totalDays) { ... }

There is one thing in the collapse that is a lie. At the edge of every stage the temperature stops falling and ticks back up, by about three degrees. It changes nothing. What it buys is a moment where you think the worst is over, and then the next stage starts, steeper than the last. Drag the slider across a marker and watch the temperature cell turn amber.

9commits to PhaseManager, ever
7of them in the first three days
12lines of mixin shrink the sun
Where it came from
62191a3Initial commit: Frozen Dawn mod (NeoForge 1.21.1)23 Feb · +2,762 / 91 files

Phases 1 through 5 shipped in the first commit. The bones of the whole thing were there on day one.

ad26cfcAdd Phase 6: Atmospheric Collapse24 Feb · +638 / −92 · 19 files

Stars, suffocation, geothermal ambience, vacuum silence. The ending arrived two days after the beginning.

50d3943Add Phase 0, visual sun scaling via mixin24 Feb · +31 / −17 · 3 files

The famous shrinking sun is a single @ModifyConstant on vanilla's 30.0F sun quad. Twelve lines. It is the most recognisable thing in the mod and the cheapest thing in it.

Three days from empty repository to a world that can end. Everything after that was not building the collapse. It was making the rest of Minecraft agree with it.

A number going down is not a feature. The feature is what the world does about it.

Each stage turns over more of the world around you. Stage 2 makes one pass at it. Stage 4 makes five. The world does not just get colder, it gets colder faster.

Each phase unlocks a specific set of block transformations and escalates the number of checks per player per tick.

NormalPhase 0 · 0.00

Vanilla Minecraft. No effects at all. The opening stretch exists so that the first change has something to be a change from.

TwilightPhase 1 · 0.05

The sun visibly shrinks. Rain becomes frequent, temperature dips slightly. Last chance to see the sun clearly.

sun 1.00 to 0.35no block changes yet

CoolingPhase 2 · 0.12

The cold becomes real at minus ten. Water freezes, farmland and grass die back, snow starts to settle. This is the first phase the block freezer runs at all.

water freezesgrass, podzol, myceliumsnow up to 3 deep

The Long NightPhase 3 · 0.22

The first true survival wall at minus twenty-five. Permanent storms, fog rolls in, and lava begins to cool. Sand and ice both start converting. Checks double.

lava coolsice thickenssand, dead grass2× checks

Deep FreezePhase 4 · 0.34

Surface survival becomes lethal at minus forty-five. Obsidian freezes. Coal ore crystallizes. Magma blocks go out. Dense fog, near darkness. Checks go to five times base.

obsidian freezescoal crystallizespacked ice5× checks

Eternal WinterPhase 5 · 0.46

Endgame cold, minus seventy and falling. Permanent midnight, blizzard whiteout, clouds and celestial bodies stop rendering entirely. Only deep underground is survivable, and even there your heaters shrink: an exposed thermal heater keeps sixty percent of its radius.

blizzard whiteoutpermanent nightmasonry begins to crack

Atmospheric CollapsePhase 6 · 0.60

The last forty percent. Minus one twenty down to absolute zero. It has its own three stages, and it is the only phase where the world gets clearer as it gets worse.

see the next section
And your heat shrinks with it

Stage 5 also quietly shortens your reach. From then on, a heater with nothing over it warms only sixty percent as far.

Every lit thermal heater checks one line before deciding how far it warms. From phase 5 onward an unroofed one keeps sixty percent of its radius, rounded down.

The radius cut, and the number it was tuned againstJava
/** * In phase 5+, exposed heaters (no roof) have 60% radius (distSq × 0.36). * This ensures Diamond exposed (r≈8.4) > Base enclosed (r=7). */ int baseRadius = hasCapacitor ? 28 : 14; // diamond tier if (phase >= 5 && !sheltered) baseRadius = Math.max(2, (int) (baseRadius * 0.6f));
HeaterShelteredExposedWarmth Stone7 / 144 / 835 / 52.5 Iron9 / 185 / 1050 / 75 Gold11 / 226 / 1365 / 97.5 Diamond14 / 288 / 1680 / 120

Radius in blocks, bare / with capacitor. Warmth in degrees, flat anywhere inside the radius.

A diamond heater standing in the open still reaches further than a plain one under a roof, eight blocks against seven. So the best heater never becomes a downgrade, and a roof never becomes compulsory. Shelter just makes what you already built go further.

That comment is the whole balance decision in one line.

What never takes the cut

Campfires, furnaces and lava never take the cut at all. They keep every block of their reach and simply stop mattering instead: a soul campfire is worth twenty eight degrees against an outside temperature of minus seventy.

They sit on a different code path, one that is handed no phase at all, which is why the cut never finds them.

One phase later, a roof stops being enough by itself. What a room has to hold in is no longer heat, and the question changes from how warm you are to whether you can breathe at all. That is the next section.

Depth is the answer, and it is also a ladder

The one honest escape is downward. How deep you have dug is its own ladder, from minus ten at the top of the world to plus eighty at bedrock. Dig far enough and what the surface says stops mattering, which is exactly why stage 6 eventually reaches down and takes that away too.

How deep is deep enoughPick a day, then drag yourself down through the world and watch what you actually feel.
What you feel0°C
Outside air0
Being this deep0
A roof0

Every other phase takes something away by adding something: fog, snow, dark. The last one takes things away by removing the air they were in.

Stage 6 covers the last forty percent of the world's life, and it is the only stage that has stages of its own.

They are constants, not a separate system, so the same single number still drives everything.

The phase 6 thresholds and the stage enumJava
public static final float PHASE6_START = 0.60f; public static final float PHASE6_MID_START = 0.72f; public static final float PHASE6_VACUUM_START = 0.85f; public static final float PHASE6_END = 1.00f; public enum Phase6Stage { INACTIVE, EARLY, MID, VACUUM }

Early0.60 to 0.72

Extreme blizzard. Louder and worse than phase 5, and it is the last thing that behaves like weather. The whiteout is still on, wind is still screaming, and most players assume this is the ending.

Mid0.72 to 0.85

The atmosphere thins. Wind dies. Fog lifts. Visibility comes back, and for the first time since phase 3 you can see to the horizon, which is the point: you can finally see how far the dead goes. Stars fade in on a black sky, their alpha driven directly by how far through this stage you are.

starAlpha = getPhase6MidFadeProgress(progress)

Vacuum0.85 to 1.00

No sound on the surface. Ice sublimates rather than melts. Breathing fails outside a sealed room or a habitable oxygen zone. The mod ships a damage type called atmospheric_suffocation that exists only for this stretch.

frozen atmosphere formssound muffledoxygen required
Frozen atmosphere

Below minus one fifty, anywhere open to the sky, the air itself starts settling out as a block. It is the only thing in the mod that genuinely undoes itself, and it undoes itself for a reason: build a heat source and the deposits around it boil off. The world you get back is exactly as big as the warmth you can hold.

Formation
from
progress 0.85
radius
48
checks
16
chance
10%

Requires phase 6, sky access, and a local temperature under minus 150.

Sublimation
trigger
temp > −150
radius
32
checks
8

Runs on the same tick. Warm the air and the deposit goes.

Exempt
zone
Blast Pit
zone
Thermal vents

Warm zones and volcanic fields are skipped outright, so the sanctuaries stay breathable by construction rather than by luck.

The detail I am most attached to

By the very end there is nothing left to freeze. It keeps going anyway, because a world that stops changing when it is finished feels finished, and this one is supposed to feel like it is still going somewhere without you.

The block freezer keeps escalating inside phase 6 after the surface is done. Past 0.65 it starts a second sublimation pass, and past 0.75 it adds another ten percent on top of every remaining conversion chance.

You walk two thousand blocks east on day seventy and the grass is green.

This is the bug that gave the chapter its name, and it is not really a bug. Minecraft only builds the land when somebody goes there. Everything that makes the world colder only runs close to a player. So land nobody has walked on has never been cold. The apocalypse had been happening, precisely and beautifully, only in the places someone had already stood.

Every ambient system in the mod, the freezer, the snow and the vegetation decay, runs inside a 64 block radius of a player and gets a few hundred block checks per tick.

So it needed a way to give a brand new chunk a past.

The class javadoc, ChunkCatchUpManager.javaJava
/** * Applies coarse apocalypse epochs to naturally loaded chunks. * * This is not a missed-random-tick simulator. It makes stale/new chunks * visually believable for the current phase while staying inside a * bounded server budget. */
What the land only has to look like

You cannot replay two months of missed time, and there would be no point. Nobody was there to see it. The land only has to look like somewhere the last seventy days happened to.

That second sentence is the entire design. The other half of it is that producing the result must not stall the server.

Three budgets, chosen per tick
Normal
time
2.5 ms
blocks
16,384
edits
384

The steady state. A player wandering into fresh terrain at walking pace.

Burst
time
5.0 ms
blocks
49,152
edits
768

Triggered by 64 fresh chunks queued, or 48 pending within 12 chunks of a player. Elytra, boats, portals.

Prewarm
time
8.0 ms
blocks
49,152
edits
1,024

Once, on server start, before anyone is looking. The most generous budget goes where nobody can feel it.

The tick loop exits on whichever of the three limits it reaches first, and then leaves the queue exactly where it was.

The tick loop, and how it decides to stopJava
while (!pendingChunks.isEmpty() && blocks < blockBudget && System.nanoTime() - start < budgetNanos) { ... }
Why the order mattersSeven passes, and the order is load-bearing

The order is not arbitrary. Trees have to come out before the snow is sorted out, or the snow lands on branches that are about to be deleted, and then hangs there in mid air.

A chunk is not transformed in one go. It is walked through seven passes, each with its own cursor, and the work can stop between any two of them and resume on a later tick.

Give a chunk a pastThis is land nobody has ever walked on. Press step and watch seventy days happen to it.
UntouchedMinecraft just built this. It has never been cold, because nobody has ever been here.
The takeaway. Nothing has happened here yet. The apocalypse only ran where somebody stood.
TREE_CLEARStrip dead canopy, 96 blocks deep. First, because everything below depends on it.
DETACHED_SNOWRemove snow that was resting on something the previous pass just took away.
SURFACEThe top face of every column: freeze, convert, kill.
SURFACE_COLUMN48 blocks down from the surface. 12,288 positions per chunk.
VOLUME_SAMPLE768 sampled positions in the deep volume. Sampled, not exhaustive, on purpose.
COAL_SAMPLECoal ore crystallization, its own pass because its rules differ.
ATMOSPHEREFrozen atmosphere deposits, last, once the world beneath them is settled.
A version number is a promise
TRANSFORM_VERSION and needsCatchUp()Java
public static final int TRANSFORM_VERSION = 4; public boolean needsCatchUp(int chunkX, int chunkZ, int transformVersion, int targetDay) { Record record = get(chunkX, chunkZ); return record == null || !record.complete || record.transformVersion < transformVersion || record.targetDay < targetDay; }

Every chunk stores the version of the transform it received and the day it was brought up to. Change the rules, bump the number, and every chunk in every existing save quietly re-qualifies for catch-up the next time it loads. It has been bumped four times. That single integer is the reason I could keep changing what phase 4 means without breaking anybody's world.

One deliberate exception: land inside a protected ORSA structure is skipped entirely. The sanctuaries are supposed to be the places the apocalypse did not reach, and the surest way to guarantee that is to have the catching up agree.

What it actually cost
7192e4efeat(world): add chunk catch-up epoch processing24 May · +1,111 / 5 files

899 lines of manager and a 199 line saved-data class, in one commit. It worked.

477cf36fix(world): stabilize chunk catch-up bursts29 May · +663 / −200 · 3 files

Five days later I rewrote roughly two thirds of it. This is where the three budget modes and the resumable cursors came from, because the first version would happily blow a 200 millisecond tick when you flew somewhere.

···42 days, no commits29 May to 10 July

The longest gap in the entire project sits immediately after this file, and it is the only stretch of the repository with nothing in it at all.

df81cc4fix(world): reconcile snow after tree catch-up10 July · +136 / −11 · 1 file

The first commit back, after six weeks away, is this file again. Floating snow where a tree used to be.

260fdf4fix(world): bound frozen atmosphere catch-up work23 Aug · +41 / −9 · 2 files

The last pass in the list turned out to need a bound of its own. Eight commits total, spread across three months.

Three days to build the collapse. Six months to make the rest of the game agree with it.

The gap between those two numbers is the honest story of this chapter, and it is not the story I expected to be telling. I thought the hard part would be the physics. The hard part was that a single shared value has to be read correctly by forty different things, every one of which was written on a different day by someone who had slightly forgotten the rules.

3days to a world that can end
46commits in chapter one
6phases the world moves through
One constant, twenty-four files
7a3248efix(phase): centralize phase 6 thresholds2 Apr · +152 / −80 · 24 files

The number 0.72 had been typed out by hand in the sky renderer, the wind ambience, the flag physics, the frost overlay, the oxygen HUD, the music controller, the suit ambience and seventeen other places. Changing what "mid phase 6" meant required finding all of them. This commit made it one constant and touched twenty-four files to do it.

22e69afrefactor(phase): clarify phase 6 presentation stages2 Apr · +144 / −98 · 8 files

Same afternoon. This is the one that introduced Phase6Stage, so the question stopped being "is progress above 0.72" and started being "which stage are we in". Twenty-four lines added to PhaseManager, and they paid for themselves within a week.

Two commits, one day, no new behaviour at all. If you played the mod before and after, nothing changed. It is still the most useful thing I did to this system after the first week, and it is the kind of work that leaves no trace in a changelog.

And then the silence

Forty-one days with no commits, starting five days after the chunk loader's rewrite. I would like to say it was a planned break. It was not. I had just spent a week rewriting two thirds of the hardest file in the project because the first version stalled the server, it was finally correct, and I could not make myself open the editor again.

The thing that eventually brought me back was a bug report about snow floating where a tree used to be. Not a grand plan, not a new feature. A one file fix, 136 lines, in the same class I had walked away from.

The mod is about a world that keeps getting colder whether or not anyone is there to watch. I did not notice until I read my own commit history back that I had built the system that handles exactly that, and then tested it on myself.