Dev blog 2026-10-22 · Keel 0.5.3 development

Six Rifts that stay put

Six fixed maps with twice the NPCs and twice the pay.

A Rift used to open at random and dissolve. In 0.5.3 there are six, Rift 1 to Rift 6, each matched to a layer, each twice its size, each reachable from every layer. This post covers the rules, a pilot's way in and out, and the code behind them.

The six Rifts are coming in 0.5.3. The live build is 0.5.2.1, and it still has the temporary Rifts this replaces.

Layer 6 in a fight. A fleet of two Patrols stands at the centre of the map with sectors of Raider ships beside it. The contacts list on the right counts fourteen, headed by hostile Raider Carriers and Cruisers. The header reads Open PvP and No Haven, and blocks of orange hexes mark incursions.
Layer 6, from our store screenshots. This is a layer, not a Rift. Rift 6 flies the same NPC tables with twice the ships in a sector.
The problem

A Rift you could not plan around.

Since 0.4.0 a Rift has been a pocket that opens at random, lives a few hours, and dissolves. You could stumble into one. You could not learn one.

At most three Rifts were open at once. Each lived 3 to 6 hours under a random code for a name, and a Rift past its lifetime closed once the last ship left.

A Rift borrowed the tables of Layer 4, Layer 5, or Layer 6, chosen at random. No Rift matched Layer 1, 2, or 3. It paid 1.25 times the drops and Salt of the layer it borrowed, and its belts and clouds were 1.5 times the size and richness.

It was laid out once and never reshaped, so the layer storms of 0.5.0 left it out. A charter could not build in it. A Rift could not be learned, held, or built on.

3Rifts open at most, at once
3 to 6 hHow long one Rift lived
L4 to L6The only layers whose tables a Rift could borrow
1.25xDrops and Salt over the layer it borrowed

These are the numbers of the build that is live today, 0.5.2.1.

What we wanted instead

Three ideas from one short request.

The request for the rework fit in a paragraph. It asked for six fixed Rifts that reshape like the layers, twice the size, doors from every layer, twice the difficulty with loot to match, and charters that can build in them. Three ideas carry the rest.

Fixed, so you can learn them

Rift 1 to Rift 6 are always there. No expiry and no random name. A Rift reshapes on a clock, so the map is new but the place is not.

Twice the danger, twice the pay

A Rift holds twice its layer's NPCs at the same stats. Loot is twice the layer's, and an incursion clear pays at four times. We wrote no new tables.

A door from every layer

A wormhole on any layer can lead into any Rift, so Rift 6 is reachable from Layer 1. The card names the Rift and counts the pilots inside. Nothing asks you to confirm.

What we built

Six maps, each matched to a layer.

Rift N uses Layer N's NPC gear, squads, loot, and Salt tables. It is bigger, fuller, and richer than that layer, and it never closes.

6Fixed Rifts, Rift 1 to Rift 6, with no Haven and no expiry
28 by 28Hidden box, up to 450 sectors, against 20 by 20 and 225 on a layer
2xNPCs in a sector at the same stats, squads and sector caps doubled
12 hBetween storms on one Rift. One Rift storm lands every 2 hours.
Rift 1Layer 1's tables. 414 to 450 sectors. Up to 4 NPCs a sector, 8 in an incursion. A full map pays 2 Notches.Low
Rift 2Layer 2's tables. 370 to 450 sectors. Up to 6 NPCs a sector, 12 in an incursion. A full map pays 2 Notches.Moderate
Rift 3Layer 3's tables. 352 to 450 sectors. Up to 8 NPCs a sector, 16 in an incursion. A full map pays 4 Notches.High
Rift 4Layer 4's tables. 334 to 450 sectors. Up to 12 NPCs a sector, 24 in an incursion. A full map pays 6 Notches.Severe
Rift 5Layer 5's tables. 316 to 450 sectors. Up to 16 NPCs a sector, 32 in an incursion. A full map pays 8 Notches.Deadly
Rift 6Layer 6's tables. 302 to 450 sectors. Up to 20 NPCs a sector, 40 in an incursion. A full map pays 12 Notches.Deadly

The tag on the right is the danger word the wormhole card shows. A Rift reads one step deeper than its layer, because it flies twice the NPCs, and the scale stops at Deadly. Every Rift floor is twice its layer's floor, and every sector cap is twice the layer's. Tests pin both.

Bigger on purpose

A bigger box and the same generator.

A Rift is laid out on a hidden 28 by 28 box and spends up to 450 sectors. A layer spends up to 225 on a 20 by 20 box. The generator is the layer generator, so a Rift gets the same patterns and hazards, and every open sector can still be reached from the centre.

Each Rift storm gives a Rift a new shape, and a sector that holds a charter structure never changes. Storms have a post of their own, so that is all we say about them here.

