"A console or editor text field with keyboard focus starves every Input read in game code"
▸ SYMPTOM
- A feature that reads input — a debug fly camera, a movement handler, any
Input.*consumer — is reported as intermittently dead: "it just doesn't move." - The rig looks perfect. It owns
IsMainCamera, its enabled flag is on, and its own state line reports healthy in every field. It has simply never been fed a single input. - The same feature works when toggled from its keybind and fails when toggled from the console — nothing differs between the two paths except where the caret is.
- An MCP / headless play session drives the build through the console, so it is permanently in this state and can never prove a device-input path works.
▸ CAUSE
A console or editor text field that holds keyboard focus starves every Input.* read in game code. Typing a command into the console is exactly what moves keyboard focus off the game viewport — so every subsequent WASD (or any key) lands in the text box, not in your game's input reads.
That is why the reports are intermittent. Toggle the feature from its keybind and focus is already on the game, so it works. Toggle it by typing its ConCmd into the console and the act of typing has moved the caret off the viewport, so the freshly-enabled feature reads nothing. Any feature whose ConCmd and its keybind do the same thing inherits this split, and console-driven verification is the normal way agents and test batteries drive a build — so a headless play session is stuck in the starved state by construction.
A debug camera in this state is pixel-identical to a genuinely broken one: same still frame, same healthy state line, zero motion.
▸ FIX
You cannot take focus back from game code. The console is the tools shell's own widget and InputRouter.KeyboardFocusPanel is internal to Sandbox.Engine — there is no public API that moves the caret to the viewport.
So fix the silence, not the focus. What game code can read to detect the state:
Application.IsFocused— the game window.falseis conclusive (you are definitely not getting input);trueis not conclusive, because a panel inside the same window can hold the caret.Input.Suppressed— documented as "everything will act like there is no input."Input.MouseCursorVisible/Mouse.Active/Mouse.Visibility— the cursor half. (A visible cursor separately hard-zeroesAnalogLook.)
Make the feature notice it is live and has read nothing for a few seconds, and say so once, with the focus reads attached:
protected override void OnUpdate()
{
var moved = ReadMovement(); // your Input.* reads
if ( moved.IsNearZeroLength )
{
_idleSeconds += Time.Delta;
if ( _idleSeconds > 3f && !_warned )
{
Log.Info( $"[flycam] live but unfed for {_idleSeconds:0.0}s — " +
$"IsFocused={Application.IsFocused} Suppressed={Input.Suppressed}. " +
$"Caret probably in the console — toggle from the keybind." );
_warned = true;
}
}
else { _idleSeconds = 0; _warned = false; }
}Put "seconds since last input" and "has this session ever been fed" on the feature's own state command, so the starved state is visible instead of being indistinguishable from a crash.
▸ WHY IT WORKS
The starvation is legitimate engine behavior — the console owns the caret, and while it does, your reads correctly return nothing. Being indistinguishable from a broken feature is the actual defect, and that is what the diagnostic removes. Application.IsFocused and Input.Suppressed are readable from game code even though the focus itself is not writable, so the feature can report exactly why it is idle without ever being able to fix it — which is the honest and correct thing for it to do.
- Published.