v0.6.0 - Mods over the wire
For five releases, modding Nova Protocol meant dropping files next to the game and restarting. v0.6.0 is where that changes: a static mod portal ships alongside the game on every deploy, and a new Explore online tab lets you browse, install, update and uninstall mods from inside the game - on native and in the browser - with dependencies that resolve themselves. Around that headline, scenarios got a proper picker with thumbnails and a mid-run skybox swap, the web build got its particles back by moving to WebGPU, event dispatch got measurably faster behind a measure-first gate, and the Mods menu was rebuilt into a two-pane Factorio-style screen. This is the biggest release so far; here is the whole of it, section by section.
The mod portal
Every deploy now publishes a static mod portal next to the game. Each mod is
a validated bundle served under /mods/<id>/<version>/, hashed with sha256, and
listed in a generated catalog.json. There is deliberately no server and no
database behind it - the portal is just static files, so it rides the exact same
GitHub Pages deploy the game already ships on. That choice is the whole reason
the feature is cheap to run: publishing a mod is publishing files, and the
client only ever does plain HTTP GETs against predictable paths.
Validated, hashed, versioned bundles
The /mods/<id>/<version>/ layout is not cosmetic. Pinning the version into the
path means an installed mod always points at an immutable location - an update
is a fetch of a new <version> directory, never a mutation of an existing one.
Each published bundle is validated at build time before it is written, so a
malformed bundle never reaches the catalog, and every file carries a sha256 that
the client checks after download. The generated catalog.json is the single
index the client reads to discover what exists: ids, versions, sizes, and
hashes, all produced from the bundles themselves rather than hand-maintained.
First mod on the shelf: Gauntlet Run
The first mod on the portal is Gauntlet Run, the wave-survival scenario that used to be baked straight into the game. Moving it out onto the portal is the proof the pipeline works end to end: a scenario that used to ship in the binary is now discovered, downloaded, verified and merged exactly like any third-party mod would be. It also makes the base game a little leaner, since players who never touch Gauntlet Run no longer carry it.
Explore online: mods from inside the game
The Explore online tab is what makes that portal live. You browse the catalog in-game and then install, update or uninstall any mod without ever leaving Nova Protocol, and it behaves the same whether you are on native or in the browser. This is the piece that completes the mod-portal arc: discovery, install, update and removal all now happen over the wire, from inside the game.
Progress, offline fallback, and enabled-state you keep
Three details make Explore online feel finished rather than merely functional. Installs report per-file progress, so a multi-file bundle downloading over a slow link shows real movement instead of a frozen spinner. An offline catalog fallback keeps the list usable when the portal is unreachable - you still see what you have and what you last knew about, rather than an empty error screen. And updates preserve whether a mod was enabled: refreshing a mod to a new version never quietly toggles it off, so an update is safe to run on a mod you depend on being active.
A real install pipeline
Underneath Explore online sits an install pipeline built with one goal above all: never corrupt your game. It came together in two layers - a local cache foundation first, then the network client that fills it.
The local cache foundation
Downloaded mods live in a local cache backed by a new mods:// asset source
and a RON installed index that records what is present. The important
property is that a downloaded bundle is not a second-class citizen: it loads and
merges through the exact same pipeline as the mods that ship with the game.
The cache was built and landed as its own foundation - mods:// source,
installed index, and downloaded bundles that load and merge like shipped ones -
before any network code existed, so the merge path was proven against local
files first and the fetch layer had a solid target to write into.
Staged, verified, commit-once installs
On top of that cache, the portal client fetches the portal's catalog.json
and installs or uninstalls mods over the wire, on both native and web. Every
install is staged and verified against both size and sha256, and a bundle is
committed only once every file checks out. That commit-once rule is the
safety property: a download that is interrupted, truncated, or tampered with
fails verification and is discarded before it can replace anything, so a
half-finished or corrupted download never becomes a half-installed mod. The
staging area does the risky work; the live install only ever sees a fully
verified result.
Dependencies that resolve themselves
Mods can now depend on other mods, and v0.6.0 resolves those dependencies end to end rather than leaving the player to untangle them by hand.
- Installing a mod auto-pulls anything it is missing, so you never install a broken half of a pair.
- Enabling a mod auto-enables its transitive dependencies, walking the whole chain rather than just the direct parent.
- Disabling a mod that something else still depends on is refused instead of silently breaking the dependent - the game stops you rather than letting you create an inconsistent state.
- Merge order follows dependency-respecting topological order, so a mod always merges after the mods it builds on, and a dependency's content is in place before anything layers on top of it.
For now this is ids-only: a mod names the mods it needs, but there are no version constraints yet. Pinning specific versions of a dependency is future work; the graph resolution, auto-pull, auto-enable, refuse-on-disable and topological merge are all here today.
The Mods menu, rebuilt
The Mods menu is now a two-pane, Factorio-style screen. The Installed and
Explore online tabs sit over a scrollable mod list where each row carries its
own enable checkbox, and a details side panel renders from the selected mod's
bundle metadata - name, description, version, all read straight out of the
bundle rather than a separate index. The two-pane layout is what lets discovery
(Explore online) and management (Installed) share one coherent screen instead of
living in different corners of the UI.
Hidden mods stay out of the way
Dev and tooling mods can mark themselves hidden: true in the catalog. A
hidden mod stays installed and remains enableable by id from code, but it is kept
out of the player-facing list, so the menu stays about mods a player actually
chose rather than internal scaffolding. This is what lets tooling ride the same
mod system without cluttering the screen a player sees.
Breaking - migrating your mods
Mod metadata moved out of the shared catalog and into each mod's own
*.bundle.ron as a meta block, and
assets/mods.catalog.ron is now a thin pointer list. This is a
format break for the catalog and for any out-of-tree bundles. If you maintain
a bundle outside the tree, move its name, description and version into a
meta block inside the bundle itself, and slim your catalog entry
down to a pointer at that bundle.
A home for scenarios
The main menu gained a Scenarios picker: a two-pane overlay that lists every
playable scenario - base and mod-added alike - with a details pane and a Play
button. Before this, a mod could add a scenario and a player would have no way to
find it short of a console; now mod-added scenarios are first-class and
discoverable next to the built-in ones. To support it, scenarios gained two
optional fields: a thumbnail so the details pane can show what a scenario
looks like, and a hidden flag so a scenario can exist without appearing in
the picker.
Swapping the sky mid-scenario
Scenarios can now change the backdrop while they run. A new SetSkybox
action swaps the skybox cubemap mid-scenario, and it is deferred until the new
image finishes loading - the swap only happens once the replacement is ready.
That ordering is deliberate: a bad or missing path leaves the current sky
unchanged instead of flashing an empty backdrop or crashing the renderer, so an
authoring mistake degrades quietly rather than breaking the scene.
Particles return to the web
Moving the web build from WebGL2 to WebGPU brought the particle effects back online in the browser. Muzzle flash, projectile trails, and the torpedo launch and detonation bursts now render on the web exactly as they do on native - the effects were always there in the code, but the old WebGL2 backend could not run the particle path, so the browser build had been quietly going without them.
When WebGPU is not available
WebGPU is newer than WebGL2 and not every browser has it yet, so the release handles its absence directly. A browser without WebGPU now shows a clear "WebGPU required" message instead of a black canvas, and there is a matching heads-up under the landing page's "Play in browser" button. Between the two, a player on an unsupported browser learns exactly what is wrong at the front door and again at the canvas, rather than staring at a blank screen guessing.
Faster event dispatch, measured first
Modding event dispatch is now indexed by event name. Instead of walking
every registered handler to find the ones that care about an event, dispatch
looks handlers up by the event's name, which pays off sharply when many handlers
are registered. The change was upstreamed to bevy-common-systems (rev
4c81117) rather than kept as a local patch, so the whole ecosystem on that
crate benefits.
The measure-first gate
The number here is earned, not guessed. A new criterion benchmark exercises the modding scenario-dispatch hot path, run with:
cargo bench -p nova_scenario --bench scenario_dispatch
Against that benchmark, name-indexed dispatch measures 17-24% faster under
bursts of 500-5000 handlers, and neutral at the realistic
one-event-per-frame rate - it helps the pathological case and costs nothing in
the common one. The whole approach is governed by a measure-before-optimizing
gate documented in tasks/20260714-083331/modding-perf-report.md: an optimization has to show
up in the benchmark before it ships, not just look faster on paper.
Two optimizations measured and deferred
That gate did its job by talking us out of two changes. Alongside the dispatch index, two sibling optimizations were prototyped and measured:
- filter-key interning - interning the filter keys handlers match on, to make the match cheaper per handler.
- condition-eval compilation - compiling handler conditions ahead of time instead of evaluating them interpretively each dispatch.
Both were measured against the same benchmark, and at realistic event rates their per-handler savings came out as noise - they only mattered in the same extreme handler counts the name index already covers. Rather than ship complexity for a win that does not show up at the rates the game actually runs at, both were deliberately deferred and kept as documented insurance: the analysis is written down, so if event volumes ever climb into that regime the work is a known quantity rather than a fresh investigation.
Fixes
Two bugs got fixed in this release, both small in surface but with real mechanisms behind them.
Scenarios picker no longer crashes on odd thumbnails
The Scenarios picker could crash the renderer when a scenario's thumbnail was
not a plain 2D image. A cubemap or other non-2D texture handed to the picker's
image widget would fault the render path. The fix is twofold: non-2D
thumbnails are now skipped with a warning instead of being forced into a 2D
slot, and images mount only once they have finished loading, so the picker
never tries to display a handle that is not yet a valid 2D texture. A bad
thumbnail now costs a log line, not the renderer.
Local web testing serves the portal same-origin
Testing the mod portal against a local web build used to require a cross-origin
?portal= override to point the game at a separately hosted portal, which
did not match how production serves everything from one origin. scripts/preview-web.sh
now serves the portal same-origin as the game, exactly as the deployed site
does. This closes a local-dev and docs gap: what you test locally now behaves
like production, and there is no override to remember or to forget.
Removed: the Screenshot Reel capture set
The Screenshot Reel capture set no longer ships in the game assets. It was a capture tool, not gameplay, and its scenario has moved into the example that films it - the code that needs it still has it, but players and the web build stop downloading a tool they never use. It is a small size win and a clarity win: the shipped assets are now things a player might actually play, and the capture scenario lives next to the example that is its only real consumer.