Layer 20 by 20 up to 225 Rift 28 by 28 up to 450
Schematic drawn for this post, not a capture. The outer squares are the hidden boxes, to scale. Each inner square has an area in proportion to the most sectors a map can spend. Real maps are sprawls, channels, archipelagos, or rings, not squares.

Twice the pay, and four times at an incursion clear.

2xDrops, Remnants, wreck copies, and wreck material from Rift NPCs, and the size and richness of belts and clouds, against the layer it matches
4xWhat a Rift incursion clear multiplies: its Salt, and the chance that the top contributor's copy is a hull copy
1 in 4The most a Rift's blueprint original chance can reach. A layer keeps 1 in 10.
1.25xSalt for a fair PvP kill in a Rift. It did not double.

Incursion NPCs drop nothing, in a Rift or on a layer, so a Rift incursion pays when it is cleared. Each contributor over 5 percent gets 4 Salt on Rift 1, 2, and 3, then 8, 12, and 16 on Rift 4, 5, and 6. That is the layer's Salt times 4, and Layer 1 has none, so Rift 1 counts a base of 1. The hull copy chance is 1 in 4 on a layer and 0.25 times 4, which is certain, in a Rift. Rift 1 has no hull class in its tier and pays module copies. A fair PvP kill pays Salt only in Rift 3 to Rift 6, as only Layer 3 and deeper do.

  • A Rift incursion sector holds twice what a normal Rift sector holds, and each ship has twice the hull, shields, and damage of the same ship on the layer. Waves, warnings, and the clear are the incursion story.
  • Rift 1 runs real incursions that pay copies. Layer 1 keeps only its training incursion.
  • Open fire with full hull loss in every Rift, Rift 1 included. A hull lost there is gone for good.
  • No Haven, no dock, and no layer travel. Charters can build in any of the six, where before they could build only on Layers 4 to 6.
As a pilot lives it

You find a door, read the card, and jump.

The server rolls where a wormhole leads and counts who is inside. The client reads the card it is sent and shows it.

  1. Find a doorEach layer holds about two wormholes. About three in ten new ones lead into a Rift, and any of the six can be the one, so a given Rift takes about one in twenty. An end stays hidden until a ship stands in its sector or a wormhole scanner reaches it.
  2. Read the cardA found end names where it leads, its age, a danger word, and for a Rift how many pilots are inside. Rift 1 reads Low. Rift 5 and Rift 6 read Deadly. Nothing asks you to confirm.
  3. JumpThe trip takes 5 seconds. You land on a random sector of the Rift with a 5 second grace. Nothing can lock you, and you cannot fire.
  4. Work the mapNPC squads hold the sectors, from 4 in a sector on Rift 1 to 20 on Rift 6. A full chart pays double Notches. Retreat flies your escorts to the centre, where they wait.
  5. Find the way outEach Rift keeps one exit wormhole to a random sector of its matching layer. It lives 30 to 90 minutes. A new one opens on the next tick at a random sector of the Rift, and you find it again.
CardHigh danger: Rift 3, twice the NPCs of Layer 3, NPC squads of up to 8; open fire, and ships lost there are gone for good.
LogWormhole KRN-418 to Rift 3. 5 s. You cannot lock or fire.
LogArrived on Rift 3. Transit flag ended.
LogA Rift has no Haven. Leave through a wormhole.
GlobalA plasma front is building on Rift 3. The Rift will reshape in 5 minutes.
GlobalThe storm has passed. Rift 3 has settled into rings.

The wording follows the server's text. The wormhole code is made up for this example, and the server picks the storm name and the new shape.

A full map

Charted, with belts, clouds, and incursions.

This is Layer 2, fully charted. The header reads Charted 100%, and a fleet of six Carriers sits at the centre. A Rift draws the same things on a bigger box and pays twice the Notches for a full chart.

The two travel buttons at the lower right are layer travel from Haven. A Rift has none, so the only ways out are wormholes.

Layer 2 fully charted. The header reads Charted 100 percent. Belts and gas clouds are drawn around a fleet of six Carriers at the centre, and blocks of orange and violet hexes mark two incursions. Two travel buttons at the lower right lead down to Layer 1 and up to Layer 3.
Layer 2 from our store screenshots, not a Rift. The Marks and Notches in the corner come from a prepared test account.

What a new pilot can find.

Layer 1 is safe. Rift 1 is not.On Layer 1 no pilot can fire on you and no ship is ever lost. Rift 1 borrows its tables, not its safety. It is open fire, and a lost hull is gone.
The card is the only warning.A wormhole on Layer 1 can lead into Rift 6. We chose a card label and a pilot count over a confirm step. Testers will tell us whether that is enough.
Under the hood

