diff --git a/Assets/DebugOverlay/CVar.cs b/Assets/DebugOverlay/CVar.cs
new file mode 100644
index 0000000..32aa76e
--- /dev/null
+++ b/Assets/DebugOverlay/CVar.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+///
+/// Quake-style configuration variables ("CVars").
+/// These are intended for very cheap runtime access (one static field read)
+/// while still being discoverable and editable from the in-game console.
+///
+
+#region Registry
+
+///
+/// Static global registry of all CVars. Every CVar registers itself in its
+/// constructor. The registry is only used by the console or tooling – normal
+/// runtime code never goes through it.
+///
+public static class CVarRegistry
+{
+ static readonly Dictionary s_Map = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ static readonly List s_List = new List();
+
+ internal static void Register(CVarBase v)
+ {
+ if (s_Map.ContainsKey(v.Name))
+ {
+ Debug.LogWarning($"CVar '{v.Name}' already registered – ignoring duplicate.");
+ return;
+ }
+ s_Map.Add(v.Name, v);
+ s_List.Add(v);
+ }
+
+ public static bool TryGet(string name, out CVarBase v) => s_Map.TryGetValue(name, out v);
+ public static IEnumerable All => s_List;
+
+ ///
+ /// Adds console commands for every registered CVar and the helper "cvar" command.
+ /// Call this once after the Console instance is created.
+ ///
+ public static void RegisterConsoleCommands(Console console)
+ {
+ // Per-CVar command: same name as the variable.
+ foreach (var cvar in s_List)
+ {
+ var local = cvar; // avoid modified-closure issue
+ console.AddCommand(local.Name, (args) => local.HandleConsoleCommand(args), local.Description);
+ }
+
+ // Helper command: "cvar list" (extend with save/load in the future).
+ console.AddCommand("cvar", (args) => CmdCVar(console, args), "cvar list – list all CVars");
+ }
+
+ static void CmdCVar(Console console, string[] args)
+ {
+ if (args.Length == 0 || string.Equals(args[0], "list", StringComparison.OrdinalIgnoreCase))
+ {
+ foreach (var v in s_List)
+ console.Write(" {0,-15} = {1} {2}\n", v.Name, v.ValueString, v.Description);
+ return;
+ }
+
+ console.Write("Unknown cvar argument. Try 'cvar list'\n");
+ }
+}
+#endregion
+
+#region Base class
+///
+/// Non-generic abstract base so that the registry can store different CVar
+/// types in one collection.
+///
+public abstract class CVarBase
+{
+ public readonly string Name;
+ public readonly string Description;
+
+ protected CVarBase(string name, string description)
+ {
+ Name = name;
+ Description = description;
+ CVarRegistry.Register(this);
+ }
+
+ public abstract string ValueString { get; }
+ public abstract void SetValueFromString(string value);
+
+ internal void HandleConsoleCommand(string[] args)
+ {
+ if (args.Length == 0)
+ {
+ Game.console.Write("{0} = {1}\n", Name, ValueString);
+ return;
+ }
+
+ // Accept syntaxes: "var 123", "var = 123"
+ int valueIndex = 0;
+ if (args[0] == "=")
+ {
+ if (args.Length < 2)
+ {
+ Game.console.Write("Expected value after '='\n");
+ return;
+ }
+ valueIndex = 1;
+ }
+ SetValueFromString(args[valueIndex]);
+ Game.console.Write("{0} set to {1}\n", Name, ValueString);
+ }
+}
+#endregion
+
+#region Primitive specialisations
+public sealed class CVarFloat : CVarBase
+{
+ float _value;
+ public CVarFloat(string name, float defaultValue, string description = null) : base(name, description)
+ {
+ _value = defaultValue;
+ }
+
+ public float Value => _value;
+ public static implicit operator float(CVarFloat v) => v._value;
+ public override string ValueString => _value.ToString();
+ public override void SetValueFromString(string s)
+ {
+ if (float.TryParse(s, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var f))
+ _value = f;
+ else
+ Game.console.Write("Could not parse float: {0}\n", s);
+ }
+}
+
+public sealed class CVarInt : CVarBase
+{
+ int _value;
+ public CVarInt(string name, int defaultValue, string description = null) : base(name, description)
+ {
+ _value = defaultValue;
+ }
+ public int Value => _value;
+ public static implicit operator int(CVarInt v) => v._value;
+ public override string ValueString => _value.ToString();
+ public override void SetValueFromString(string s)
+ {
+ if (int.TryParse(s, out var i))
+ _value = i;
+ else
+ Game.console.Write("Could not parse int: {0}\n", s);
+ }
+}
+
+public sealed class CVarBool : CVarBase
+{
+ bool _value;
+ public CVarBool(string name, bool defaultValue, string description = null) : base(name, description)
+ {
+ _value = defaultValue;
+ }
+ public bool Value => _value;
+ public static implicit operator bool(CVarBool v) => v._value;
+ public override string ValueString => _value ? "true" : "false";
+ public override void SetValueFromString(string s)
+ {
+ if (bool.TryParse(s, out var b))
+ _value = b;
+ else if (int.TryParse(s, out var i))
+ _value = i != 0;
+ else
+ Game.console.Write("Could not parse bool: {0}\n", s);
+ }
+}
+
+public sealed class CVarString : CVarBase
+{
+ string _value;
+ public CVarString(string name, string defaultValue, string description = null) : base(name, description)
+ {
+ _value = defaultValue;
+ }
+ public string Value => _value;
+ public static implicit operator string(CVarString v) => v._value;
+ public override string ValueString => _value;
+ public override void SetValueFromString(string s) => _value = s;
+}
+#endregion
diff --git a/Assets/DebugOverlay/CVar.cs.meta b/Assets/DebugOverlay/CVar.cs.meta
new file mode 100644
index 0000000..1dd8f5e
--- /dev/null
+++ b/Assets/DebugOverlay/CVar.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 4b93b9209f3fbe74a8a9aea98ebb6970
\ No newline at end of file
diff --git a/Assets/DebugOverlay/Console.cs b/Assets/DebugOverlay/Console.cs
index 944080d..bd42518 100644
--- a/Assets/DebugOverlay/Console.cs
+++ b/Assets/DebugOverlay/Console.cs
@@ -255,6 +255,30 @@ void ExecuteCommand(string command)
}
else
{
+ // Support syntax like "name=value" or "name= value" for CVars
+ int eqIdx = commandName.IndexOf('=');
+ if (eqIdx > 0)
+ {
+ var potentialName = commandName.Substring(0, eqIdx);
+ if (m_Commands.TryGetValue(potentialName, out commandDelegate))
+ {
+ var rhs = commandName.Substring(eqIdx + 1);
+ // Build new argument list: if RHS not empty use it, otherwise use remaining split args
+ if (!string.IsNullOrEmpty(rhs))
+ commandDelegate(new string[] { rhs });
+ else if (splitCommand.Length > 1)
+ {
+ var args = new string[splitCommand.Length - 1];
+ Array.Copy(splitCommand, 1, args, 0, args.Length);
+ commandDelegate(args);
+ }
+ else
+ {
+ commandDelegate(Array.Empty());
+ }
+ return;
+ }
+ }
Write("Unknown command: {0}\n", splitCommand[0]);
}
}
diff --git a/Assets/Game/Game.cs b/Assets/Game/Game.cs
index 1ba3bdc..80b1250 100644
--- a/Assets/Game/Game.cs
+++ b/Assets/Game/Game.cs
@@ -40,6 +40,13 @@ public void Init()
m_Console = new Console();
m_Console.Init();
+ // Force static initialization of known CVars
+ _ = GameCVars.Fov;
+ _ = GameCVars.GodMode;
+
+ // Register variable commands (CVars)
+ CVarRegistry.RegisterConsoleCommands(m_Console);
+
m_Console.AddCommand("quit", CmdQuit, "Quit game");
m_Stats = new Stats();
diff --git a/Assets/Game/GameCVars.cs b/Assets/Game/GameCVars.cs
new file mode 100644
index 0000000..ddbbe80
--- /dev/null
+++ b/Assets/Game/GameCVars.cs
@@ -0,0 +1,11 @@
+///
+/// Example CVars used by the game. Feel free to add more.
+///
+public static class GameCVars
+{
+ // Visuals
+ public static readonly CVarFloat Fov = new CVarFloat("fov", 60f, "Vertical field of view (degrees)");
+
+ // Gameplay toggles
+ public static readonly CVarBool GodMode = new CVarBool("god", false, "Player invulnerability toggle");
+}
diff --git a/Assets/Game/GameCVars.cs.meta b/Assets/Game/GameCVars.cs.meta
new file mode 100644
index 0000000..0d622cc
--- /dev/null
+++ b/Assets/Game/GameCVars.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: fec9699f5a6105148a4740bcfaffc027
\ No newline at end of file
diff --git a/DEVELOPMENT_GUIDELINES.md b/DEVELOPMENT_GUIDELINES.md
new file mode 100644
index 0000000..b66c3c8
--- /dev/null
+++ b/DEVELOPMENT_GUIDELINES.md
@@ -0,0 +1,56 @@
+# Development Guidelines
+
+This document contains guidelines extracted from analyzing preferred implementations to ensure future development aligns with the project's design philosophy.
+
+## Core Principles
+
+### 1. Prioritize Simplicity Over Abstraction
+- **Prefer concrete classes over generic abstractions**
+- **Avoid complex manager patterns when simple registration works**
+- **Keep related functionality in single files when possible**
+- **Write less code when possible**
+
+### 2. Performance is Paramount
+- **Direct field access over method calls for hot paths**
+- **Minimize allocation and indirection**
+- **Avoid implicit operators or complex conversion layers**
+- **Prefer explicit over implicit behavior**
+
+### 3. Follow Unity's Design Patterns
+- **Use direct field access like Unity components do**
+- **Keep registration simple and automatic**
+- **Prefer concrete implementations over abstract interfaces**
+- **Use clear, descriptive names**
+
+### 4. Console Integration Should Feel Natural
+- **Use intuitive command syntax (`name value` vs `name = value`)**
+- **Query by name for getting values**
+- **Keep parsing logic simple and direct**
+- **Minimize complex assignment detection**
+
+### 5. Code Style Guidelines
+- **Write less code when possible**
+- **Prefer concrete implementations over abstract interfaces**
+- **Use clear, descriptive names**
+- **Avoid over-engineering solutions**
+
+### 6. File Organization
+- **Keep related functionality together**
+- **Prefer single files over multiple small files**
+- **Minimize cross-file dependencies**
+
+## Key Takeaways
+
+1. **Simplicity wins**: The preferred implementation is much shorter and easier to understand
+2. **Performance matters**: Direct field access (`cvar.value`) is faster than method calls
+3. **Unity-like patterns**: Follow Unity's style of direct, explicit access
+4. **Natural integration**: Console commands should feel intuitive and simple
+5. **Less is more**: Avoid unnecessary abstraction layers and complex type systems
+
+## When to Apply These Guidelines
+
+- **New feature development**: Always consider the simpler approach first
+- **Performance-critical code**: Prioritize direct access over abstraction
+- **Console integration**: Keep commands natural and intuitive
+- **Code reviews**: Question complex abstractions and prefer simpler solutions
+- **Refactoring**: Look for opportunities to simplify existing code
\ No newline at end of file