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?
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
# 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)
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()
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
# 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)
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()
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)
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
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 —")
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)
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()