Environment variables
Every NOVA_* variable the game reads, in one place: what it gates, which
crate owns it, and who it is for. This page is the INDEX. It does not restate
what each variable means - the page that owns the mechanism does that, and the
link in each row goes there.
Nothing type-checks an environment variable. Renaming one compiles clean
and fails at load, or - worse - silently stops arming something and the run
looks fine while measuring nothing. So there is one rule, and
tests/env_contract.rs enforces it:
- Every variable is a named constant, declared once, in the crate that owns
the behaviour it gates. No bare literals at a use site in
crates/orsrc/. The measurement knobs are computed from one prefix bynova_probe::probe_env, so the host that pushes a name into a child run and the child that reads it back cannot disagree. - The test is also a roster. It walks
crates/,src/andtests/forNOVA_*literals and fails on anything it does not name, so a new variable cannot arrive undeclared. examples/keeps its literals on purpose. A range that drifts turns a probe run red, which is detection shipped code does not have (AGENTS.mdNova behavior).
Three names are spelled twice, deliberately: nova_gameplay’s mute policy,
nova_scenario’s Screenshot action and the probe sandbox each need a name
another crate owns, and a dependency edge from a shipping crate to a
dev-tooling one - or from the host harness to the whole asset stack - is worse
than the repetition. The contract test asserts each pair equal.
Who the columns are for
- harness - set by a run script, by CI, or by
probe. A player never sees it, and an unarmed run pays nothing for it. - tooling - set by a test or a contributor’s shell to keep a run off the real profile on disk.
- player - usable on a normal
cargo runor a shipped build.
The harness: what drives a run, and where its pictures go
Owned by nova_autopilot. The mechanism is
The automation harness, which carries the values and
the stall semantics.
| Variable | Gates | For |
|---|---|---|
NOVA_AUTOPILOT | arms the scripted state driver; unset, the plugin adds nothing. Also makes the settings store INERT | harness |
NOVA_AUTOPILOT_DEADLINE | seconds before the completion watcher error-exits naming the laggards | harness |
NOVA_CAPTURE | puts a script on its CAPTURE path - its shot beats write PNGs, its loops record. Also makes the settings store INERT | harness |
NOVA_CAPTURE_DIR | directory relative capture paths stage under; absolute paths ignore it | harness |
NOVA_CAPTURE arms the SHOTS, never a driver, so a capturing run sets
NOVA_AUTOPILOT too and one script owns the window. NOVA_CAPTURE_DIR is also
read by the scenario Screenshot action, which is the in-game photo-mode lever
rather than a harness one.
Either of the two also silences the run and pins its settings. SettingsStorePlugin::from_env
reads the same pair the mute does (HARNESS_ENVS, through
nova_gameplay::harness_env_active) and goes INERT: no load at startup, no
save on change, no window mode applied. So a scripted run flies on the DEFAULTS
whatever the launching player has saved - which is what makes a capture the
same picture on every machine - and cannot write that player’s settings.ron.
A wasm build has no environment to read, so an app that must be inert there
(nova_perf_web) adds the plugin itself with access: SettingsStoreAccess::Inert.
Inert is the far end of a three-way access, not a flag. A normal app gets
Read: it boots on the player’s saved settings and cannot write them back.
NovaMenuPlugin raises that to ReadWrite when it builds the settings panel,
which is the only surface that asks for a value to be kept. An inert store is
never raised, so a scripted run that happens to build a menu still writes
nothing.
Measurement: what a run records about itself
Owned by nova_probe, all under one prefix, all inert unless set. The full
table - defaults, units, and the wasm URL-query twin of each - is the crate’s
own rustdoc (cargo doc --open -p nova_probe), and
Measuring performance is what to read before quoting a
number from any of them.
| Variable | Gates | For |
|---|---|---|
NOVA_PROBE | arms frame-time capture, the scene census and the frame-cost breakdown | harness |
NOVA_PROBE_MODE | correctness drops the measuring passes from a child run | harness |
NOVA_PROBE_WARMUP / _FRAMES | the capture window, in frames; wins over an example’s declared one | harness |
NOVA_PROBE_OUT | directory the run writes frametime.csv, <label>.json and census.json into | harness |
NOVA_PROBE_LABEL | the row label a capture records itself under | harness |
NOVA_PROBE_RES | forced primary-window resolution for the measured run | harness |
NOVA_PROBE_RENDER_SCALE | forces the render-scale lever, holding the rest of the quality preset fixed | harness |
NOVA_PROBE_MAX_DELTA | ceiling on how many fixed steps one frame may run | harness |
NOVA_PROBE_PRESENT | presentation mode forced on the primary window | harness |
NOVA_PROBE_QUALITY | graphics preset for the run, recorded in the metadata | harness |
NOVA_PROBE_SCENARIO | the scenario a sweep cell measures | harness |
NOVA_PROBE_SHA / _HOST | override the recorded git SHA and host tag | harness |
NOVA_PROBE_CENSUS_FRAME | frames after Playing at which the scene census is taken | harness |
NOVA_PROBE_FRAMECOST_FRAMES | frames averaged into one frame-cost report | harness |
NOVA_PROBE_RENDER_DIAG | asks the renderer for GPU timestamp queries, so passes can be named | harness |
NOVA_PROBE_TIMELINE | JSONL path for the run timeline: states, events, variables, markers | harness |
NOVA_PROBE_INVARIANTS | arms the continuous engine-bound invariant checks | harness |
NOVA_PROBE_CONTRACT | JSON path the run declares its wired capabilities to | harness |
NOVA_PROBE_SNAPSHOT / _SNAPSHOT_FRAMES | JSONL path for world-state snapshots, and the frames to take them at | harness |
NOVA_PROBE_STEPDIAG | CSV path for the per-fixed-step physics diagnostics | harness |
NOVA_PROBE_STEPDIAG_BODIES | the body-count REGIME floor its end-of-run summary is taken over | harness |
NOVA_PROBE_SANDBOX_RESOLVER_CHILD | marks the re-executed child in the probe host’s own sandbox test | harness |
NOVA_PROBE_RENDER_DIAG is declared in nova_core rather than nova_probe:
the wgpu feature can only be requested where RenderPlugin is built, and
nova_core is the lowest crate both name.
Outputs off
One variable per output device, and a matching debug-only flag on the game binary. An example has no command line of its own, which is why the environment half exists at all. See Building and running.
| Variable | Flag | Gates | Owner | For |
|---|---|---|---|---|
NOVA_NORENDER | --norender | every AppBuilder::new() in the process assembles a headless app: no device, no window, no winit | nova_core | harness |
NOVA_MUTE | --mute | zeroes the audio OUTPUT; the volume setting is untouched | nova_gameplay | player |
NOVA_MUTE unset still mutes a run that has NOVA_AUTOPILOT or
NOVA_CAPTURE set; NOVA_MUTE=0 forces sound through one. The flag wins over
both, and a muted run says nova audio: output muted for this run once.
The agent bench: an agent at the channel
Owned by nova_bench. The mechanism is The agent bench.
| Variable | Gates | For |
|---|---|---|
NOVA_BENCH_SOCKET | the unix socket the referee answers on; bench play sets it for the agent process, and a cmd: agent reads it | harness |
NOVA_BENCH_PI | the pi binary bench play --agent pi spawns; unset, pi on the path | harness |
The bench sets NOVA_SEED on the child game from --seed, and points the
child at an empty profile under the run directory, so a play never reads or
writes the settings on disk.
The replay seed
Owned by nova_gameplay, next to the entropy plugin it seeds.
| Variable | Gates | For |
|---|---|---|
NOVA_SEED | seeds the gameplay RNG with one u64, so a driven run replays byte for byte; unset, the OS seeds it and no two runs agree | harness |
A value that does not parse as a u64 refuses the boot rather than running
unseeded - a replay that silently lost its seed is the failure the knob exists
to prevent.
Modding, and the settings store
Owned by nova_assets. See /create/publish-a-mod/ for the portal.
| Variable | Gates | For |
|---|---|---|
NOVA_MODDING_CACHE_ROOT | moves the local mod cache off the platform data dir | tooling |
NOVA_MODDING_PORTAL_URL | points a native build at another portal tree | tooling |
NOVA_CONFIG_ROOT | moves the settings store off the platform config dir | tooling |
NOVA_CONFIG_ROOT is deliberately NOT in the modding family. It is the
settings store root, and its name is already right.
It is the store root for a whole PROCESS, so it is a tooling lever, not a test
one: a test that repoints it repoints every test running beside it. A test
names its own root on SettingsStorePlugin instead.
The menu
| Variable | Gates | Owner | For |
|---|---|---|---|
NOVA_MENU_BACKDROP | pins the first backdrop of each menu entry to one role: Backdrop scenario id; the rotation continues from there; an unknown id warns and falls back to the draw | nova_menu | harness |
Not on the roster
-
Example-local knobs.
NOVA_STRESS_PD_*,NOVA_EDITOR_FRAMELOG,NOVA_SOAK_SCENARIO,NOVA_SOAK_SECS,NOVA_VFX_RANGE_BARE_SLUG,NOVA_RAILGUN_AFTERMATH,NOVA_RAILGUN_LIVE,NOVA_WAKE_LOOP,NOVA_COLLAPSE_LOOP,NOVA_SIGHT_LOOPandNOVA_STANDOFF_TRACEbelong to one example each and stay literals beside the range that reads them. The two railgun names pick which of the shipped loopsscreenshot_railgunrecords - the aftermath cut, and the real-speed pass against the default slowed clock - andscripts/capture-web-media.shsets them. Neither goes wrong quietly.NOVA_RAILGUN_AFTERMATHis a duration: unset it falls back to the producer’s own window, and a value that is not a positive number of seconds panics. A row that forgetsNOVA_RAILGUN_LIVE=1records the SLOWED loop name, so the live row’s file never appears - and the scriptrm -fs that file before the run and hard-checks it after, so the capture aborts naming the missing loop, having overwritten the already-staged slowed row on the way.The three
*_LOOPnames put one range each on its RECORDING path instead of its reading one, andscripts/capture-web-media.shsets them.NOVA_WAKE_LOOP=1recordsrailgun_wake_bench’s close pass asnews-0130-railgun-wakeinstead of walking the stills.NOVA_COLLAPSE_LOOP=1recordsstress_hull_collapse’s collapse asnews-0130-hull-collapse; the range still checks its invariants, but the loop pins the clock, so that run’s millisecond claims are the recorder’s and not the collapse’s.NOVA_SIGHT_LOOP=1recordssystem_lock_line_of_sight’s drifting cover asnews-0130-lock-occlusionand runs no assertions at all. Each takes0or1, and any other value is an authoring error that panics rather than picking a path.NOVA_STANDOFF_TRACEis a diagnostic and not a mode: set to anything, it addsloop_goto_standoff’s per-leg print - the plan the computer publishes beside what the hull does, five times a second of world time - and changes nothing the producer records. -
NOVA_OS_*. Around 180 of these exist and NONE is an environment variable: they areconst Color, layout and volume values innova_interface,nova_commandandnova_gameplay::audio. A grep forNOVA_[A-Z_]*is dominated by them, so countenv::varcall sites instead of identifiers. -
Shell-only.
NOVA_UI_PORT,NOVA_GAME_PORT,NOVA_MODS_PORT,NOVA_MODS_DIR,NOVA_PORT_LO/_HIare read byscripts/andweb/webpack.config.js. No Rust reads any of them.The media packagers own two more.
NOVA_UNFREEZEtakes a comma-separated prefix list and re-opens the frozennews-names of the post being authored (NOVA_UNFREEZE=news-0130), inscripts/gen-web-screenshots.pyfor stills andscripts/capture-web-media.shfor loops; without it a name a post already ships is never overwritten.NOVA_REUSE_STAGE=1tellsscripts/capture-web-media.shto package a completedtarget/loop-shotsset rather than re-run the producers, which is the repackage path after a capture that finished. -
Retired.
NOVA_MENU_PATHis read by nothing.examples/ui/editor.rsowns the create-a-ship-and-Play sequence end to end, sosystem_menu_bootdropped the branch; the only surviving mention is that example’s module doc saying the branch is gone. -
Foreign variables the code legitimately reads:
DISPLAY,WAYLAND_DISPLAY,RUST_LOG,BEVY_ASSET_ROOT,CARGO_*,CI,XDG_DATA_HOME,XDG_CONFIG_HOME,LVP_ICD,RUSTFLAGS,VK_ICD_FILENAMES,VK_DRIVER_FILES,WGPU_BACKEND,TRACE_CHROME.
Adding one
- Declare it as a named constant in the crate that owns the behaviour it
gates. A measurement knob belongs in
nova_probe, never in a gameplay plugin - the four fixed-step knobs that lived inNovaGameplayPlugin, one of them able topanic!on a malformed value, are the worked example of getting this wrong. - Export it through that module’s prelude.
- Add it to the roster in
tests/env_contract.rs. The test fails until you do, which is the point. - Add a row above.