"Input.Keyboard.Pressed refires on OS key auto-repeat: toggles flip twice"
▸ SYMPTOM
You bind a toggle to a key with Input.Keyboard.Pressed (open a panel, enable freecam, flip a mode) and it misbehaves on a held press: the panel opens and then closes itself about half a second later. Users report the key "isn't working," so they press longer, which makes it worse.
▸ CAUSE
Input.Keyboard.Pressed fires again on the OS key auto-repeat, not only on the physical down edge. Windows delivers its first repeat at the configured repeat delay (~500 ms by default), so:
if (Input.Keyboard.Pressed("J")) open = !open; // toggles TWICE on a held keytoggles once on the real down edge and again on the first auto-repeat: the panel opens, then closes ~0.5 s later. Any one-shot action bound to Pressed re-fires the same way.
▸ FIX
Toggle on the true down edge, tracked with previous-frame state:
bool down = Input.Keyboard.Down(key);
if (down && !_wasDown) Toggle(); // fires once, on the real press
_wasDown = down;Down stays true across the whole hold (including between repeats), so a repeat can never re-toggle, at any hold length.
- Don't reach for a time-based debounce. A "ignore repeats within N ms" window fails on long holds: a later repeat outlives the window and flips state anyway. The previous-frame edge has no such hole.
Pressedis fine where re-fire is harmless or wanted: stepping menu rows, paging a list, repeat-fire actions.- Audit one-shot raw-letter actions that spend money or mutate state. On a held key they re-fire too; a purchase or a contract mutation bound to
Pressedcan trigger twice.
Diagnosing a flapping toggle
If a panel or mode flaps and you have no console bridge to inspect it, look at the engine log for paired on/off lines from any consumer that runs on a ~0.5 s heartbeat (a freecam inspect request, a visibility toggle). On/off pairs cycling ~1 s apart with ~0.5 s open windows are the auto-repeat signature.
▸ WHY IT WORKS
Pressed reflects the input event stream, which includes OS repeats: it answers "did a press event arrive this frame," not "did the physical key just go down." Down reflects the physical hold state, which is level, not edged. Deriving your own edge from Down (down && !wasDown) gives you exactly one trigger per physical press regardless of how the OS chooses to repeat the event underneath.
- Published