One small file and a lot of multipliers.

The Rift code is a 212 line file, rifts.py, and a block of about sixty lines in rules.py. Sixteen other server files mention a Rift, and twelve of them do it in six lines or fewer.

Staterifts.py is a mixin on the World class. It holds six fixed rows with no timestamps, keeps one exit open on each Rift, fills a Rift a few squads a step, strengthens an incursion NPC, and migrates old saves.
Numbersrules.py holds nine pay and size constants and small functions such as squad_size, sector_cap, and rift_multiple. The doubled rows are written out as data in tuning.csv.
Shape and clockgenerate_rift in layout.py is four lines: the layer generator with a 28 box and a 450 budget. storms.py gives each Rift its own rotation.

One Rift, from the door to the first kill

  1. DoorEach tick keeps two wormholes on every layer. Three in ten new ones roll a Rift, and one of the six is picked._tick_wormholes, _roll_destination, _rift_for_new_wormhole
  2. CardA found end names the Rift and its danger, and counts the online pilots with a ship in space there._wormholes_view, _danger, _pilots_inside
  3. JumpFollowing escorts gather, the 5 second transit runs, and the fleet lands on the far end._wormhole_jump, _depart, _wormhole_arrived
  4. ShapeThe layout is built from a seed when something first asks for it, and again after each Rift storm._layout, generate_rift
  5. FillA Rift seeds four squads a step until it holds its target once. After that the usual respawn clock runs._tick_squads, _rift_fill, _seed_layer, _spawn_squad
  6. PayA roaming kill rolls drops, Remnants, and copies at the Rift multiple. An incursion NPC is built twice as strong, and its clear pays Salt and copies at four times._spawn_wreck, rift_multiple, _rift_strength, _pay_incursion

The main functions

_tick_riftsrifts.py

Runs every step from the wormhole tick. A Rift with no static exit gets one, leading to the matching layer. That is the whole exit rule, and both a collapse and a restart fall out of it.

_rift_fillrifts.py

Seeds a Rift's squads a few at a time, then marks it filled. A Rift can ask for up to 90 squads, and filling them in one step would stall the 50 ms tick for the first pilot to arrive.

_rift_strengthrifts.py

Doubles the hull, shields, and volley of an incursion NPC. A roaming Rift NPC is left alone and keeps the layer's stats.

squad_size, sector_caprules.py

A Rift reads its own rows, twice the layer's. A squad is bigger on paper than a sector holds from Rift 4 up, 28 against 20 on Rift 6, and the sector cap wins.

rift_multiplerules.py

1 on a layer, 2 in a Rift, 4 in a Rift incursion. The wreck code reads it for drops, Remnants, copies, and material, and the clear reads it for the hull copy chance.

incursion_salt, incursion_bpo_chancerules.py

Salt is the layer's base times the multiple. The original chance is 0.1 times 4, capped at 0.25. The hull copy chance is 0.25 times 4, which a probability caps at 1.

_dangerwormholes.py

Builds the card's danger line. A Rift reads one step deeper than its layer and shows the smaller of its squad size and its sector cap.

_migrate_riftsrifts.py

Runs once at load. Closes the temporary Rifts of an old save, moves each ship to the Rift of the tier it borrowed, and rewrites the saved ids in one pass.

syncsector_card.gd

Draws Leads to and the pilots inside from the wormhole row the server sent. The client counts nothing itself.

Four choices we are happy with

A Rift is a key and a tier.A Rift is named rift-3 and reads Layer 3's tables through one lookup. Gear, squads, loot, and Salt already worked, so the rework added multiples and no parallel tables.
One pair of multiples.Pay is RIFT_PAY and RIFT_INCURSION_PAY. A test checks that no second copy for Salt exists, and that moving the pair moves Salt with it.
Doubling is written as data.The squads, caps, and Notches are explicit rows in the balance sheet, and tests pin each against twice its layer. Retune a layer and a test fails until its Rift row follows.
Fill a big map a little at a time.Four squads a step is not balance. It is there so the first arrival never stalls the tick. The batch is one number.
The client side

The server owns the Rift. The client draws a bigger box.

  • hex_grid.gd accepts a box up to 28, so a Rift sector is on the grid. A layer's box stays 20.
  • main.gd reads the box from the layout the server sends and draws every Rift in one palette. All six Rifts share it.
  • sector_card.gd shows the Rift name and the pilots inside from the snapshot. The self-test feeds it a Rift 3 card and expects both lines.
  • journal_panel.gd lists the Rift storms soonest first from a field of the snapshot. The server owns the schedule.
What the code looks like in numbers
212Lines in rifts.py
9Pay and size constants in rules.py
16Other server files that mention a Rift
21Tests in the Rift test class
What the tests prove

