Dev blog 2026-10-22 · Keel 0.5.3 development

Lighting a beacon

A Dreadnought lights a Beacon, and a faction's Titan answers.

Titans are the raid a Dreadnought opens. This post follows one from the price of the Beacon to the kill banner, tours the one server module that runs it, and says plainly what we have not measured.

Titans are coming in Keel 0.5.3, with the Dreadnought. No released build has them. Every number here is a value in the code or the design documents, and none of them comes from a played fight.

The Raider Titan sprite: an orange winged hull with a dark cockpit slot, inside a thin orange ring. Raider
The Sentry Titan sprite: a pale blue needle-shaped hull with elliptical bands around its middle, inside a thin blue ring. Sentry
The Breaker Titan sprite: a dark purple boxy hull with ribbed plating and side pods, inside a purple ring. Breaker
The three Titan sprites, straight from the game's art files. This art is coming in 0.5.3. We have no capture of a Titan fight yet.
The problem

A top hull needs a fight only it can open.

A Dreadnought is the sixth hull class, and a charter pays a large bill and waits three days for one. Building it is its own story. This one is about where it goes next.

The big fights we had were set by chance or by a developer. Neither is something a charter can plan an evening around, choose a side for, or pay to start.

We wanted the top of the game to be a raid that a charter starts on purpose, with a price, a side to pick, a clock, and a job for every kind of ship. A sieged Dreadnought sits at the heart of it. World bosses stay as a developer event, and Titans are the raid that players start.

IncursionsAppear where the server rolls them, on Layer 2 and deeper and in the Rifts.
World bossesAppear when a developer raises one.
TitansOpen when a charter lights a Beacon, on the layer, at the sector, and at the hour the charter picks.
What we wanted instead

Three rules for a raid.

The rules came from a short pitch and a list of questions we answered one at a time before any code. These three shaped the rest.

Only a Dreadnought opens it, and only a Dreadnought breaks it

The Beacon works in a Dreadnought's hands alone. The Titan wears Aegis plating over a reactor ring, and only the shots of a sieged Dreadnought reach the ring. More small guns do nothing.

Everyone else has a job

Hangar waves arrive every two minutes. Doomsday needs a Jam. The Dreadnought needs repair. Each job counts toward the kill, so a pilot who does only one of them can still reach the reward line.

A kill costs you a friend

The Beacon is bought with standing from the faction the Titan's faction beats. A kill drops your standing with the Titan's own faction to Hostile. You pick a side every time.

What we built

One Beacon, one pocket, one hour.

A Dreadnought lights a one-use Beacon. That opens a 9 by 9 pocket with the Titan at its centre, and a wormhole at the Dreadnought's sector that anyone can follow. The pocket collapses after 60 minutes, or 120 seconds after the kill.

5Standing the rival Enclave charges for a Beacon. One use, and never on the market.
9 by 9Sectors in the pocket box. The Titan sits at the centre.
60 minLife of a pocket. A kill cuts it to 120 seconds more.
4Pockets open at once, at most. A guard so a flood of Beacons cannot starve the tick.
The pocket

A small box with the Titan in the middle.

The pocket is a temporary map named titan, then a number that is never reused. It is built like an Enclave map: the generator keeps 80 to 100 percent of the 81 sectors, leaves the centre and the entry open, and draws from its sprawl and archipelago patterns.

  • Open fire and no Haven, as in a Rift, so a rival who follows the wormhole can shoot at you. Every pocket uses the deepest tables, Layer 6 level, wherever the Beacon was lit.
  • The arrival wormhole sits on an edge sector. Every pilot with a ship in space on the layer when it opens sees it, rivals included. A later arrival finds it as for any wormhole.
  • A Dreadnought can enter by a Titan's wormhole and no other.
The Titan's sectorThe arrival wormhole
Schematic drawn for this post, not a capture. The real shape is generated, so which sectors are missing differs from pocket to pocket.
As a pilot lives it

From the merchant to the banner.

