| import os |
| import sys |
| import time |
| import json |
| import argparse |
| from pathlib import Path |
| from collections import OrderedDict |
|
|
| import cv2 |
| import numpy as np |
| from PIL import Image |
|
|
| from nitrogen.shared import BUTTON_ACTION_TOKENS, PATH_REPO |
|
|
| |
| |
| try: |
| from nitrogen.inference_viz import create_viz, VideoRecorder |
| _VIZ_IMPORT_ERROR = None |
| except Exception as exc: |
| create_viz = None |
| VideoRecorder = None |
| _VIZ_IMPORT_ERROR = exc |
|
|
|
|
| class _NoOpGamepad: |
| def update(self): |
| pass |
|
|
|
|
| class _NoOpGamepadEmulator: |
| def __init__(self): |
| self.gamepad = _NoOpGamepad() |
|
|
| def press_button(self, button): |
| pass |
|
|
| def release_button(self, button): |
| pass |
|
|
|
|
| class MockGamepadEnv: |
| """Linux/debug replacement for GamepadEnv. |
| |
| It has the same small API surface used by this script: |
| reset/pause/unpause/step/close and gamepad_emulator. |
| step() returns a random RGB PIL image as obs. |
| """ |
|
|
| def __init__( |
| self, |
| game, |
| game_speed=1.0, |
| env_fps=60, |
| async_mode=True, |
| width=1280, |
| height=720, |
| seed=0, |
| ): |
| self.game = game |
| self.game_speed = game_speed |
| self.env_fps = env_fps |
| self.async_mode = async_mode |
| self.width = width |
| self.height = height |
| self.rng = np.random.default_rng(seed) |
| self.step_count = 0 |
| self.gamepad_emulator = _NoOpGamepadEmulator() |
|
|
| def _random_obs(self): |
| arr = self.rng.integers( |
| 0, |
| 256, |
| size=(self.height, self.width, 3), |
| dtype=np.uint8, |
| ) |
| return Image.fromarray(arr, mode="RGB") |
|
|
| def reset(self): |
| self.step_count = 0 |
| return self._random_obs() |
|
|
| def pause(self): |
| pass |
|
|
| def unpause(self): |
| pass |
|
|
| def close(self): |
| pass |
|
|
| def step(self, action): |
| self.step_count += 1 |
| obs = self._random_obs() |
| reward = 0.0 |
| terminated = False |
| truncated = False |
| info = { |
| "mock_env": True, |
| "step_count": self.step_count, |
| "game": self.game, |
| } |
| return obs, reward, terminated, truncated, info |
|
|
|
|
| class VisualDebugGamepadEnv: |
| """Linux/debug environment with meaningful RGB observations. |
| |
| This env does not need a display server. It renders a simple 2D scene with OpenCV: |
| - a controllable player dot |
| - a target dot |
| - recent button states |
| - left/right joystick values |
| Therefore action outputs visibly change the next observation/video. |
| """ |
|
|
| def __init__( |
| self, |
| game, |
| game_speed=1.0, |
| env_fps=60, |
| async_mode=True, |
| width=1280, |
| height=720, |
| seed=0, |
| ): |
| self.game = game |
| self.game_speed = game_speed |
| self.env_fps = env_fps |
| self.async_mode = async_mode |
| self.width = width |
| self.height = height |
| self.rng = np.random.default_rng(seed) |
| self.step_count = 0 |
| self.gamepad_emulator = _NoOpGamepadEmulator() |
| self.player = np.array([width * 0.25, height * 0.5], dtype=np.float32) |
| self.velocity = np.zeros(2, dtype=np.float32) |
| self.target = np.array([width * 0.78, height * 0.5], dtype=np.float32) |
| self.last_action = make_zero_action() |
| self.trail = [] |
|
|
| @staticmethod |
| def _scalar(v): |
| if isinstance(v, np.ndarray): |
| return float(np.asarray(v).reshape(-1)[0]) |
| return float(v) |
|
|
| def reset(self): |
| self.step_count = 0 |
| self.player = np.array([self.width * 0.25, self.height * 0.5], dtype=np.float32) |
| self.velocity[:] = 0 |
| self.target = np.array([ |
| self.rng.uniform(self.width * 0.55, self.width * 0.9), |
| self.rng.uniform(self.height * 0.2, self.height * 0.8), |
| ], dtype=np.float32) |
| self.trail = [] |
| self.last_action = make_zero_action() |
| return self._render() |
|
|
| def pause(self): |
| pass |
|
|
| def unpause(self): |
| pass |
|
|
| def close(self): |
| pass |
|
|
| def step(self, action): |
| self.step_count += 1 |
| self.last_action = action |
|
|
| lx = self._scalar(action.get("AXIS_LEFTX", 0)) / 32767.0 |
| ly = self._scalar(action.get("AXIS_LEFTY", 0)) / 32767.0 |
| rx = self._scalar(action.get("AXIS_RIGHTX", 0)) / 32767.0 |
| rt = self._scalar(action.get("RIGHT_TRIGGER", 0)) / 255.0 |
| lt = self._scalar(action.get("LEFT_TRIGGER", 0)) / 255.0 |
|
|
| |
| if action.get("DPAD_LEFT", 0): |
| lx -= 1.0 |
| if action.get("DPAD_RIGHT", 0): |
| lx += 1.0 |
| if action.get("DPAD_UP", 0): |
| ly -= 1.0 |
| if action.get("DPAD_DOWN", 0): |
| ly += 1.0 |
|
|
| speed = 10.0 + 18.0 * max(rt, 0.0) |
| damping = 0.78 if not action.get("SOUTH", 0) else 0.55 |
| accel = np.array([lx, ly], dtype=np.float32) * speed |
| self.velocity = self.velocity * damping + accel |
| if action.get("EAST", 0): |
| |
| self.velocity += np.array([18.0, -12.0], dtype=np.float32) |
| if action.get("WEST", 0): |
| self.velocity *= 0.35 |
| if action.get("NORTH", 0): |
| |
| self.target = np.array([ |
| self.rng.uniform(self.width * 0.1, self.width * 0.9), |
| self.rng.uniform(self.height * 0.15, self.height * 0.85), |
| ], dtype=np.float32) |
|
|
| self.player += self.velocity |
| margin = 35 |
| self.player[0] = np.clip(self.player[0], margin, self.width - margin) |
| self.player[1] = np.clip(self.player[1], margin, self.height - margin) |
|
|
| self.trail.append(tuple(self.player.astype(int))) |
| self.trail = self.trail[-80:] |
|
|
| dist = float(np.linalg.norm(self.player - self.target)) |
| reward = -dist / max(self.width, self.height) |
| if dist < 45: |
| reward = 1.0 |
| self.target = np.array([ |
| self.rng.uniform(self.width * 0.1, self.width * 0.9), |
| self.rng.uniform(self.height * 0.15, self.height * 0.85), |
| ], dtype=np.float32) |
|
|
| obs = self._render(lx=lx, ly=ly, rx=rx, lt=lt, rt=rt, reward=reward) |
| terminated = False |
| truncated = False |
| info = { |
| "debug_env": "visual", |
| "step_count": self.step_count, |
| "distance_to_target": dist, |
| "game": self.game, |
| } |
| return obs, reward, terminated, truncated, info |
|
|
| def _render(self, lx=0.0, ly=0.0, rx=0.0, lt=0.0, rt=0.0, reward=0.0): |
| img = np.zeros((self.height, self.width, 3), dtype=np.uint8) |
| img[:] = (28, 30, 36) |
|
|
| |
| for x in range(0, self.width, 80): |
| cv2.line(img, (x, 0), (x, self.height), (45, 48, 56), 1) |
| for y in range(0, self.height, 80): |
| cv2.line(img, (0, y), (self.width, y), (45, 48, 56), 1) |
|
|
| |
| cv2.rectangle(img, (self.width // 2 - 80, 130), (self.width // 2 + 80, 210), (80, 80, 95), -1) |
| cv2.rectangle(img, (self.width // 2 - 120, self.height - 230), (self.width // 2 + 120, self.height - 150), (80, 80, 95), -1) |
|
|
| |
| if len(self.trail) >= 2: |
| for i in range(1, len(self.trail)): |
| thickness = 1 + i // 25 |
| cv2.line(img, self.trail[i - 1], self.trail[i], (90, 180, 255), thickness) |
|
|
| |
| target_xy = tuple(self.target.astype(int)) |
| player_xy = tuple(self.player.astype(int)) |
| cv2.circle(img, target_xy, 34, (70, 220, 100), -1) |
| cv2.circle(img, target_xy, 45, (70, 220, 100), 2) |
| cv2.circle(img, player_xy, 28, (70, 130, 255), -1) |
| cv2.circle(img, player_xy, 36, (230, 235, 245), 2) |
|
|
| |
| tip = (int(self.player[0] + lx * 90), int(self.player[1] + ly * 90)) |
| cv2.arrowedLine(img, player_xy, tip, (255, 220, 120), 5, tipLength=0.3) |
|
|
| |
| panel_x, panel_y = 30, 35 |
| line_h = 32 |
| cv2.putText(img, "Linux VisualDebugGamepadEnv", (panel_x, panel_y), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (245, 245, 245), 2) |
| cv2.putText(img, f"step={self.step_count} reward={reward:.3f}", (panel_x, panel_y + line_h), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (230, 230, 230), 2) |
| cv2.putText(img, f"left=({lx:+.2f},{ly:+.2f}) right_x={rx:+.2f} LT={lt:.2f} RT={rt:.2f}", (panel_x, panel_y + 2 * line_h), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (230, 230, 230), 2) |
|
|
| pressed = [k for k, v in self.last_action.items() if not isinstance(v, np.ndarray) and bool(v)] |
| pressed_text = "pressed: " + (", ".join(pressed[:10]) if pressed else "none") |
| cv2.putText(img, pressed_text, (panel_x, panel_y + 3 * line_h), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (230, 230, 230), 2) |
|
|
| |
| button_names = ["WEST", "SOUTH", "EAST", "NORTH", "START", "BACK"] |
| bx = panel_x |
| by = panel_y + 5 * line_h |
| for idx, name in enumerate(button_names): |
| on = bool(self.last_action.get(name, 0)) |
| color = (80, 220, 120) if on else (75, 75, 85) |
| x0 = bx + idx * 125 |
| cv2.rectangle(img, (x0, by), (x0 + 105, by + 42), color, -1) |
| cv2.putText(img, name, (x0 + 8, by + 28), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (10, 10, 10) if on else (210, 210, 210), 2) |
|
|
| return Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) |
|
|
|
|
| class GymRGBGamepadEnv: |
| """Optional Gymnasium RGB-array environment for Linux. |
| |
| Example: |
| pip install "gymnasium[classic-control]" pygame |
| python play_linux_debug_visual.py --debug-env gym --gym-id CartPole-v1 --mock-policy --max-steps 10 |
| """ |
|
|
| def __init__(self, game, game_speed=1.0, env_fps=60, async_mode=True, gym_id="CartPole-v1", seed=0, **kwargs): |
| os.environ.setdefault("SDL_VIDEODRIVER", "dummy") |
| import gymnasium as gym |
|
|
| self.game = game |
| self.env_fps = env_fps |
| self.gym_id = gym_id |
| self.seed = seed |
| self.step_count = 0 |
| self.gamepad_emulator = _NoOpGamepadEmulator() |
| self.env = gym.make(gym_id, render_mode="rgb_array") |
| self.discrete_n = getattr(self.env.action_space, "n", None) |
|
|
| @staticmethod |
| def _scalar(v): |
| if isinstance(v, np.ndarray): |
| return float(np.asarray(v).reshape(-1)[0]) |
| return float(v) |
|
|
| def _render(self): |
| arr = self.env.render() |
| return Image.fromarray(np.asarray(arr, dtype=np.uint8), mode="RGB") |
|
|
| def reset(self): |
| self.step_count = 0 |
| self.env.reset(seed=self.seed) |
| return self._render() |
|
|
| def pause(self): |
| pass |
|
|
| def unpause(self): |
| pass |
|
|
| def close(self): |
| self.env.close() |
|
|
| def _map_gamepad_to_gym_action(self, action): |
| lx = self._scalar(action.get("AXIS_LEFTX", 0)) / 32767.0 |
| if self.discrete_n is not None: |
| if self.discrete_n == 2: |
| return 1 if lx > 0 else 0 |
| pressed = [name for name in BUTTON_ACTION_TOKENS if action.get(name, 0) and name in action] |
| if pressed: |
| return min(BUTTON_ACTION_TOKENS.index(pressed[0]), self.discrete_n - 1) |
| return int(np.clip(round((lx + 1.0) * 0.5 * (self.discrete_n - 1)), 0, self.discrete_n - 1)) |
| |
| shape = self.env.action_space.shape |
| low, high = self.env.action_space.low, self.env.action_space.high |
| raw = np.zeros(shape, dtype=np.float32) |
| raw.flat[0] = lx |
| return np.clip(raw, low, high) |
|
|
| def step(self, action): |
| self.step_count += 1 |
| gym_action = self._map_gamepad_to_gym_action(action) |
| _, reward, terminated, truncated, info = self.env.step(gym_action) |
| if terminated or truncated: |
| self.env.reset() |
| info = dict(info) |
| info.update({"debug_env": "gym", "gym_id": self.gym_id, "gym_action": gym_action, "step_count": self.step_count}) |
| return self._render(), float(reward), bool(terminated), bool(truncated), info |
|
|
|
|
| class MockModelClient: |
| """Optional model-server replacement for quickly testing script plumbing.""" |
|
|
| def __init__(self, action_len=4, seed=0): |
| self.action_len = action_len |
| self.rng = np.random.default_rng(seed) |
|
|
| def reset(self): |
| pass |
|
|
| def info(self): |
| return { |
| "action_downsample_ratio": 1, |
| "ckpt_path": "mock_linux_debug.ckpt", |
| } |
|
|
| def predict(self, obs): |
| n = self.action_len |
| return { |
| "j_left": self.rng.uniform(-1.0, 1.0, size=(n, 2)), |
| "j_right": self.rng.uniform(-1.0, 1.0, size=(n, 2)), |
| "buttons": self.rng.random(size=(n, len(BUTTON_ACTION_TOKENS))), |
| } |
|
|
|
|
| class NullVideoRecorder: |
| def __init__(self, *args, **kwargs): |
| pass |
|
|
| def __enter__(self): |
| return self |
|
|
| def __exit__(self, exc_type, exc, tb): |
| return False |
|
|
| def add_frame(self, frame): |
| pass |
|
|
|
|
| def parse_args(): |
| parser = argparse.ArgumentParser(description="VLM Inference") |
| parser.add_argument("--process", type=str, default="celeste.exe", help="Game to play") |
| parser.add_argument("--allow-menu", action="store_true", help="Allow menu actions (Disabled by default)") |
| parser.add_argument("--host", type=str, default="10.245.92.193", help="Host/IP of model server") |
| parser.add_argument("--port", type=int, default=5555, help="Port for model server") |
|
|
| |
| parser.add_argument( |
| "--mock-env", |
| action="store_true", |
| help="Use the Linux debug environment instead of the real GamepadEnv.", |
| ) |
| parser.add_argument( |
| "--debug-env", |
| type=str, |
| default="gym", |
| choices=["visual", "random", "gym"], |
| help="Linux/debug env backend. visual is deterministic and action-responsive; random is pure noise; gym uses Gymnasium.", |
| ) |
| parser.add_argument( |
| "--gym-id", |
| type=str, |
| default="CartPole-v1", |
| help="Gymnasium environment id used when --debug-env gym.", |
| ) |
| parser.add_argument( |
| "--real-env", |
| action="store_true", |
| help="Force real GamepadEnv even on Linux. Useful only if you really configured GUI/game backend.", |
| ) |
| parser.add_argument( |
| "--mock-policy", |
| type=bool, |
| default=False, |
| help="Use random policy outputs instead of connecting to the model server.", |
| ) |
| parser.add_argument( |
| "--mock-seed", |
| type=int, |
| default=0, |
| help="Random seed for mock env/policy.", |
| ) |
| parser.add_argument( |
| "--mock-width", |
| type=int, |
| default=1280, |
| help="Mock observation width.", |
| ) |
| parser.add_argument( |
| "--mock-height", |
| type=int, |
| default=720, |
| help="Mock observation height.", |
| ) |
| parser.add_argument( |
| "--mock-action-len", |
| type=int, |
| default=4, |
| help="Number of actions predicted by MockModelClient per step.", |
| ) |
| parser.add_argument( |
| "--max-steps", |
| type=int, |
| default=100, |
| help="Stop after N outer-loop steps. 0 means run forever, matching the original script.", |
| ) |
| parser.add_argument( |
| "--no-video", |
| action="store_true", |
| help="Do not write mp4 files. Useful when debugging without ffmpeg/video deps.", |
| ) |
| return parser.parse_args() |
|
|
|
|
| def preprocess_img(main_image): |
| main_cv = cv2.cvtColor(np.array(main_image), cv2.COLOR_RGB2BGR) |
| final_image = cv2.resize(main_cv, (256, 256), interpolation=cv2.INTER_AREA) |
| return Image.fromarray(cv2.cvtColor(final_image, cv2.COLOR_BGR2RGB)) |
|
|
|
|
| def make_zero_action(): |
| return OrderedDict( |
| [ |
| ("WEST", 0), |
| ("SOUTH", 0), |
| ("BACK", 0), |
| ("DPAD_DOWN", 0), |
| ("DPAD_LEFT", 0), |
| ("DPAD_RIGHT", 0), |
| ("DPAD_UP", 0), |
| ("GUIDE", 0), |
| ("AXIS_LEFTX", np.array([0], dtype=np.int64)), |
| ("AXIS_LEFTY", np.array([0], dtype=np.int64)), |
| ("LEFT_SHOULDER", 0), |
| ("LEFT_TRIGGER", np.array([0], dtype=np.int64)), |
| ("AXIS_RIGHTX", np.array([0], dtype=np.int64)), |
| ("AXIS_RIGHTY", np.array([0], dtype=np.int64)), |
| ("LEFT_THUMB", 0), |
| ("RIGHT_THUMB", 0), |
| ("RIGHT_SHOULDER", 0), |
| ("RIGHT_TRIGGER", np.array([0], dtype=np.int64)), |
| ("START", 0), |
| ("EAST", 0), |
| ("NORTH", 0), |
| ] |
| ) |
|
|
|
|
| def load_policy(args): |
| if args.mock_policy: |
| policy = MockModelClient(action_len=args.mock_action_len, seed=args.mock_seed) |
| else: |
| from nitrogen.inference_client import ModelClient |
|
|
| policy = ModelClient(host=args.host, port=args.port) |
| |
|
|
| policy.reset() |
| policy_info = policy.info() |
| return policy, policy_info |
|
|
|
|
| def make_env(args, use_mock_env): |
| if use_mock_env: |
| if args.debug_env == "visual": |
| return VisualDebugGamepadEnv( |
| game=args.process, |
| game_speed=1.0, |
| env_fps=60, |
| async_mode=True, |
| width=args.mock_width, |
| height=args.mock_height, |
| seed=args.mock_seed, |
| ) |
| if args.debug_env == "gym": |
| return GymRGBGamepadEnv( |
| game=args.process, |
| game_speed=1.0, |
| env_fps=60, |
| async_mode=True, |
| gym_id=args.gym_id, |
| seed=args.mock_seed, |
| ) |
| return MockGamepadEnv( |
| game=args.process, |
| game_speed=1.0, |
| env_fps=60, |
| async_mode=True, |
| width=args.mock_width, |
| height=args.mock_height, |
| seed=args.mock_seed, |
| ) |
|
|
| |
| |
| from nitrogen.game_env import GamepadEnv |
|
|
| return GamepadEnv( |
| game=args.process, |
| game_speed=1.0, |
| env_fps=60, |
| async_mode=True, |
| ) |
|
|
|
|
| def maybe_init_game_menu(args, env, use_mock_env): |
| if use_mock_env: |
| return |
|
|
| |
| if args.process not in {"isaac-ng.exe", "Cuphead.exe"}: |
| return |
|
|
| print(f"GamepadEnv ready for {args.process} at {env.env_fps} FPS") |
| input("Press enter to create a virtual controller and start rollouts...") |
| for i in range(3): |
| print(f"{3 - i}...") |
| time.sleep(1) |
|
|
| def press(button): |
| env.gamepad_emulator.press_button(button) |
| env.gamepad_emulator.gamepad.update() |
| time.sleep(0.05) |
| env.gamepad_emulator.release_button(button) |
| env.gamepad_emulator.gamepad.update() |
|
|
| press("SOUTH") |
| for _ in range(5): |
| press("EAST") |
| time.sleep(0.3) |
|
|
|
|
| def main(): |
| args = parse_args() |
|
|
| use_mock_env = args.mock_env or (sys.platform != "win32" and not args.real_env) |
| if use_mock_env: |
| print(f"Using Linux debug env backend: {args.debug_env}") |
| else: |
| print("Using real GamepadEnv.") |
|
|
| if args.no_video: |
| Recorder = NullVideoRecorder |
| else: |
| if VideoRecorder is None or create_viz is None: |
| raise RuntimeError( |
| "Failed to import nitrogen.inference_viz. Re-run with --no-video for lightweight debugging." |
| ) from _VIZ_IMPORT_ERROR |
| Recorder = VideoRecorder |
|
|
| policy, policy_info = load_policy(args) |
| action_downsample_ratio = int(policy_info.get("action_downsample_ratio", 1)) |
|
|
| ckpt_path = policy_info.get("ckpt_path", "unknown_ckpt") |
| CKPT_NAME = Path(ckpt_path).stem |
| NO_MENU = not args.allow_menu |
|
|
| PATH_DEBUG = PATH_REPO / "debug" |
| PATH_DEBUG.mkdir(parents=True, exist_ok=True) |
|
|
| PATH_OUT = (PATH_REPO / "out" / CKPT_NAME).resolve() |
| PATH_OUT.mkdir(parents=True, exist_ok=True) |
|
|
| BUTTON_PRESS_THRES = 0.5 |
| TOKEN_SET = BUTTON_ACTION_TOKENS |
|
|
| class RolloutFinished(Exception): |
| pass |
|
|
| video_files = sorted(PATH_OUT.glob("*_DEBUG.mp4")) |
| if video_files: |
| existing_numbers = [f.name.split("_")[0] for f in video_files] |
| existing_numbers = [int(n) for n in existing_numbers if n.isdigit()] |
| next_number = max(existing_numbers) + 1 if existing_numbers else 1 |
| else: |
| next_number = 1 |
|
|
| PATH_MP4_DEBUG = PATH_OUT / f"{next_number:04d}_DEBUG.mp4" |
| PATH_MP4_CLEAN = PATH_OUT / f"{next_number:04d}_CLEAN.mp4" |
| PATH_ACTIONS = PATH_OUT / f"{next_number:04d}_ACTIONS.json" |
|
|
| zero_action = make_zero_action() |
|
|
| print("Model loaded, starting environment...") |
| if not use_mock_env: |
| for i in range(3): |
| print(f"{3 - i}...") |
| time.sleep(1) |
|
|
| env = make_env(args, use_mock_env=use_mock_env) |
| maybe_init_game_menu(args, env, use_mock_env=use_mock_env) |
|
|
| env.reset() |
| env.pause() |
|
|
| |
| obs, reward, terminated, truncated, info = env.step(action=zero_action) |
|
|
| step_count = 0 |
|
|
| with Recorder(str(PATH_MP4_DEBUG), fps=60, crf=32, preset="medium") as debug_recorder: |
| with Recorder(str(PATH_MP4_CLEAN), fps=60, crf=28, preset="medium") as clean_recorder: |
| try: |
| while True: |
| obs = preprocess_img(obs) |
| obs.save(PATH_DEBUG / f"{step_count:05d}.png") |
|
|
| pred = policy.predict(obs) |
|
|
| j_left = np.asarray(pred["j_left"]) |
| j_right = np.asarray(pred["j_right"]) |
| buttons = np.asarray(pred["buttons"]) |
|
|
| n = len(buttons) |
| assert n == len(j_left) == len(j_right), "Mismatch in action lengths" |
|
|
| env_actions = [] |
|
|
| for i in range(n): |
| move_action = zero_action.copy() |
|
|
| xl, yl = j_left[i] |
| xr, yr = j_right[i] |
| move_action["AXIS_LEFTX"] = np.array([int(xl * 32767)], dtype=np.int64) |
| move_action["AXIS_LEFTY"] = np.array([int(yl * 32767)], dtype=np.int64) |
| move_action["AXIS_RIGHTX"] = np.array([int(xr * 32767)], dtype=np.int64) |
| move_action["AXIS_RIGHTY"] = np.array([int(yr * 32767)], dtype=np.int64) |
|
|
| button_vector = buttons[i] |
| assert len(button_vector) == len(TOKEN_SET), ( |
| "Button vector length does not match token set length" |
| ) |
|
|
| for name, value in zip(TOKEN_SET, button_vector): |
| if "TRIGGER" in name: |
| move_action[name] = np.array([int(value * 255)], dtype=np.int64) |
| else: |
| move_action[name] = 1 if value > BUTTON_PRESS_THRES else 0 |
|
|
| env_actions.append(move_action) |
|
|
| print( |
| f"Executing {len(env_actions)} actions, " |
| f"each action will be repeated {action_downsample_ratio} times" |
| ) |
|
|
| for i, a in enumerate(env_actions): |
| if NO_MENU: |
| if a["START"]: |
| print("Model predicted start, disabling this action") |
| a["GUIDE"] = 0 |
| a["START"] = 0 |
| a["BACK"] = 0 |
|
|
| for _ in range(action_downsample_ratio): |
| obs, reward, terminated, truncated, info = env.step(action=a) |
|
|
| |
| |
| |
| |
| |
|
|
| |
|
|
| if not args.no_video: |
| obs_viz = np.array(obs).copy() |
| clean_viz = cv2.resize( |
| obs_viz, |
| (1920, 1080), |
| interpolation=cv2.INTER_AREA, |
| ) |
| debug_viz = create_viz( |
| cv2.resize( |
| obs_viz, |
| (1280, 720), |
| interpolation=cv2.INTER_AREA, |
| ), |
| i, |
| j_left, |
| j_right, |
| buttons, |
| token_set=TOKEN_SET, |
| ) |
| debug_recorder.add_frame(debug_viz) |
| clean_recorder.add_frame(clean_viz) |
|
|
| with open(PATH_ACTIONS, "a") as f: |
| for i, a in enumerate(env_actions): |
| serializable_action = {} |
| for k, v in a.items(): |
| if isinstance(v, np.ndarray): |
| serializable_action[k] = v.tolist() |
| else: |
| serializable_action[k] = v |
| serializable_action["step"] = step_count |
| serializable_action["substep"] = i |
| json.dump(serializable_action, f) |
| f.write("\n") |
|
|
| step_count += 1 |
| if args.max_steps > 0 and step_count >= args.max_steps: |
| print(f"Reached --max-steps={args.max_steps}, exiting.") |
| break |
| |
| except RolloutFinished: |
| print("Rollout finished. Closing environment...") |
| |
| finally: |
| env.unpause() |
| env.close() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|