05-06-26

SOCD Cleaner Lua Script

This Lua script for Rebind hardware input devices implements SOCD (Simultaneous Opposing Cardinal Directions) cleaning. It resolves conflicting key presses, offering "Last Input" and "Neutral" modes f

rebindlua scriptsocdinputkeyboardscriptconflict resolution

Question

SOCD cleaner for any keyboard

Rebind AI

luau
-- rebind: min_sdk=0.1.0 tick_rate=8000
-- rebind: name=SOCD Cleaner

local cfg = UI.Schema({
    enabled = UI.Toggle(true, { label = "Enable SOCD" }),
    mode = UI.Select("Last Input", { "Last Input", "Neutral" }, { label = "Conflict Mode" }),
    delay = UI.Slider(20, { min = 0, max = 100, label = "Overlap Delay", suffix = "ms", group = "Advanced" }),
})

-- Define opposing key pairs
local PAIRS = {
    { "A", "D" },
    { "W", "S" },
    { "Left", "Right" },
    { "Up", "Down" },
}

local held = {}
local virt = {}
local lastInput = {}

local function setVirt(key, state)
    if virt[key] == state then return end
    if state then
        HID.Down(key)
    else
        HID.Up(key)
    end
    virt[key] = state
end

local function resolveConflict(key1, key2)
    local h1 = held[key1] or false
    local h2 = held[key2] or false

    if cfg.mode == "Neutral" then
        -- Both pressed = release both
        if h1 and h2 then
            setVirt(key1, false)
            setVirt(key2, false)
            return
        end
    else
        -- Last Input mode: the most recently pressed key wins
        if h1 and h2 then
            local last = lastInput[key1] or lastInput[key2] or key1
            setVirt(key1, last == key1)
            setVirt(key2, last == key2)

            -- If delay is set, implement humanised overlap
            if cfg.delay > 0 and last == key1 then
                Timer.After(cfg.delay, function()
                    if held[key1] and held[key2] then
                        setVirt(key2, false)
                    end
                end)
            elseif cfg.delay > 0 and last == key2 then
                Timer.After(cfg.delay, function()
                    if held[key1] and held[key2] then
                        setVirt(key1, false)
                    end
                end)
            end
            return
        end
    end

    -- One or neither pressed: pass through
    setVirt(key1, h1)
    setVirt(key2, h2)
end

function OnDown(key)
    if not cfg.enabled then return true end

    -- Check if this key is part of any pair
    for _, pair in ipairs(PAIRS) do
        if key == pair[1] or key == pair[2] then
            held[key] = true
            lastInput[pair[1]] = System.Time()
            lastInput[pair[2]] = System.Time()
            resolveConflict(pair[1], pair[2])
            return false -- block physical input, we control output
        end
    end

    return true
end

function OnUp(key)
    if not cfg.enabled then return true end

    for _, pair in ipairs(PAIRS) do
        if key == pair[1] or key == pair[2] then
            held[key] = false
            resolveConflict(pair[1], pair[2])
            return false
        end
    end

    return true
end

function OnBlur()
    -- Release all virtual keys on blur
    for key, _ in pairs(virt) do
        setVirt(key, false)
    end
    held = {}
    virt = {}
    lastInput = {}
end

function OnStop()
    OnBlur()
end

How it works:

This script detects when two opposing keys are pressed simultaneously and handles the conflict based on your configured mode:

  • Last Input mode (default): The most recently pressed key "wins" and the other is silenced. Includes an optional Overlap Delay that humanizes transitions — if you press A+D with a 20ms delay, D will release 20ms after the switch, simulating real keyboard behavior. This is SOCD like fightstick controllers use.

  • Neutral mode: When both keys of a pair are held, both are released (neutral stick position). Useful for some game engines that don't handle conflicting inputs well.

Supported pairs:

  • A/D (strafe)
  • W/S (forward/back)
  • Left/Right (arrows)
  • Up/Down (arrows)

You can easily add more pairs to the PAIRS table if you want to handle other key combinations. The script blocks the original input and outputs the resolved state, so the game only sees valid, non-conflicting input.

Setup: Just load this script globally (no process targeting). It runs at 8000 Hz for sub-millisecond precision on conflict detection.