v0.12.0 - Build the mission
For eleven releases the editor built one thing: a ship. You placed sections on a grid, watched a skin close around them, and pressed Play to fly the result around a fixed sandbox. Everything else about a mission - who else is out there, what the range holds, what has to happen for you to win - lived in a RON file you wrote by hand, in a format the editor could not read and would not write.
v0.12.0 closes that gap. The editor now holds a whole scenario under one scene tree: your ship, the other ships, the planetoid, the derelicts, the pickets, the beacons and the lights - and, on a second tab, the script that runs them. Every node opens in an Inspector that reads its fields off the config itself, so a thruster shows thrust and a beacon shows the sphere it triggers in. Handlers, filters and actions are built from a menu and edited the same way. An expression is a page you author rather than a string you get right.
Then File > Save writes the whole document out as an ordinary mod - each ship a design, the range a scenario naming them - and F5 brings it back without a restart. What comes out is not an editor save file. It is the same kind of bundle the campaign ships as.
The release carries two more arcs. Every key and pad button in the game became rebindable from a real Settings panel, and the rebind reaches the flight, camera and scenario rigs on the spot. And combat learned that it happens in a vacuum: a torpedo is dropped inert and lights a few body lengths out, a muzzle flash is a ball of burning gas measured in metres rather than a hundred screen dots, and a detonation is the first thing in this game that has ever cast light on the hulls around it.
v0.12.0 is the editor release
Play it in the browser or download the native build from the landing page. The complete compatibility notes are at the end of this post.
The editor holds a whole mission
One document, one tree
The left rail is a scene tree of the actual document. The scenario is the root. Under it sit the ships and the world objects; under a ship sit its sections. One click picks a node. Two clicks enter it - entering a ship isolates that ship, in the tree and on the stage, so you are building one thing rather than hunting for it inside five. Backspace leaves, and so does a single click on any rung above you. Going back up stopped needing a double click on the root.
Rows step in by depth and print a section as its part name, with the ordinal in a column the rail cannot clip, so six alike hulls stay six distinguishable rows. Every row wears an icon for what it is, and resting on one tells you the kind that icon stands for and the whole id the row had to shorten. Resting on a row lights the thing on the stage, and resting on the stage lights the row.
Names are for reading and ids are for scripting, so the tree does both: a ship carries a name of its own, the minted id is on hover, and View > Ids reads the whole tree as ids instead - which is what an event's filter will name them by.
The range is part of the document
The sandbox was previously a fixed backdrop the editor flew you to. Now it is nodes. The planetoid, the hulks, the pickets, the beacons and the lights all come up in the tree, and Play flies the range the tree holds rather than the one that was compiled in.
The top bar's Add menu places anchors, asteroids, beacons, salvage crates and lights in front of the camera. Dragging or deleting a world object works exactly as it does for a ship, and Add obeys where you are: the world palette greys out inside a ship, and Add offers that ship's own parts instead.
Two problems come with editing in three dimensions, and both are answered by drawing something. The first is where things are. The stage stands on a world grid whose cells step by decades as the camera pulls back, with the origin's own X and Z lines picked out and a plumb line under the selection - and the selected node carries move and turn handles, so a node can go somewhere other than the ground plane. The second is what a thing does. A beacon and a salvage crate have no body worth looking at; what matters is the sphere they trigger in. So objects draw what they have no body to show - trigger spheres, a lamp's reach, a sun's direction, each in its own colour. View > World Grid and View > Object Volumes turn both off when you want the shot.
Every node opens
The right-hand Inspector is not a hand-written panel per kind. It reads a node's own fields off its config by reflection, and typing into one writes it back - so a placed object keeps what you set, and a kind that gains a field gains a row without anyone editing the editor.
Reflection alone gives you a column of untyped boxes, so the rows are typed to what they hold. A colour paints a swatch beside its hex, and pressing the swatch opens a floating picker: four channel sliders over a live preview, dragged by its bar and closed by pressing the swatch again. An enum of bare names becomes a row of choices you can switch rather than a readout you cannot. Every vector is three axis-tinted boxes - position, rotation in yaw, pitch and roll degrees, and every offset in a config. There is no scale row: Nova sections mate, they do not stretch.
A field knows what it holds - a unit, a floor, and the step it drags by. A number is scrubbed by pulling its row's name, with the pointer wrapping at the screen edge, and a typed negative radius is refused. Units stand in a column of their own, so every box, chip and checkbox down the panel ends on the same line.
Two field kinds do more than hold text. A row that names an OBJECT opens a
picker of the ids the document actually spawns, and reads unknown beside one
that names nothing - the handler a save would quietly drop. A row that names a
FILE picks from what the installed bundles declare - images, sounds and models,
written as the dep:// ref that resolves - and reads unknown beside a path no
bundle ships. Both are the same idea: the panel offers what exists instead of
letting you type what does not.
The Inspector also reaches into a node's lists. A turret's fire rate lives on a muzzle inside its joint tree, and the panel draws that as an indented tree of ruled headings rather than a column of long paths. By default it opens on what a kind is actually authored through - a turret's fire rate and ammunition, not its joint offsets - and View > All Fields brings the whole config back.
Every Inspector row says what it is for on hover: its whole name, then the sentence the config author wrote beside that field. The panel is what gets read, rather than the documentation.
The script is authored in the panel
SCENE and EVENTS are two modes of one screen
SCENE gives you the world beside the Inspector. EVENTS gives the whole window to the script: the scenario, its handlers, and their filters, actions, steps and gates, as one tree.
A handler is edited like a rock. What it fires on, whether it retires, and every field of every filter and action are all read off the config by reflection, the same way a thruster's thrust is. A filter or an action is switched to any other kind from its own row, and keeps the operands the new kind can hold. Add builds the script - Handler, Filter, Action, Sequence, Step and Gate land under the marked node - and the menu offers only what the mode you are in can place.
A handler can be named, and the tree reads both, so On Enter - picket warden wakes is a row you can find. Six handlers on one trigger used to be six
identical rows.
A condition is a page, not a string
The interesting half of a scenario is its conditions, and a condition is a tree whether or not you get to see it as one. An expression filter now opens its condition as one page: every operator and every leaf on its own row, offered the kinds its place in the grammar allows. A leaf takes any expression as text, or picks one of the scenario's variables.
A Variable Set action builds the value it writes on that same page, and then
its tree row reads as the assignment it is - beat = beat + 1 - rather than as
the name of a kind. That is the difference the whole EVENTS mode is chasing: the
tree should read as what the script does.
The script also opens where you are working. Containers arrive shut, two clicks open one, and a caret says which way each goes. A shipped scenario no longer fills the rail four times over the moment you switch to EVENTS.
The language got shorter
The editor made the scenario language's rough edges obvious, because it had to draw them. Three of them are gone.
A handler can say once: true and retire itself the first time its filters
pass. A beat that happens one time used to need a latch variable, a filter
reading it, and an action writing it - three authored things to express "not
again".
A Sequence action runs ordered beats, each behind its own delay or event gate,
and the engine holds the cursor. A paced chain costs one action instead of a
handler and a step counter per beat.
The proof is the story mod. The Ledger (1.25.0 to 1.26.0) rewrote all five
chapters onto once, keyed timers and Sequence: 74 handlers down to 65, and
half the latch variables gone, 51 down to 25.
The third is not about authoring at all. OnUpdate used to mean "run these
filters every frame", which is what a value-gated scenario spends its budget
on. The pulse now sleeps: it fires only when a variable its filters read is
written, or when the clock crosses a threshold they compare against.
A scenario also says how bright its sky comes up. skybox_brightness is in lux,
and defaults so that a file authoring none still comes up at the shipped 1000.
Save it and it is a mod
File > Save writes the whole document - ships and range together - and File > Open reads it back on the same ids and poses. Ctrl+S saves, and the rail's line reports what happened.
What it writes is not a private format. A range saved in the editor is an ordinary mod: each built ship a design, the range a scenario naming them. Editing a design moves every instance of it. The script comes with it - every handler the editor wrote survives Save and Open on the ids it was authored with, objectives and outcome included.
F5 reads every content file off disk behind a loading screen, as does leaving the editor or the mods panel, so a range saved in the editor reaches the Scenarios list without a restart.
Play flies the whole document: every ship stands where it was dragged and faces where it was turned, and every non-empty AI design spawns as a neutral AI ship beside the player's. A seeded spacecraft IS a ship - it wears the driver's mark, opens on a double click like any other, and its panel reads the hull it flies, who flies it and which side it is on.
Nothing throws a document away without asking. File > New Scenario and Back to Main Menu both put the question up first, and Back to Main Menu is the one that ends the session and deletes the draft - Play round-trips still return to it.
The controls are yours
Settings opens on tabs - Audio, Graphics, Controls, Interface - and Controls lists one binding group at a time, from the main menu and the pause overlay alike. A row draws the button it is bound to as a picture, keycap or pad glyph, and a key that cannot move, like Escape, wears the same chip greyed rather than falling back to plain text.
A binding moves by pressing its chip and then the key or pad button you want. A source another action already holds is refused by name, and so is one a live ship section holds - and Reset Defaults puts the whole table back. Radar hold and tap are one row, so rebinding the gesture moves both halves; they could previously end up on different keys.
The part that took the work is that a rebind made in flight reaches everything that answers it. The flight, camera and scenario rigs rebuild on the spot, and the HUD dock redraws on the new key rather than the one it shipped with. The editor's section-key chip and the NOVA OS ship viewer's chip both read the LIVE binding table rather than the shipped defaults, so a moved verb takes its conflict warning with it. The OBJECTIVES affordance draws the key the NOVA OS is bound to now, not the Tab it ships with.
The Controls list is also honest about what it holds. It reads the bindings the game actually flies with, so a key it prints is the key that fires, and it names what it used to omit: RCS, the wheel's lock cycle, and the scenario advance. Tab and the HUD cycle key are named actions now - the list prints them, the settings file remembers a move, and both answer a rebind like every flight key already did.
Gamepad support caught up at the same time. The shortcuts outside the flight rig fire again - pause, the HUD cycle, the NOVA OS, and the editor's place and Sandbox-return chords all read a resource Bevy no longer has. RCS fine adjust moved off the left trigger, which it shared with Raise Weapons, onto the left stick click. The NOVA OS viewers answer a pad: orbit, pan, cycle, GOTO and reload reuse the flight rig's buttons, which the context rule keeps from ever being live beside them. And the pad's left stick translates under RCS - a held deflection is full authority in that direction, where the mouse only nudges per frame.
Two smaller ones. Graphics offers Windowed or Borderless fullscreen, applied at once and remembered, so the window is no longer stuck at the size it was created with. And the NOVA OS prompt edits like a shell: Home and End, Ctrl+A/E/U/K, and the first deliberate key finishes the boot banner rather than making you wait the reveal out.
Combat happens in a vacuum
The combat effects were built to read on a screen. Several of them were reading as diagrams: a ring where a hit landed, a dotted line where a burst should be, a hundred screen-space dots where a muzzle flash should be. v0.12.0 rebuilt them as things with size, heat and momentum.
A torpedo is dropped, then lit
A torpedo bay no longer fires. It kicks the torpedo clear inert, and the motor catches a few body lengths out. Until it does, the torpedo can neither be shot down nor damage anything - it is a heavy object being pushed away from a hull, which is what a launch tube does.
A bay authors the coast as ignition_delay, in seconds. Zero keeps the old
launch-under-power behaviour, and the shipped bays coast for 0.6 s: ejected at 8
units a second into 0.8 linear damping, which carries the warhead about 3.8
units - several body lengths, and clear of the hull - and hands the drive a
torpedo already doing about 5 units a second.
Two bugs were hiding underneath that, and both only became visible once there was a cold coast to see them in. A launched torpedo was born in the bay's own frame, which laid its nose at a right angle to the way it was travelling - whatever the bay authored. Thrust from the first tick had always hidden it, because the controller swung the nose round inside the frames it took the drive to matter. The other is simpler: a bay was ejecting sideways out of its own roof, across whatever was mounted above it, instead of out of its bow face - the one face on a section that cannot take another section, so it is free to be a muzzle.
The launch reads as two events now, which is what it is. The bay throws a puff as the torpedo leaves, and the drive lights with a flash on the bay it just left. Both of them ride the ship that fired them, rather than hanging at the point in space the bay used to be at - which is the difference between a moving ship and a firing range.
The muzzle flash is an object
The old muzzle flash was 100 dots three pixels across, at any range. A gun firing nearby looked like a flash; the same gun at 300 units looked like confetti, because screen-space dots do not get smaller.
It is now a real object at the barrel: a ball of burning gas with a cone leaving it, sized in metres. It shrinks with distance because it is a thing in the world.
The rounds themselves had the opposite problem. A point-defence cannon at a high fire rate was drawing a dotted line, because each round was a point and the gaps between them were wider than the points. Each round is now stretched along its own travel, so the gap the eye was seeing closes at any frame rate and a burst reads as a stream. And every glowing particle is drawn through a round mask: close to the camera an untextured one is a square, and no colour gradient fixes a corner.
Like the launch puff, a muzzle flash rides the ship that fired it. It used to hang at the point in space the shot left from, which on a manoeuvring ship walked a trail of flashes across the sky behind the barrel.
A hit throws sparks, not a diagram
The gizmo impact rings are gone. A hit throws a burst of incandescent sparks where it landed, thinned by distance the way the camera shake already is. A ring was a diagram of a hit.
Two details make the debris read as material rather than as particles. A chip carved off a hull leaves near-white and cools to gunmetal in under a second - it was just cut out of a ship, so it was never cold. And a body says what it is made of, so a shot rock spalls rock: dusty, slower, more of it, and not glowing. Every rock in the game used to throw the hull's gunmetal chips.
A detonation lights the room
Nothing in combat cast light before this release. A torpedo would detonate against a hull and the fireball would be the only thing in the frame that knew the warhead had gone off. A detonation now lights the hulls around it.
Brief lights are not free, so the number that may burn at once is a graphics-tier budget rather than a constant: six on High, three on Medium, none on Low. A salvo asking for more is refused rather than dimmed - a light that fades in as the tier fills would make the tier visible, and the point of a budget is that you do not see it.
The blast itself gained two things. It carries the torpedo's velocity, so an intercept throws its fireball along the closing course instead of expanding about a point the warhead was only passing through. And it has a core: a white-hot flash that cools through amber while the ejecta leaves through it.
Drives you can see
The basic thruster wore a placeholder barrel with a plume stuck on the end. It now wears an authored exposed bell with its exhaust fitted to the nozzle. The placeholder nozzle also learned to glow: it called itself a hot red and had albedo only, so the side away from the key light read as painted plastic.
Two larger drives join the editor as real multi-cell sections - a 3x3x2 vector drive and a 5x5x3 capital drive - with full-size mass, mounting faces, thrust, skin footprints and exhaust clearance. Large WFC arena ships fly with a seeded stern stamp: one capital drive, or a bank of two to three vector drives.
Sections also keep a stable name for as long as they exist, so a ship read back after a trip out to flight is the ship that was built.
Performance
This is a small performance release and the post will not pretend otherwise. The cycle spent its budget on the editor, and the one measured win is a straightforward piece of waste: the target inset was drawing the whole scene a second time at 512 px into a 256 px panel and throwing three quarters of those pixels away in the downscale. It now renders at the size of the panel it fills.
Two of the release's other changes are structural rather than measured, and
both are worth naming because they bound work instead of trimming it. The
OnUpdate pulse sleeping takes a value-gated scenario from evaluating its
filters every frame to about 2% of them. And the transient-light budget is a
ceiling, so a fifty-torpedo salvo costs the same six lights a two-torpedo one
does on High.
The VFX rework was measured on the new range example at every stage rather than guessed at, and the finished tree measures no slower than the one the work started from - which is the result worth having, because the effects it draws are larger, lit, and more numerous.
One attributed cost did not survive its own check, and the post would rather say so than quietly drop it. Drawing the muzzle flash cost about a millisecond a frame, and that millisecond was attributed to the overdraw of its particles. Halving the particle count returned a fifth of it - less than this host's pass-to-pass spread, so not a result at all. The attribution was therefore wrong, and the remaining suspect is a different change in the same commit: a blast core that had been drawn edge-on as a sliver became a full-area billboard. The halving was kept regardless, because it draws the same ball for half the fill.
More in v0.12.0
The feature story above is the release, but several shipped behaviours also got cleaner:
- The editor reads in the game's own typeface. Its text went out unmarked and rendered in Bevy's built-in face, which has none of the line art the panel is drawn with, so every picker chip was an empty box.
- What is selected is what Delete deletes, at any depth - a trash on the tree row, the Del key, or Edit > Delete. The delete brush that had to be armed is gone.
- Every refusal answers on the editor's own line. A rebind conflict names the action already holding the key, instead of saying so only in a log nobody is reading. The placement verdict is said beside the ghost rather than at the window edge.
- The parts gallery is framed to its parts: each thumbnail fitted to the part's own bounds, sockets drawn on the focus preview, and the filter visible while it is set.
- The rail reads the ship back - turn rate, mass, thrust, hull and part count - and it names what holds the turn down: shorten the hull, or fit another computer.
- Link points read at any distance, the socket the ghost would take is marked apart from the rest, and a part's keybind chip stands off it on a leader.
- Hulls carry their name on the stage, and anything else takes one while it is selected or under the pointer, so five derelicts stop being five identical grey shapes.
- Labels pinned to something in the world - a keybind chip, a NOVA OS blip - sat a label's height off target on a HiDPI screen. They point where they mean at any scale and landscape size.
- A combat lock that lets go says why in the NOVA OS log: target gone, out of range, no longer hostile, or the idle clock running out. It used to just vanish.
- A ship flown under a manual speed cap reads
current / ratedon the speed chip, so a held burn levelling off reads as the cap it is against rather than as a drive that quit pulling. - The targeting reds are three named families - inbound, lock, hot metal - one hue at whatever opacity a widget wants. Five that had drifted snap back onto the family they meant.
- Destroyed sections keep burnt damage on detached wreckage instead of reverting to pristine.
- Typing a backquote at the NOVA OS prompt no longer cycles the HUD behind the monitor: the cycle key answers in flight only, so the instruments are the ones you left.
- The comms stack dropped its V and B keys. Dismissing the oldest card and skipping the backlog were near-identical shortcuts, and the paced window already flows on its own.
- Both side panels are wider and scroll, with a draggable bar beside each saying how far down the list runs, and a wheel notch moving three lines rather than one.
- The path in the top bar is a control: press a rung to go there. View holds front, side, top and iso presets, and an axis rose in the corner says which way is up.
nix runlaunches the game. The flake packages the binary with its assets, the libraries Bevy opens by hand, a desktop entry and an icon, sonix profile installputs Nova Protocol in the application menu.
Modding and compatibility
v0.12.0 adds more than it breaks, and the additions are all defaulted:
ignition_delayis a new field on a torpedo bay, in seconds. It defaults to 0.6 and is skipped when it holds that, so bays authored before this release round-trip byte-identical - but they now coast. Author0.0for the old launch-under-power behaviour.- A torpedo bay's
spawn_rotationmust aim the launch axis - the spawner's +Y - out of the section's bow face. The shipped bays author the quarter turn that does it. A bay that authored nothing was ejecting through its own roof, so a mod relying on the old attitude is relying on a bug. skybox_brightnessis a new scenario field in lux. A file authoring none comes up at the shipped 1000.- A handler may author
once: true, andSequenceis a new action with ordered beats behind delays or event gates. Both are additions; nothing that used the latch-variable pattern stopped working. - The scenario format's private copy of the input-source type is gone. Content
bindings, the weapon components and every rebind surface all speak
nova_input's type now. - Ranges saved by the editor are ordinary mod bundles - designs plus a scenario
- so they load, lint and ship like any other bundle.
Run cargo run content lint against each bundle. The current /create/
reference owns the authored vocabulary, and the bundled content shows the
migrated forms.
Build a mission, then fly it
v0.12.0 is available from the Nova Protocol landing page. Play in the browser, download the native build, or open the editor, drag a range together, write the handler that ends it, and press Play.
The terse list is in CHANGELOG.md. This post is the visible version: an
editor that holds the whole mission, controls that are yours, and combat that
finally behaves like it is happening in a vacuum.