# OSC control bug

**URL:** <https://ask.vrchat.com/t/osc-control-bug/46956>\
**Category:** Help!\
**Created:** [October 17, 2025, 3:04pm UTC](https://ask.vrchat.com/t/osc-control-bug/46956 "2025-10-17T15:04:08Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![cam8528](https://yyz1.discourse-cdn.com/vrchat/user_avatar/ask.vrchat.com/cam8528/32/24547_2.png) [@cam8528](https://ask.vrchat.com/u/cam8528)\
**Post date:** [October 17, 2025, 3:04pm UTC](https://ask.vrchat.com/t/osc-control-bug/46956/1 "2025-10-17T15:04:08Z")

</div>

hi i have a script that copilot and gemini generated for me. it is supposed to make osc port control across three different vr chat instances. i want the first instance to “be the leader” and i want the second instance to just copy the leader’s input. and i want the third instance to also, “follow the leader”. so far, the jumping seems to work well in the script. with the leader window in focus, pressing the jump button on the controller causes the other two instances to also jump, through OSC control. however this is where the bug occurs, with movement and camera rotation. pressing the left joystick causes the “leader” instance to move around uncontrollably. its like there is random joystick movement across all three OSC instances. you cant control movement. its just aimless and constant. the same thing happens with right joystick camera control, it just gets stuck in a rotation loop where you cant control camera rotation. camera rotation goes uncontrollably. what is wrong with my script and how do i fix it? i have several versiions of the scriipt and copilot and gemeni have tried several variations of the script. following are a few variations of the script. if i recall correctly, one of the scripts has no movement working at all, and another variant has uncontrollable camera rotation and uncontrollable movement input. how do i properly implement an OSC script to have three instances of vrchat follow each other around?

---

<div class="post-metadata">

**Author:** ![cam8528](https://yyz1.discourse-cdn.com/vrchat/user_avatar/ask.vrchat.com/cam8528/32/24547_2.png) [@cam8528](https://ask.vrchat.com/u/cam8528)\
**Post date:** [October 17, 2025, 3:04pm UTC](https://ask.vrchat.com/t/osc-control-bug/46956/2 "2025-10-17T15:04:54Z")

</div>

import tkinter as tk  
from threading import Thread  
from inputs import get\_gamepad  
from pythonosc.udp\_client import SimpleUDPClient

# OSC targets

osc\_targets = [  
SimpleUDPClient(“127.0.0.1”, 9000),  
SimpleUDPClient(“127.0.0.1”, 9002),  
SimpleUDPClient(“127.0.0.1”, 9004)  
]

# Configurable settings

DEAD\_ZONE = 50 # Increased dead zone for smoother control  
INVERT\_LOOK\_Y = True  
INVERT\_LOOK\_X = False  
INVERT\_MOVE\_Y = True

# Button state tracking

button\_states = {  
“BTN\_SOUTH”: 0, # A (Jump)  
“BTN\_EAST”: 0, # B (Crouch)  
“ABS\_Y”: 128, # Left stick vertical  
“ABS\_X”: 128, # Left stick horizontal  
“ABS\_RY”: 128, # Right stick vertical  
“ABS\_RX”: 128 # Right stick horizontal  
}

def send\_osc\_to\_all(address, value):  
for client in osc\_targets:  
client.send\_message(address, value)

def controller\_loop(log\_callback):  
while True:  
events = get\_gamepad()  
for event in events:  
code = event.code  
state = event.state

```auto
        # Jump (A button)
        if code == "BTN_SOUTH":
            if state != button_states[code]:
                send_osc_to_all("/input/Jump", state)
                log_callback(f"Jump: {state}")
                button_states[code] = state

        # Crouch (B button)
        elif code == "BTN_EAST":
            if state != button_states[code]:
                send_osc_to_all("/input/Crouch", state)
                log_callback(f"Crouch: {state}")
                button_states[code] = state

        # Movement - Forward/Backward (Y-axis)
        elif code == "ABS_Y":
            if INVERT_MOVE_Y:
                forward = 1 if state > (128 + DEAD_ZONE) else 0
                backward = 1 if state < (128 - DEAD_ZONE) else 0
            else:
                forward = 1 if state < (128 - DEAD_ZONE) else 0
                backward = 1 if state > (128 + DEAD_ZONE) else 0

            send_osc_to_all("/input/MoveForward", forward)
            send_osc_to_all("/input/MoveBackward", backward)
            log_callback(f"Forward: {forward}, Backward: {backward}")
            button_states[code] = state

        # Movement - Left/Right
        elif code == "ABS_X":
            left = 1 if state < (128 - DEAD_ZONE) else 0
            right = 1 if state > (128 + DEAD_ZONE) else 0
            send_osc_to_all("/input/MoveLeft", left)
            send_osc_to_all("/input/MoveRight", right)
            log_callback(f"Left: {left}, Right: {right}")
            button_states[code] = state

        # Look - Up/Down
        elif code == "ABS_RY":
            if INVERT_LOOK_Y:
                look_up = 1 if state > (128 + DEAD_ZONE) else 0
                look_down = 1 if state < (128 - DEAD_ZONE) else 0
            else:
                look_up = 1 if state < (128 - DEAD_ZONE) else 0
                look_down = 1 if state > (128 + DEAD_ZONE) else 0

            send_osc_to_all("/input/LookUp", look_up)
            send_osc_to_all("/input/LookDown", look_down)
            log_callback(f"LookUp: {look_up}, LookDown: {look_down}")
            button_states[code] = state

        # Look - Left/Right
        elif code == "ABS_RX":
            if INVERT_LOOK_X:
                look_left = 1 if state > (128 + DEAD_ZONE) else 0
                look_right = 1 if state < (128 - DEAD_ZONE) else 0
            else:
                look_left = 1 if state < (128 - DEAD_ZONE) else 0
                look_right = 1 if state > (128 + DEAD_ZONE) else 0

            send_osc_to_all("/input/LookLeft", look_left)
            send_osc_to_all("/input/LookRight", look_right)
            log_callback(f"LookLeft: {look_left}, LookRight: {look_right}")
            button_states[code] = state

```

# GUI setup

def start\_gui():  
root = tk.Tk()  
root.title(“VRChat Controller Router”)  
root.geometry(“360x220”)  
root.resizable(False, False)

```auto
log_box = tk.Text(root, height=12, width=45)
log_box.pack(pady=10)

def log(msg):
    log_box.insert(tk.END, msg + "\n")
    log_box.see(tk.END)

Thread(target=controller_loop, args=(log,), daemon=True).start()
root.mainloop()

```

# Run GUI

start\_gui()

---

<div class="post-metadata">

**Author:** ![cam8528](https://yyz1.discourse-cdn.com/vrchat/user_avatar/ask.vrchat.com/cam8528/32/24547_2.png) [@cam8528](https://ask.vrchat.com/u/cam8528)\
**Post date:** [October 17, 2025, 3:05pm UTC](https://ask.vrchat.com/t/osc-control-bug/46956/3 "2025-10-17T15:05:32Z")

</div>

import tkinter as tk  
from threading import Thread  
from inputs import get\_gamepad  
from pythonosc.udp\_client import SimpleUDPClient

# OSC targets

osc\_targets = [  
SimpleUDPClient(“127.0.0.1”, 9000),  
SimpleUDPClient(“127.0.0.1”, 9002),  
SimpleUDPClient(“127.0.0.1”, 9004)  
]

# Configurable settings

DEAD\_ZONE = 50  
INVERT\_LOOK\_Y = True  
INVERT\_LOOK\_X = False  
INVERT\_MOVE\_Y = True

# Button state tracking

button\_states = {  
“BTN\_SOUTH”: 0,  
“BTN\_EAST”: 0,  
“ABS\_Y”: 128,  
“ABS\_X”: 128,  
“ABS\_RY”: 128,  
“ABS\_RX”: 128  
}

def send\_osc\_to\_all(address, value):  
for client in osc\_targets:  
client.send\_message(address, value)

def controller\_loop(log\_callback):  
while True:  
events = get\_gamepad()  
for event in events:  
code = event.code  
state = event.state

```auto
        # Jump
        if code == "BTN_SOUTH":
            if state != button_states[code]:
                send_osc_to_all("/input/Jump", state)
                log_callback(f"Jump: {state}")
                button_states[code] = state

        # Crouch
        elif code == "BTN_EAST":
            if state != button_states[code]:
                send_osc_to_all("/input/Crouch", state)
                log_callback(f"Crouch: {state}")
                button_states[code] = state

        # Movement - Forward/Backward
        elif code == "ABS_Y":
            forward = backward = 0
            if INVERT_MOVE_Y:
                if state > (128 + DEAD_ZONE):
                    forward = 1
                elif state < (128 - DEAD_ZONE):
                    backward = 1
            else:
                if state < (128 - DEAD_ZONE):
                    forward = 1
                elif state > (128 + DEAD_ZONE):
                    backward = 1

            send_osc_to_all("/input/MoveForward", forward)
            send_osc_to_all("/input/MoveBackward", backward)
            log_callback(f"Forward: {forward}, Backward: {backward}")
            button_states[code] = state

        # Movement - Left/Right
        elif code == "ABS_X":
            left = right = 0
            if state < (128 - DEAD_ZONE):
                left = 1
            elif state > (128 + DEAD_ZONE):
                right = 1

            send_osc_to_all("/input/MoveLeft", left)
            send_osc_to_all("/input/MoveRight", right)
            log_callback(f"Left: {left}, Right: {right}")
            button_states[code] = state

        # Look - Up/Down
        elif code == "ABS_RY":
            look_up = look_down = 0
            if INVERT_LOOK_Y:
                if state > (128 + DEAD_ZONE):
                    look_up = 1
                elif state < (128 - DEAD_ZONE):
                    look_down = 1
            else:
                if state < (128 - DEAD_ZONE):
                    look_up = 1
                elif state > (128 + DEAD_ZONE):
                    look_down = 1

            send_osc_to_all("/input/LookUp", look_up)
            send_osc_to_all("/input/LookDown", look_down)
            log_callback(f"LookUp: {look_up}, LookDown: {look_down}")
            button_states[code] = state

        # Look - Left/Right
        elif code == "ABS_RX":
            look_left = look_right = 0
            if INVERT_LOOK_X:
                if state > (128 + DEAD_ZONE):
                    look_left = 1
                elif state < (128 - DEAD_ZONE):
                    look_right = 1
            else:
                if state < (128 - DEAD_ZONE):
                    look_left = 1
                elif state > (128 + DEAD_ZONE):
                    look_right = 1

            send_osc_to_all("/input/LookLeft", look_left)
            send_osc_to_all("/input/LookRight", look_right)
            log_callback(f"LookLeft: {look_left}, LookRight: {look_right}")
            button_states[code] = state

```

# GUI setup

def start\_gui():  
root = tk.Tk()  
root.title(“VRChat Controller Router”)  
root.geometry(“360x220”)  
root.resizable(False, False)

```auto
log_box = tk.Text(root, height=12, width=45)
log_box.pack(pady=10)

def log(msg):
    log_box.insert(tk.END, msg + "\n")
    log_box.see(tk.END)

Thread(target=controller_loop, args=(log,), daemon=True).start()
root.mainloop()

```

# Run GUI

start\_gui()

---

<div class="post-metadata">

**Author:** ![cam8528](https://yyz1.discourse-cdn.com/vrchat/user_avatar/ask.vrchat.com/cam8528/32/24547_2.png) [@cam8528](https://ask.vrchat.com/u/cam8528)\
**Post date:** [October 17, 2025, 3:12pm UTC](https://ask.vrchat.com/t/osc-control-bug/46956/4 "2025-10-17T15:12:33Z")

</div>

import tkinter as tk  
from threading import Thread  
from inputs import get\_gamepad  
from pythonosc.udp\_client import SimpleUDPClient

# OSC targets

osc\_targets = [  
SimpleUDPClient(“127.0.0.1”, 9000),  
SimpleUDPClient(“127.0.0.1”, 9002),  
SimpleUDPClient(“127.0.0.1”, 9004)  
]

# — Configurable Settings —

# DEAD\_ZONE changed to 108, which is approx 85% of the 0-127.5 half-axis range.

DEAD\_ZONE = 108

# Set to True if movement/look is reversed in VRChat

INVERT\_LOOK\_Y = True  
INVERT\_LOOK\_X = False  
INVERT\_MOVE\_Y = True

# — VRChat Axis Mapping Configuration —

# Set to True to use alternative VRChat addresses (/input/Vertical, etc.)

USE\_ALTERNATIVE\_AXES = True

if USE\_ALTERNATIVE\_AXES:  
AXIS\_MAPPING = {  
“ABS\_Y”: “/input/Vertical”, # Left Stick Vertical (Fwd/Bwd)  
“ABS\_X”: “/input/Horizontal”, # Left Stick Horizontal (Lft/Rgt)  
“ABS\_RY”: “/input/LookVertical”, # Right Stick Vertical (Up/Down)  
“ABS\_RX”: “/input/LookHorizontal”, # Right Stick Horizontal (Lft/Rgt)  
}  
else:  
AXIS\_MAPPING = {  
“ABS\_Y”: “/input/AxisY”, # Left Stick Vertical (Fwd/Bwd)  
“ABS\_X”: “/input/AxisX”, # Left Stick Horizontal (Lft/Rgt)  
“ABS\_RY”: “/input/VerticalLookAxis”,# Right Stick Vertical (Up/Down)  
“ABS\_RX”: “/input/LookAxis”, # Right Stick Horizontal (Lft/Rgt)  
}

# Button/Axis state tracking

button\_states = {  
“BTN\_SOUTH”: 0, “BTN\_EAST”: 0,  
“ABS\_Y”: 128, “ABS\_X”: 128,  
“ABS\_RY”: 128, “ABS\_RX”: 128,  
“ABS\_Z”: 0, “ABS\_RZ”: 0  
}

def send\_osc\_to\_all(address, value):  
if isinstance(value, (int, float)):  
value = float(value)

```auto
for client in osc_targets:
    try:
        client.send_message(address, value)
    except Exception as e:
        print(f"Error sending OSC message to client: {e}") 

```

def scale\_axis\_input(raw\_state, dead\_zone):  
“”"  
Scales the raw 0-255 axis state (Sticks) to a float from -1.0 to 1.0,  
applying the dead zone.  
“”"  
center = 127.5  
normalized\_state = raw\_state - center

```auto
if abs(normalized_state) < dead_zone:
    return 0.0 # Within dead zone

direction = 1.0 if normalized_state > 0 else -1.0
raw_magnitude = abs(normalized_state) - dead_zone
max_scale_range = 127.5 - dead_zone 

if max_scale_range <= 0:
    return 0.0

scaled_magnitude = min(1.0, raw_magnitude / max_scale_range)

return scaled_magnitude * direction

```

def scale\_trigger\_input(raw\_state, max\_val=255):  
“”"  
Scales the raw 0-Max trigger state to a float from 0.0 to 1.0.  
“”"  
return min(1.0, float(raw\_state) / float(max\_val))

def controller\_loop(log\_callback):  
mode = “ALTERNATIVE” if USE\_ALTERNATIVE\_AXES else “PRIMARY”  
log\_callback(f"— USING {mode} VRChat AXIS INPUTS —")

```auto
while True:
    events = get_gamepad()
    for event in events:
        code = event.code
        state = event.state

        # --- Digital Buttons ---
        if code == "BTN_SOUTH":
            if state != button_states[code]:
                send_osc_to_all("/input/Jump", state)
                log_callback(f"Jump: {state}")
                button_states[code] = state

        elif code == "BTN_EAST":
            if state != button_states[code]:
                send_osc_to_all("/input/Crouch", state)
                log_callback(f"Crouch: {state}")
                button_states[code] = state

        # --- Analog Axes & Triggers ---
        
        # Filter noise on analog inputs
        if code in button_states and code.startswith("ABS"):
            noise_threshold = 3 if code.endswith(('X', 'Y', 'RY', 'RX')) else 5 
            if abs(state - button_states[code]) < noise_threshold:
                continue
            button_states[code] = state
        
        # --- STICKS (Move & Look) ---
        
        # Movement - Forward/Backward (ABS_Y - Left Stick Vertical)
        if code == "ABS_Y":
            move_value = scale_axis_input(state, DEAD_ZONE)
            if INVERT_MOVE_Y:
                move_value = -move_value
            
            address = AXIS_MAPPING[code]
            send_osc_to_all(address, move_value)
            # The log is crucial: check if it shows smooth floats (e.g., 0.10, 0.25, 0.50)
            log_callback(f"{address}: {move_value:.2f}")

        # Movement - Left/Right (ABS_X - Left Stick Horizontal)
        elif code == "ABS_X":
            move_value = scale_axis_input(state, DEAD_ZONE)
            
            address = AXIS_MAPPING[code]
            send_osc_to_all(address, move_value)
            log_callback(f"{address}: {move_value:.2f}")

        # Look - Up/Down (ABS_RY - Right Stick Vertical)
        elif code == "ABS_RY":
            look_value = scale_axis_input(state, DEAD_ZONE)
            if INVERT_LOOK_Y:
                look_value = -look_value
            
            address = AXIS_MAPPING[code]
            send_osc_to_all(address, look_value)
            log_callback(f"{address}: {look_value:.2f}")

        # Look - Left/Right (ABS_RX - Right Stick Horizontal)
        elif code == "ABS_RX":
            look_value = scale_axis_input(state, DEAD_ZONE)
            if INVERT_LOOK_X:
                look_value = -look_value
            
            address = AXIS_MAPPING[code]
            send_osc_to_all(address, look_value)
            log_callback(f"{address}: {look_value:.2f}")

        # --- TRIGGERS ---
        
        # Left Trigger (ABS_Z) -> VRChat Run/Sprint
        elif code == "ABS_Z":
            run_value = scale_trigger_input(state, max_val=255) 
            send_osc_to_all("/input/Run", run_value)
            log_callback(f"/input/Run (Left Trigger): {run_value:.2f}")
            
        # Right Trigger (ABS_RZ) -> VRChat Use
        elif code == "ABS_RZ":
            use_value = scale_trigger_input(state, max_val=255) 
            send_osc_to_all("/input/Use", use_value) 
            log_callback(f"/input/Use (Right Trigger): {use_value:.2f}")

```

# GUI setup

def start\_gui():  
root = tk.Tk()  
root.title(“VRChat Controller Router”)  
root.geometry(“400x300”)  
root.resizable(False, False)

```auto
log_frame = tk.Frame(root)
log_frame.pack(pady=10, padx=10, fill="both", expand=True)

scrollbar = tk.Scrollbar(log_frame)
log_box = tk.Text(log_frame, height=15, width=50, yscrollcommand=scrollbar.set)
scrollbar.config(command=log_box.yview)

scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
log_box.pack(side=tk.LEFT, fill="both", expand=True)

def log(msg):
    log_box.insert(tk.END, msg + "\n")
    log_box.see(tk.END)

log(f"Starting controller loop...")
log(f"Targets: 9000, 9002, 9004")
log(f"Dead Zone: {DEAD_ZONE}")

# Log the Axis Addresses being used
mode = "ALTERNATIVE" if USE_ALTERNATIVE_AXES else "PRIMARY"
log(f"--- USING {mode} VRChat AXIS INPUTS ---")

Thread(target=controller_loop, args=(log,), daemon=True).start()

def on_closing():
    root.quit()
    
root.protocol("WM_DELETE_WINDOW", on_closing)

root.mainloop()

```

# Run GUI

start\_gui()