The server owns every step: the price, the permission, the pocket, every hit and every timer. The client shows a button, a sector card, and a panel.

  1. BuyAt the rival Enclave's station, for 5 standing. The Sentries sell the Raider Titan's Beacon, the Breakers the Sentry Titan's, the Raiders the Breaker Titan's. You can hold one of each.
  2. LightA Dreadnought on Layer 2 or deeper, or in a Rift, off Haven's ring and any wormhole. The charter's lead may light it, and so may a member the lead trusts. A Global line and a banner name the layer, never the sector.
  3. FollowAnyone may, rivals included. The wormhole's sector card names the Titan, the pilots inside, and the minutes left. Arrival brings the usual 5 seconds of wormhole grace.
  4. CrackThe sieged Dreadnought shoots the ring. The rest clear the hangar, repair the Dreadnought, and Jam the Titan whenever Doomsday charges. A broken ring opens the hull to everyone for 180 seconds.
  5. CollectA kill pays out and the pocket stays open 120 seconds more. Then everyone inside goes back to the Beacon's sector, with a 60 second arrival flag.
LogThe Raider Titan Beacon is lit. A wormhole is open here for 60 minutes.
GlobalNAME of CHARTER lit a Raider Titan Beacon. A wormhole is open on Layer 4.
LogThe Titan launches its hangar: 14 ships.
LogThe reactor ring is broken. The hull is open for 180 seconds.
LogDOOMSDAY: the Titan is charging on NAME's Dreadnought. Jam it within 30 seconds.
LogNAME's Jam stopped the Titan's Doomsday charge.
LogThe reactor ring regrows. Aegis is up again.
GlobalThe Raider Titan has fallen to CHARTER. 5 pilots shared the kill.
LogThe Titan pocket has closed. You are back on Layer 4.

The wording is copied from the server's titans module. The server fills in the names, the charter, the layer, and the count, so NAME and CHARTER stand in for them here, and the layer and the count are examples.

The rhythm of a pocket

Minutes after the pocket opens, with a pilot inside
01234567891011
Hangar waves: 14 ships every 120 seconds
1414141414
Doomsday: the first charge starts at minute 3 and lasts 30 seconds
30s
Aegis: in this example the ring breaks at minute 5
upupupupupopenopenopenupupupup
Schematic drawn for this post, not a capture. The clocks start when the pocket opens and stand still while nobody is inside. When the ring breaks is up to the fleet, and we picked minute 5 for the drawing. After the first charge, the next one starts 180 seconds after the last one ended, whether it fired or was Jammed.

The fight in numbers

50xReactor ring, as a multiple of one base hull. Only a sieged Dreadnought's shots reach it.
150xHull, as a multiple of the same base. Immune until the ring breaks.
180 sFor a broken ring to regrow. The hull keeps its damage.
4.0xThe Titan's volley, as a multiple of the Carrier volley.
120 sBetween hangar waves. Each is 14 Layer 6 ships, and at most 42 hangar ships live at once.
30 sDoomsday charge time. A Jam on the Titan inside that window stops the shot.
180 sBetween Doomsday charges, counted from the end of the last one.
60%Of a Dreadnought's maximum hull, taken by a Doomsday that fires. It ignores shield.

The base is a Carrier hull scaled the way a large world boss is, at the depth of Layer 6. The Titan has no shield and does not mend. Doomsday picks the Dreadnought that last hit the ring, or else the first one by name, and it needs a Dreadnought inside.

What the pocket panel says

RAIDER TITAN
AEGIS UP: the hull is immune. Break the ring first.
Ring 2500 / 5000
Hull 90000 / 120000
Next Doomsday charge in 1:40
Next hangar wave in 1:15
Pocket closes in 30:05
ChargingDOOMSDAY fires in 0:12. Jam the Titan to break the charge.

The panel is shown only inside a pocket. The numbers are the canned values the client self-test feeds it, not a real Titan's. With Aegis down, line two reads AEGIS DOWN: the hull takes damage. While a charge runs, the Charging line replaces the Doomsday timer.

The kill

What a kill pays, and what it costs.

A pilot is rewarded when they did at least 5 percent of the work. The work is ring and hull damage, counted once. Damage to hangar ships, repairs on the Dreadnought, and a Jam that stops Doomsday are counted three times, so support can reach the line.

