cvars: a capture can boot on a player's setting, not only the defaults
A capture reads no `config.toml` — that hermetic boot is exactly what keeps an A/B exact — so there was no way to ask offline whether a report still reproduces at the player's own numbers. `WOW_CVAR=<name>=<value>[;…]` applies after the file and the preset, so it wins; unset it moves nothing and every capture keeps booting on the registered defaults. An unknown name or a malformed step is reported and skipped, because a typo in a measurement should not take the run down with it. Written for grom's report that interiors go "really grey and flat" when it snows with Interior Darkness turned down. That is `interiorGain`, and reproducing it needed his 1.25 in a capture that boots at 0.5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -2340,6 +2340,9 @@ fn load_config(world: &mut World) {
|
||||
if seeds_graphics_preset(&stored_kind) {
|
||||
cvars.seed_graphics_preset();
|
||||
}
|
||||
// Last, so it beats the file and the preset both: the one way to reproduce a player's
|
||||
// setting in a capture, which is otherwise hermetic and boots on the registered defaults.
|
||||
apply_env_overrides(&mut cvars);
|
||||
cvars.take_events()
|
||||
};
|
||||
for event in events {
|
||||
@@ -2347,6 +2350,31 @@ fn load_config(world: &mut World) {
|
||||
}
|
||||
}
|
||||
|
||||
/// `WOW_CVAR=<name>=<value>[;<name>=<value>…]`, applied after the file and the preset so it wins.
|
||||
///
|
||||
/// A capture reads no `config.toml` — that is what keeps an A/B exact — so without this there is
|
||||
/// no way to ask "does it still look wrong at *his* settings?" offline. Unset, it does nothing,
|
||||
/// and every capture keeps booting on the registered defaults.
|
||||
fn apply_env_overrides(cvars: &mut Cvars) {
|
||||
let Ok(script) = std::env::var("WOW_CVAR") else {
|
||||
return;
|
||||
};
|
||||
for step in script.split(';').map(str::trim).filter(|s| !s.is_empty()) {
|
||||
match step.split_once('=') {
|
||||
Some((name, value)) => {
|
||||
let (name, value) = (name.trim(), value.trim());
|
||||
match cvars.set(name, value) {
|
||||
SetOutcome::Changed | SetOutcome::Unchanged | SetOutcome::Staged => {
|
||||
info!("cvar: WOW_CVAR set {name} = {value}");
|
||||
}
|
||||
outcome => warn!("cvar: WOW_CVAR {name} = {value} refused ({outcome:?})"),
|
||||
}
|
||||
}
|
||||
None => warn!("cvar: WOW_CVAR step {step:?} is not <name>=<value>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// MONKEY (presets): which [`StoredConfig`] arm a boot took, kept past the table's move.
|
||||
enum StoredKind {
|
||||
Absent,
|
||||
@@ -3037,6 +3065,57 @@ mod tests {
|
||||
Cvars::default()
|
||||
}
|
||||
|
||||
/// `WOW_CVAR` is how a capture reproduces a player's setting. A capture reads no
|
||||
/// `config.toml` — that hermetic boot is what keeps an A/B exact — so without this there is no
|
||||
/// way to ask offline whether a report still reproduces at *their* numbers. Unset it must do
|
||||
/// nothing at all, or every capture ever taken drifts off the registered defaults.
|
||||
#[test]
|
||||
fn the_env_override_beats_the_defaults_and_is_inert_when_unset() {
|
||||
let _l = crate::local_state::test_env::ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
|
||||
let mut untouched = fresh_registry();
|
||||
{
|
||||
let _c = crate::local_state::test_env::EnvGuard::unset("WOW_CVAR");
|
||||
apply_env_overrides(&mut untouched);
|
||||
}
|
||||
assert_eq!(
|
||||
untouched.take_events().len(),
|
||||
0,
|
||||
"unset, it must not move a single row"
|
||||
);
|
||||
|
||||
let mut cvars = fresh_registry();
|
||||
let before = cvars.get("interiorGain").map(str::to_string);
|
||||
assert_eq!(before.as_deref(), Some("0.5"), "the registered default");
|
||||
{
|
||||
let _c = crate::local_state::test_env::EnvGuard::set(
|
||||
"WOW_CVAR",
|
||||
"interiorGain=1.25; nightGain=0.9",
|
||||
);
|
||||
apply_env_overrides(&mut cvars);
|
||||
}
|
||||
assert_eq!(cvars.get("interiorGain"), Some("1.25"));
|
||||
assert_eq!(cvars.get("nightGain"), Some("0.9"));
|
||||
|
||||
// An unknown name and a malformed step are reported, not fatal: a typo in a measurement
|
||||
// must not take the run down with it.
|
||||
let mut noisy = fresh_registry();
|
||||
{
|
||||
let _c = crate::local_state::test_env::EnvGuard::set(
|
||||
"WOW_CVAR",
|
||||
"noSuchCvar=1;interiorGain",
|
||||
);
|
||||
apply_env_overrides(&mut noisy);
|
||||
}
|
||||
assert_eq!(
|
||||
noisy.get("interiorGain"),
|
||||
Some("0.5"),
|
||||
"a malformed step changes nothing"
|
||||
);
|
||||
}
|
||||
|
||||
/// **The round trip, for every rung**: apply a preset, derive, and get that preset's own name
|
||||
/// back. This is the property the whole ladder rests on — without it the dropdown would show
|
||||
/// `Custom` the instant after the player picked something, which is the exact failure the
|
||||
|
||||
Reference in New Issue
Block a user