Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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/ or src/. The measurement knobs are computed from one prefix by nova_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/ and tests/ for NOVA_* 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.md Nova 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 run or 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.

VariableGatesFor
NOVA_AUTOPILOTarms the scripted state driver; unset, the plugin adds nothingharness
NOVA_AUTOPILOT_DEADLINEseconds before the completion watcher error-exits naming the laggardsharness
NOVA_CAPTUREputs a script on its CAPTURE path - its shot beats write PNGs, its loops recordharness
NOVA_CAPTURE_DIRdirectory relative capture paths stage under; absolute paths ignore itharness

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.

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.

VariableGatesFor
NOVA_PROBEarms frame-time capture, the scene census and the frame-cost breakdownharness
NOVA_PROBE_MODEcorrectness drops the measuring passes from a child runharness
NOVA_PROBE_WARMUP / _FRAMESthe capture window, in frames; wins over an example’s declared oneharness
NOVA_PROBE_OUTdirectory the run writes frametime.csv, <label>.json and census.json intoharness
NOVA_PROBE_LABELthe row label a capture records itself underharness
NOVA_PROBE_RESforced primary-window resolution for the measured runharness
NOVA_PROBE_RENDER_SCALEforces the render-scale lever, holding the rest of the quality preset fixedharness
NOVA_PROBE_MAX_DELTAceiling on how many fixed steps one frame may runharness
NOVA_PROBE_PRESENTpresentation mode forced on the primary windowharness
NOVA_PROBE_QUALITYgraphics preset for the run, recorded in the metadataharness
NOVA_PROBE_SCENARIOthe scenario a sweep cell measuresharness
NOVA_PROBE_SHA / _HOSToverride the recorded git SHA and host tagharness
NOVA_PROBE_CENSUS_FRAMEframes after Playing at which the scene census is takenharness
NOVA_PROBE_FRAMECOST_FRAMESframes averaged into one frame-cost reportharness
NOVA_PROBE_RENDER_DIAGasks the renderer for GPU timestamp queries, so passes can be namedharness
NOVA_PROBE_TIMELINEJSONL path for the run timeline: states, events, variables, markersharness
NOVA_PROBE_INVARIANTSarms the continuous engine-bound invariant checksharness
NOVA_PROBE_CONTRACTJSON path the run declares its wired capabilities toharness
NOVA_PROBE_SNAPSHOT / _SNAPSHOT_FRAMESJSONL path for world-state snapshots, and the frames to take them atharness
NOVA_PROBE_STEPDIAGCSV path for the per-fixed-step physics diagnosticsharness
NOVA_PROBE_STEPDIAG_BODIESthe body-count REGIME floor its end-of-run summary is taken overharness
NOVA_PROBE_SANDBOX_RESOLVER_CHILDmarks the re-executed child in the probe host’s own sandbox testharness

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.

VariableFlagGatesOwnerFor
NOVA_NORENDER--norenderevery AppBuilder::new() in the process assembles a headless app: no device, no window, no winitnova_coreharness
NOVA_MUTE--mutezeroes the audio OUTPUT; the volume setting is untouchednova_gameplayplayer

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 replay seed

Owned by nova_gameplay, next to the entropy plugin it seeds.

VariableGatesFor
NOVA_SEEDseeds the gameplay RNG with one u64, so a driven run replays byte for byte; unset, the OS seeds it and no two runs agreeharness

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.

VariableGatesFor
NOVA_MODDING_CACHE_ROOTmoves the local mod cache off the platform data dirtooling
NOVA_MODDING_PORTAL_URLpoints a native build at another portal treetooling
NOVA_CONFIG_ROOTmoves the settings store off the platform config dirtooling

NOVA_CONFIG_ROOT is deliberately NOT in the modding family. It is the settings store root, and its name is already right.

The menu

VariableGatesOwnerFor
NOVA_MENU_BACKDROPpins the menu backdrop to one menu_backdrop scenario id instead of re-rolling the draw; an unknown id warns and falls backnova_menuharness

Not on the roster

  • Example-local knobs. NOVA_STRESS_PD_*, NOVA_EDITOR_FRAMELOG, NOVA_SOAK_SCENARIO, NOVA_SOAK_SECS belong to one example each and stay literals beside the range that reads them.
  • NOVA_OS_*. Around 180 of these exist and NONE is an environment variable: they are const Color, layout and volume values in nova_os_ui, nova_os and nova_gameplay::audio. A grep for NOVA_[A-Z_]* is dominated by them, so count env::var call sites instead of identifiers.
  • Shell-only. NOVA_UI_PORT, NOVA_GAME_PORT, NOVA_MODS_PORT, NOVA_MODS_DIR, NOVA_PORT_LO/_HI are read by scripts/ and web/webpack.config.js; NOVA_BENCH_* by benchmark/. No Rust reads any of them.
  • 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

  1. 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 in NovaGameplayPlugin, one of them able to panic! on a malformed value, are the worked example of getting this wrong.
  2. Export it through that module’s prelude.
  3. Add it to the roster in tests/env_contract.rs. The test fails until you do, which is the point.
  4. Add a row above.