The charter keeps the originalA hull blueprint original of the Titan's faction goes into the Dreadnought charter's online storehouse. It is a random class 1 to 5 hull on a random line of that class. The lead or a quartermaster withdraws it. With no online storehouse it falls back to the lighter's blueprints. The charter screen also keeps a trophy for each faction's Titan.
The crew gets copies and XPEvery pilot over the line gets 20 XP, with no daily cap. Each of them except the lighter also gets a hull copy of the Titan's faction, a random class 1 to 5. The lighter has to clear the same line, and the original is the charter's either way.
Everyone rewarded goes HostileStanding with the Titan's faction is set to minus 2.5, which is just inside Hostile, and a kill never raises a lower standing. Hostile pilots lose that faction's Enclave dock. The decision record expects about one quest to climb back to Neutral.
The world hears itA Global chat line and a banner on every online screen name the charter. The lighting banner runs 5 minutes and the kill banner 10. Leaders ranks charters by Titans felled, twenty rows. The Discord bot posts one line for a kill, never a ping. Openings and collapses are not posted there.
Under the hood

One module, eight hooks, one tick.

The server file titans.py is 713 lines and holds nearly all of it. A mixin joins World, and the damage, repair, Jam, contact, tick, and snapshot code calls into it from eight places in five other files.

The moduleserver/titans.py holds the ops, the pocket, the spawn, the tick, Doomsday, the kill, the close, and the views.
The hookscombat.py calls it on every shot at a Titan or a hangar ship. mender.py calls it on repairs, world.py on a Jam and on contacts, wormholes.py runs its tick, and snapshot.py adds its view.
The clienttitan_hud.gd draws the panel. The Beacon button is in ship_tile.gd, the shop rows in merchant_panel.gd, and the pocket wormhole card in sector_card.gd.

One Titan, as a call chain

  1. BuyThe station checks where you are, what you hold, and your standing. Standing is spent, and the Beacon lands in your materials._beacon_buy
  2. LightChecks the hull, the permission, the sector, and the pocket cap. Then it opens the pocket, spawns the Titan, and adds the wormhole._beacon_use, _titan_open, _titan_spawn
  3. TickEvery tick handles the close, the ring regrow, the hangar queue, and a due Doomsday. Once a second it scans who is inside._tick_titans, _titan_scan_pocket
  4. ShotAegis gets the first look at every shot. What gets through is credited to the shooter's share._hurt_npc, _titan_absorb, _titan_note
  5. KillPays once: the original, the copies, standing, XP, the trophy, the banner, and the event record._titan_died, _titan_original
  6. CloseEmpties the pocket and puts every ship back at the Beacon's sector with the arrival flag._titan_close

The main functions

_titan_absorbtitans.py

The first line of damage to a Titan. It returns true when the shot is spent here: only a sieged Dreadnought's shot takes ring points, and every other shot does nothing while the ring stands. The shot that breaks the ring does not spill into the hull.

_titan_scan_pockettitans.py

Runs once a second. With nobody inside it pushes the wave and Doomsday clocks forward, so a first arrival never meets a wave and a charge on the same step. Otherwise it launches a due wave and starts a charge when a Dreadnought is present.

_titan_jammedtitans.py

A Jam that lands on the Titan during a charge cancels it and sets the next charge a full interval away. The Jam is credited as the hull it saved, three times over.

_titan_diedtitans.py

Guarded to run once. It removes the hangar, builds the contributor list from one damage table, and pays out.

_titan_closetitans.py

Safe to call twice. Ships, transit trips, the Titan, hangar ships, wreckage, the wormhole, the banner, and the cached layout all go through one path, whether the pocket ended in a kill, a timeout, or a server load.

_migrate_titanstitans.py

Runs on load. A pocket whose time has passed collapses, and a ship left on a pocket that no longer exists goes to Layer 6, so a restart never strands a pilot.

linestitan_hud.gd

Builds the panel's text from the snapshot, one entry per line. The self-test asserts it, and the panel only names and draws what the server sends.

Choices we are happy with

A Titan is an ordinary NPCIt lives in the same list as every other NPC, with a few flags for the ring and the timers. Targeting, contacts, and combat work on it without a second system.
Clocks wait for companyThe wave and charge clocks stand still while nobody is in the pocket. The 60 minute clock does not, so an empty pocket still collapses.
One close pathA kill, a timeout, and a server load all end in the same function. Ships on their way in are redirected to the Beacon's sector, so nobody lands in a pocket that is gone.
An idle world barely noticesWith no pocket open the tick returns on its first line, and a test pins that it does nothing. A pilot's view of the Titan is built once a round and shared by everyone in the pocket.
The client side

