"Setting a [ConVar] to the value it already holds does nothing"
▸ SYMPTOM
You drive an action through a [ConVar] — a console command that regenerates terrain, rebuilds a world, reapplies a setting — and on a fresh play session it does nothing. No effect, and no log line at all, not even the setter's own diagnostics.
Run it once, change something, run it again with a different value and it works. Run it twice with the same value and the second run is silent.
▸ CAUSE
The engine's ConVar layer suppresses same-value writes before the C# setter fires. If the convar already holds the value you're assigning, the property setter never runs — so even a setter with no equality guard of its own does nothing.
This compounds badly with statics: [ConVar]-backed statics survive Play stop/start (they are assembly-scoped, not scene-scoped). So a convar left at 42 by a prior play session makes a fresh session's my_convar 42 a no-op, because the value is "already set." The action keyed to the setter never happens, and nothing logs.
It also fires on C# code assignments to the property, not just console commands:
MyDial = 2; // if MyDial already == 2, the setter body never runsSo orchestration code that chains convar-property assignments is structurally fragile. A build loop that does Terrain = seed; Aerial = 2; per candidate can never ship its second candidate: candidate 1 leaves Aerial at 2, so candidate 2's Aerial = 2 is suppressed, nothing rebuilds, and every later candidate is falsely rejected as "not built."
▸ FIX
Don't drive build steps through the ConVar property from code. Call the underlying apply method directly and keep the ConVar as a thin console shim:
[ConVar( "my_dial" )]
public static int MyDial
{
get => _dial;
set { _dial = value; Apply( value ); } // console-only entry point
}
// orchestration code calls this directly, setting the backing field alongside:
public static void ApplyDirect( int value ) { _dial = value; Apply( value ); }If you must go through the convar, force a sentinel transition first so the write is never same-value:
MyDial = 0;
MyDial = 2; // now a real change, setter runsOr make the action idempotent on session start instead of setter-driven.
Diagnose by the silence: a convar command that logs on every apply but printed nothing means the setter never ran — the value was already there.
When you add a new build dial, ship its ApplyXDirect(...) twin in the same commit, so orchestration code never has a reason to touch the property.
▸ WHY IT WORKS
The suppression happens in the engine's convar plumbing, upstream of your C# setter, so the only reliable way to guarantee your side-effect runs is to not depend on the setter firing. Calling the apply method directly (with the backing field set alongside) makes the effect unconditional; a sentinel transition makes the write a genuine value change so the setter fires. Reading the silence as a signal turns "mysteriously did nothing" into a one-line diagnosis: no apply log means the value was already set.
- Published