What is pinned, and what is not.

The Rift tests live in a class of their own in test_alpha.py and run on every commit. Here is what they hold, and the gaps we know about.

Pinned

  • test_six_fixed_rifts_each_match_a_layer: six rows, Rift N on Layer N's tier, named Rift N, no Haven.
  • test_each_rift_keeps_one_static_exit_to_its_matching_layer: one exit each, and a collapsed exit on Rift 3 reopens toward Layer 3.
  • test_a_wormhole_from_any_layer_can_lead_into_any_rift: from Layer 1 all six Rifts turn up, and over 600 rolls the Rift share stays within 0.6 to 1.4 times 0.3.
  • test_the_card_names_the_rift_and_counts_the_pilots_inside: the card says Rift 5, counts one pilot, drops to none when the pilot leaves, and a layer end shows no count.
  • test_rift_lead_ships_are_open_fire_with_full_loss_even_rift_1: pilots may fire in Rift 1 and not on Layer 1, and a hull lost in Rift 1 is gone.
  • test_a_rift_flies_twice_the_layers_squads_and_caps: squads, caps, and incursion caps against twice each layer, and a Rift 6 squad of 28 spawns at the cap of 20.
  • test_a_rift_incursion_has_twice_the_count_and_twice_the_strength: 24 a sector on Rift 4, hull, shields, and volley at twice the same ship without the Rift boost, and a roaming NPC that stays below the incursion NPC.
  • test_loot_scales_to_twice_the_layer_and_four_times_in_an_incursion: the multiples, the original chance capped at 0.25, the hull copy chance at 1, and about twice the drops over 3000 seeded rolls.
  • test_a_temporary_rift_from_a_saved_world_migrates: two old Rifts, one with an id that clashes with a new name. Ships, wrecks, and wormholes move, nothing is lost, and a second start finds nothing left to migrate.
  • Size, Notches, storms, charter building, and the Salt pair each have a test. playtest_check.py flies a live pilot through a wormhole into Rift 4, sees docking refused, stands on the exit, and lands on Layer 4.

Not pinned, or thin

  • The code also carries 4 times for wreck material, Remnants, and copies from an incursion NPC, and test_rift_incursion_wrecks_remnants_and_copies_pay_four_times pins it. In play an incursion NPC drops no wreck, because the kill code returns before it makes one. That 4x is reached only in the test, and the clear carries the payout.
  • We found no live run that puts two pilots in a Rift. The open fire rule is a unit test, and the live pilot is alone.
  • The squad fill test runs on Rift 3. We found no test that fills Rift 6 at its full 450 sectors.
  • The load check can force a Rift 6 incursion from its first sector. A unit test checks that the option does it, but no test holds the timing, and this post quotes none.
  • The pilots inside number counts online pilots only. A fleet left in a Rift while its pilot is offline is not in it. A Rift has no Haven, so the berth on logout that 0.5.3 adds has nothing to berth at, and those ships stay in space.
  • No test moves the real clock across a Rift storm hour. The storm tests force one, and the rotation arithmetic has its own test.
  • The client is thin. The self-test checks the card, the 28 box, and the storm list. We found no check that draws a full Rift.
  • Nothing pins whether twice is the right number, and no released build has carried these values yet.
What we learned and what is next

Small lessons from a map that stays put.

Ask what each twice means.The request said twice the sector count, and that can mean twice the sectors or twice the NPCs in each. The rework wants both, in different places, plus twice the strength in an incursion. We wrote the readings down and asked before we built.
A borrowed tier is not a layer.Loot reads Layer 1 for Rift 1, but open fire and full loss must read the Rift. One fair kill check had to compare the Rift's own key and not its tier, and a test now pins Rift 1 as open fire.
A reused name is a migration trap.Old Rifts were saved as rift followed by a counter. The new ones are rift-1 to rift-6, so a save could hold an old rift-3 that meant something else. The migration rewrites every id in one pass so a clash cannot chain.

Where we would push next

  • Read the load check on a forced Rift 6 incursion against the 25 ms target for the 50 ms step.
  • Capture a Rift. We have none, which is why this post shows a layer and says so.
  • Tune the doubles with testers once 0.5.3 is out.
  • Give each Rift its own look. All six share one palette today.
  • Decide what the pilots inside number should say about offline fleets.
  • Bring our vision document up to date. It still calls a Rift a short-lived pocket.
Join the open alpha

Come and read the card before you jump.

Keel is free to play on PC, and the open alpha is where the Rifts will live. Find us in the Keel Discord, tell us what a Rift did to your evening, and file a report from inside the game when something looks wrong.

Players whose reports become fixes are credited in game. When 0.5.3 lands, check how many pilots are inside before you go in.