The server owns the fight. The client draws it.

  • titan_hud.gd is 120 lines. It shows only inside a pocket, and an older server that sends no titan key leaves it hidden.
  • Every number and timer on the panel comes from the snapshot. The panel turns seconds into minutes and seconds and takes its colours from the palette file.
  • The Beacon button sits on a Dreadnought's own card while the stash holds a Beacon. Pressing it asks the server, which checks the permission, the sector, and the Beacon.
What the code looks like in numbers
713Lines in titans.py.
27Tests in the TitanTest class.
14Titan rows in the balance file: 12 in the rules, 2 in the factions.
10Constants kept in titans.py, not in the balance file.
What the tests prove

What is pinned, and what is not.

TitanTest lives in test_alpha.py and runs in memory with no network. All 27 passed in 4.7 seconds when we ran them for this post. The whole unit suite runs in the pre-commit hook.

Pinned

  • test_aegis_only_a_sieged_dreadnought_reaches_the_ring_then_anyone_reaches_the_hull: other guns and an unsieged Dreadnought do nothing, a broken ring opens the hull, and the regrown ring leaves the hull damage alone.
  • test_doomsday_charges_on_the_dreadnought_and_a_jam_stops_it: the charge, the Jam that cancels it, and a shot that takes 60 percent of the hull and leaves the shield.
  • test_a_kill_pays_the_charter_storehouse_the_contributors_and_sets_standing: the original, the copies, minus 2.5 that never raises a lower standing, the XP, the trophy, and the banner.
  • test_an_absent_lighter_gets_the_charter_original_but_no_personal_reward: the original is the charter's even when the lighter did nothing.
  • test_a_pocket_survives_a_restart_and_a_lapsed_one_collapses_on_load: a restart keeps a live pocket and closes a lapsed one.
  • test_the_titan_volley_is_the_carrier_volley_times_four_with_no_depth_factor: the volley after we dropped the depth factor.

Not pinned, or thin

  • No long check runs a Titan. A search of alpha_check, playtest_check, and load_check for Titan finds nothing, so no pocket has run over real sockets or against the 25 ms step budget.
  • No played fight. How long a ring lasts, and what Doomsday and the 4.0 volley cost a fleet, are design values and not measurements.
  • The client self-test feeds the panel canned numbers. It pins the text and the hiding, not what a real server sends over a long fight.
  • The reward class is random from 1 to 5, so a Titan can pay a small hull. That is the value as built, and we have not looked at how it feels.
  • The three-times weights for support are new, and nobody has tested that they sort a crowd well.
What we learned and what is next

The first numbers were not the last.

The first shot was too bigThe first build fired 4.0 times the Carrier volley times a depth factor of 7. Our review put that near 17,000 a shot, enough to one-shot almost everything. We dropped the factor. The decision record puts the shot near 2,400 and says to raise it after a playtest.
The lighter is a contributorWe first wrote that the Dreadnought's pilot is always rewarded. A later ruling put the lighter on the same 5 percent line as everyone, with the original going to the charter either way. A test covers a lighter who did nothing.
Support has to countIf only ring and hull damage counted, a pilot who clears hangar ships, repairs the Dreadnought, or Jams a charge could never reach 5 percent. So those count three times. The three is a new number and an untested one.

Where we would push next

  • Fight one with a real fleet and write down what happens: how long the ring lasts, what Doomsday and the volley cost, and how the 5 percent line sorts a crowd.
  • Add a pocket with waves to load_check, held to the same 25 ms budget as every other fight.
  • Add a Titan loop to playtest_check, so a pocket runs over real sockets.
  • Add a capture that enters a pocket, so the next post can show a Titan and not only its sprite.
Join the open alpha

Light one, and tell us how it went.

Keel is free to play on PC, and the open alpha is where Titans will live. Find us in the Keel Discord, tell us what a Titan 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 Titans arrive, try the Jam first: it is the one answer to Doomsday.